Compare commits

...

148 Commits

Author SHA1 Message Date
Anthony Stirling
3868b4eca2 updates 2024-04-05 13:05:16 +01:00
Han
64f8765115 Update messages_tr_TR.properties (#1032)
update TR
2024-04-05 12:58:54 +01:00
github-actions[bot]
3804656218 💾 Update Version (#1033)
💾 Sync Versions
> Made via sync_versions.yml

Co-authored-by: GitHub Action action@github.com <GitHub Action action@github.com>
2024-04-05 12:58:05 +01:00
Anthony Stirling
a5d824213c OCR fix for #1019 2024-04-05 12:51:22 +01:00
albanobattistella
160a4e9f8d Update messages_it_IT.properties (#1031) 2024-04-04 21:52:24 +01:00
Ludy
74a0574462 Update progress of language (#1029) 2024-04-04 10:16:10 +01:00
Muhammad Sani Ibrahim
1cf23b3542 Update README.md (#606)
Make the English more standard

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-04-03 22:51:59 +01:00
github-actions[bot]
2ef1242cd8 💾 Update Version (#1015)
💾 Sync Versions
> Made via sync_versions.yml

Co-authored-by: GitHub Action action@github.com <GitHub Action action@github.com>
2024-04-03 22:41:33 +01:00
pixeebot[bot]
54c3bee205 Replaced Stream.collect(Collectors.toList()) with Stream.toList() (Sonar) (#1018)
Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-04-03 22:41:24 +01:00
NicolasFR
a63c0a3625 lang: update fr_FR (#1020)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-04-03 22:41:07 +01:00
Dimitris Doukas
3103a0bffc Enhance description for "Split PDF" functionality (#1025)
* Update screen of homepage

* Fix description for "Split PDF" except AR

* Fix description for "Split PDF" in AR

* Fix one mistake on AR

* Keep clear this branch from other branches
2024-04-03 22:40:35 +01:00
Dimitris Doukas
5d71ffbfaa Update homepage screen in README (#1021)
Update screen of homepage
2024-04-03 22:40:09 +01:00
CocoMaster-AI
c0c137d1b0 Sponsor information supports translate now (#1023)
Sponsored information supports translate now
2024-04-03 10:02:01 +01:00
Anthony Stirling
8625db2885 Update build.gradle 2024-04-01 22:10:33 +01:00
Anthony Stirling
a2a969a0a0 Chinese font and word conv fix (#1014)
* remove lite package

* alphine update for word conversion issue #995
2024-04-01 22:04:27 +01:00
Anthony Stirling
8a6386ca73 remove lite package (#1012) 2024-04-01 18:33:58 +01:00
NicolasFR
255c018415 Update messages_fr_FR.properties (#1011)
fix(lang): fix a translation error
2024-04-01 17:55:22 +01:00
albanobattistella
df27ed6907 Update messages_it_IT.properties (#1010) 2024-04-01 15:04:05 +01:00
CocoMaster-AI
989491b903 Extract text from error pages and sync text variable to all lang (#1008)
* feat: extract text from error pages

* feat: sync text variables to langs.properties

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-04-01 11:04:56 +01:00
NicolasFR
664dd62d8b doc: add --break-system-packages (#1001)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-04-01 10:56:33 +01:00
albanobattistella
3d6b145db5 Update messages_it_IT.properties (#1002) 2024-03-30 19:51:44 +00:00
CocoMaster-AI
37a63242a6 Update messages_ru_RU.properties (#999) 2024-03-30 10:26:51 +00:00
Eric
dfb8c64f5a fix: switch to pdftohtml for pdf to html conversions (#998)
* fix: switch to pdftohtml for pdf to html conversions

* build: include poppler-utils in dockerfile for pdftohtml
2024-03-29 17:02:33 -04:00
github-actions[bot]
27bbf7a513 💾 Update Version (#997)
💾 Sync Versions
> Made via sync_versions.yml

Co-authored-by: GitHub Action action@github.com <GitHub Action action@github.com>
2024-03-29 11:04:05 +00:00
Anthony Stirling
ca890e4b32 Pipeline fix quick 2024-03-29 11:03:13 +00:00
CocoMaster-AI
4834d01223 feat: translate messages_ko_KR.properties, messages_ru_RU.properties completed (#996)
* feat: translate ko_KR.properties completed

* feat: feat: translate ru_RU.properties completed
2024-03-29 10:17:10 +00:00
albanobattistella
84b3bb1aed Update messages_it_IT.properties (#993) 2024-03-28 20:50:29 +00:00
Anthony Stirling
a9679da719 Revert weasy 2024-03-28 19:38:56 +00:00
Anthony Stirling
f10b3ffe3c multiTool.uploadPrompts 2024-03-28 17:45:58 +00:00
SuperFan
ea982d6412 fix: Add MultiTool UploadPrompts zh_CN And en_US (#977)
Co-authored-by: superfan <admin@henniubi.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-03-28 17:44:21 +00:00
pixeebot[bot]
1035a3be31 Define a constant for a literal string that is duplicated n times (Sonar) (#978)
Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-03-28 17:44:10 +00:00
github-actions[bot]
c16db14cd9 Update 3rd Party Licenses (#992)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-28 17:43:30 +00:00
github-actions[bot]
1698f9d5df 💾 Update Version (#991)
💾 Sync Versions
> Made via sync_versions.yml

Co-authored-by: GitHub Action action@github.com <GitHub Action action@github.com>
2024-03-28 17:43:20 +00:00
Anthony Stirling
08e43cc89c fix #986 and #989 2024-03-28 17:09:21 +00:00
NicolasFR
fb1baaa275 fix: Fix package name of openjdk for debian (#990) 2024-03-28 16:53:54 +00:00
CocoMaster-AI
eda838d6f8 feat: Extract translation text from html and finished the mainly translation of zh_CN, zh_TW, ja_JP file (#987)
* feat: translate zh_cn properties

* feat: adjust some description messages_zh_CN.properties

* feat: translate zh_TW file done

* feat: zh_CN semantic optimization

* feat: Supplementary ja_JP translation

* feat: extract rest text

* feat: Sync to all lang.properties

* feat: translate zh_CN, zh_TW, ja_JP file

* fix: small bug

---------

Co-authored-by: yanyu_lin <yanyu_lin@bestsign.cn>
2024-03-28 16:53:39 +00:00
Anthony Stirling
2fff3083ae Update TextFinder.java (#980) 2024-03-26 19:25:16 +00:00
tkymmm
7e2d58b3e8 Update messages_ja_JP.properties (#975) 2024-03-26 06:55:51 +00:00
NeilJared
31ec385282 Update messages_es_ES.properties (#969)
Updated es_ES translation (100% done)
2024-03-25 19:07:53 +00:00
Anthony Stirling
14ef7c0a72 dummy commit (#968)
Update README.md
2024-03-24 10:16:14 +00:00
Anthony Stirling
c9331afeac github bug 2024-03-22 20:15:53 +00:00
Anthony Stirling
09cb92e235 github bug 2024-03-22 20:15:42 +00:00
Anthony Stirling
6bd6e6563b Update Chart.yaml 2024-03-22 20:01:37 +00:00
Anthony Stirling
3c08c20426 Update build.gradle 2024-03-22 19:57:43 +00:00
Anthony Stirling
3800e3e465 Update view-pdf.html 2024-03-22 18:15:10 +00:00
github-actions[bot]
e2bd73dbf3 Update 3rd Party Licenses (#966)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-21 21:42:34 +00:00
github-actions[bot]
a20c3018ae Update 3rd Party Licenses (#965)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-21 21:41:09 +00:00
dependabot[bot]
7f17b33859 Bump org.apache.pdfbox:pdfbox from 3.0.1 to 3.0.2 (#963)
Bumps org.apache.pdfbox:pdfbox from 3.0.1 to 3.0.2.

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:40:59 +00:00
dependabot[bot]
029937a1c5 Bump org.springframework.boot:spring-boot-starter-test from 3.2.3 to 3.2.4 (#961)
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.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:24:20 +00:00
dependabot[bot]
cfcf02708c Bump org.springframework.boot from 3.2.3 to 3.2.4 (#962)
Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:24:15 +00:00
dependabot[bot]
c1724ef74c Bump org.springdoc:springdoc-openapi-starter-webmvc-ui from 2.2.0 to 2.4.0 (#964)
Bump org.springdoc:springdoc-openapi-starter-webmvc-ui

Bumps [org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi) from 2.2.0 to 2.4.0.
- [Release notes](https://github.com/springdoc/springdoc-openapi/releases)
- [Changelog](https://github.com/springdoc/springdoc-openapi/blob/main/CHANGELOG.md)
- [Commits](https://github.com/springdoc/springdoc-openapi/compare/v2.2.0...v2.4.0)

---
updated-dependencies:
- dependency-name: org.springdoc:springdoc-openapi-starter-webmvc-ui
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:24:06 +00:00
github-actions[bot]
3c53f97c36 Update 3rd Party Licenses (#960)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-21 21:17:53 +00:00
dependabot[bot]
aa895d10ac Bump org.springframework.boot:spring-boot-starter-thymeleaf from 3.2.3 to 3.2.4 (#954)
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.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:13:55 +00:00
dependabot[bot]
6c603618ce Bump org.springframework.boot:spring-boot-starter-security from 3.2.3 to 3.2.4 (#956)
Bump org.springframework.boot:spring-boot-starter-security

Bumps [org.springframework.boot:spring-boot-starter-security](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:12:33 +00:00
dependabot[bot]
78aa0d4c61 Bump org.springframework.boot:spring-boot-starter-data-jpa from 3.2.3 to 3.2.4 (#953)
Bump org.springframework.boot:spring-boot-starter-data-jpa

Bumps [org.springframework.boot:spring-boot-starter-data-jpa](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:10:56 +00:00
dependabot[bot]
da3fc72e5c Bump org.projectlombok:lombok from 1.18.28 to 1.18.32 (#955)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.28 to 1.18.32.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.28...v1.18.32)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:09:30 +00:00
dependabot[bot]
a800766cb8 Bump org.commonmark:commonmark from 0.21.0 to 0.22.0 (#957)
Bumps [org.commonmark:commonmark](https://github.com/commonmark/commonmark-java) from 0.21.0 to 0.22.0.
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0)

---
updated-dependencies:
- dependency-name: org.commonmark:commonmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 21:08:00 +00:00
Ludy
67a1529dc7 Change to html5 (#958)
* Change to html5

with Nu Html Checker

* Update scale-pages.html

* Update sign.html

* Update common.html

* Update common.html

* Update login.html
2024-03-21 20:58:01 +00:00
github-actions[bot]
77354f47bf Update 3rd Party Licenses (#952)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-21 20:12:47 +00:00
dependabot[bot]
49ea07fd13 Bump io.github.pixee:java-security-toolkit from 1.1.2 to 1.1.3 (#946)
Bumps [io.github.pixee:java-security-toolkit](https://github.com/pixee/java-security-toolkit) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/pixee/java-security-toolkit/releases)
- [Commits](https://github.com/pixee/java-security-toolkit/compare/v1.1.2...v1.1.3)

---
updated-dependencies:
- dependency-name: io.github.pixee:java-security-toolkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 20:09:55 +00:00
dependabot[bot]
bb69c67b52 Bump org.springframework.boot:spring-boot-starter-actuator from 3.2.3 to 3.2.4 (#950)
Bump org.springframework.boot:spring-boot-starter-actuator

Bumps [org.springframework.boot:spring-boot-starter-actuator](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 20:09:37 +00:00
dependabot[bot]
6b29c28e2e Bump org.springframework.boot:spring-boot-devtools from 3.2.3 to 3.2.4 (#949)
Bumps [org.springframework.boot:spring-boot-devtools](https://github.com/spring-projects/spring-boot) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 20:09:32 +00:00
dependabot[bot]
ba0fe43f31 Bump org.springframework.boot:spring-boot-starter-web from 3.2.3 to 3.2.4 (#948)
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.2.3 to 3.2.4.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 20:09:23 +00:00
dependabot[bot]
e54597f108 Bump com.github.jk1.dependency-license-report from 2.5 to 2.6 (#932)
Bumps com.github.jk1.dependency-license-report from 2.5 to 2.6.

---
updated-dependencies:
- dependency-name: com.github.jk1.dependency-license-report
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 20:09:10 +00:00
dependabot[bot]
9809ad9d7b Bump io.micrometer:micrometer-core from 1.12.3 to 1.12.4 (#930)
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.12.3 to 1.12.4.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.3...v1.12.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 19:36:45 +00:00
dependabot[bot]
1d4ad19acd Bump org.springframework:spring-webmvc from 6.1.4 to 6.1.5 (#934)
Bumps [org.springframework:spring-webmvc](https://github.com/spring-projects/spring-framework) from 6.1.4 to 6.1.5.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.4...v6.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 19:36:12 +00:00
dependabot[bot]
2c24e754be Bump org.apache.pdfbox:xmpbox from 3.0.1 to 3.0.2 (#933)
Bumps org.apache.pdfbox:xmpbox from 3.0.1 to 3.0.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 19:35:55 +00:00
albanobattistella
4a3326a560 Update messages_it_IT.properties (#927) 2024-03-17 14:24:07 +00:00
Ludy
24862e2d4a changes characters in multiple languages - Unicode ascii (#925)
* unicode to readable font

* Update messages_de_DE.properties
2024-03-17 11:09:55 +00:00
Ludy
51bb26ae34 Fix: Error after logging out and selecting another language (#924)
Fix exception url after logout
2024-03-17 09:57:17 +00:00
Anthony Stirling
f474651f36 lang, save to browser (#923) 2024-03-17 09:33:05 +00:00
tongkl1
11193b1b6d Fix typo in example yaml file (#922) 2024-03-16 09:49:28 +00:00
github-actions[bot]
3066b3e500 Update 3rd Party Licenses (#919)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-14 23:00:38 +00:00
dependabot[bot]
217f112bc4 Bump com.google.zxing:core from 3.5.2 to 3.5.3 (#779)
Bumps [com.google.zxing:core](https://github.com/zxing/zxing) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/zxing/zxing/releases)
- [Changelog](https://github.com/zxing/zxing/blob/master/CHANGES)
- [Commits](https://github.com/zxing/zxing/compare/zxing-3.5.2...zxing-3.5.3)

---
updated-dependencies:
- dependency-name: com.google.zxing:core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-03-14 22:59:27 +00:00
github-actions[bot]
e1bb0cf5ec Update 3rd Party Licenses (#914)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-03-13 22:45:32 +00:00
Anthony Stirling
3930c25a75 Frooodle patch 2 (#913)
* Update licenses-update.yml

* Update build.gradle
2024-03-13 22:34:24 +00:00
Anthony Stirling
b27e79cb52 Update build.gradle (#912) 2024-03-13 22:29:55 +00:00
Anthony Stirling
daf6486b86 Update licenses-update.yml (#911) 2024-03-13 22:26:39 +00:00
Ludy
1af41f8ea5 🔨 add Developer tools/scripts (#828)
* 🔨 add Developer tools

* Update downloader.js

* Update game.js

* Update .pre-commit-config.yaml

* Update sync_versions.yml

* removed: mixed line ending tool

* Update swagger.yml

* Update push-docker.yml

* Update build.yml

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-03-13 22:18:15 +00:00
Listiges Känguru
70e4ac21df Update messages_de_DE.properties (#903)
Added missing translations, improved some existing translations.
2024-03-13 22:11:43 +00:00
Anthony Stirling
95d9d85ca2 dep bumps and nonroot bypass (#910)
* dep bumps and nonroot bypass

* log changing
2024-03-13 22:09:56 +00:00
Ludy
9cc7a49d12 Enforcing Username Uniqueness (#906)
* Enforcing Username Uniqueness

Changes in UserService.java:

Added a new method findByUsername to allow searching for usernames regardless of case sensitivity.
Added a new method isUsernameValid to validate the username.
Changes in UserController.java:

Updated the changeUsername method to ensure the new username is valid before changing it.
Updated the editUser method to ensure the new username is unique and valid.
Changes in UserRepository.java:

Added a custom JPQL query to search for usernames regardless of case sensitivity.
Changes in HTML templates (account.html and addUsers.html):

Error messages are displayed if a username is invalid or already exists.

* JPAs auto
2024-03-13 22:09:16 +00:00
Anthony Stirling
ae73595335 Number of fixes and making pipline LIVE ! (#907)
Closes #889 and #332
#710
#901
#885
2024-03-13 19:15:10 +00:00
pavedroad
ac620082ec chore: fix some typos (#900)
Signed-off-by: pavedroad <qcqs@outlook.com>
2024-03-12 19:42:15 -04:00
Anthony Stirling
1e4134c7d1 Number fxes (#898)
* init

* user and pass to just pass lang update

* session management fixes and avoid demo user locking

* fix for UMASK and extract cleanups

* fixes for user #889 and #332

* increase session count for demo site

* fix

* gcc

* formatting

* number fixes init

* || true test

* version bump

* Hardening suggestions for Stirling-PDF / numberFxes (#899)

Switch order of literals to prevent NullPointerException

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

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-03-10 14:00:00 +00:00
albanobattistella
a7bcdd0003 Update messages_it_IT.properties (#891) 2024-03-09 14:03:58 +00:00
Anthony Stirling
121af0501a fixes for user permissions (#892) 2024-03-09 14:03:46 +00:00
Ludy
82c4e9cf41 Fix TypeError by rotation (#890)
Fixes the error in the PDF multitool when rotating all pages at the same time.
2024-03-09 12:18:00 +00:00
Ludy
142e11a59a added missing translation strings (#888) 2024-03-09 08:08:33 +00:00
Anthony Stirling
08205ed32d Custom uid (#883)
* init

* user and pass to just pass lang update

* session management fixes and avoid demo user locking

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

* user and pass to just pass lang update

* session management fixes and avoid demo user locking

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

Switch order of literals to prevent NullPointerException

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

---------

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

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

* Add error message

* Update Role.java

* default Language without translation

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

* version bump

* Update docker-compose-latest.yml

* remove customApp

---------

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

* Update RearrangePagesPDFController.java

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

* Removed outdated import

---------

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

* Use the mergeDocuments method in MergeController to implement merging

---------

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

* Update SPdfApplication.java

* Update SPdfApplication.java

---------

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

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

* Rename messages_sr_Latn_RS.properties to messages_sr_LATN_RS.properties

* Update languages.html

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 13:24:38 +00:00
dependabot[bot]
918f5954b7 Bump io.micrometer:micrometer-core from 1.12.2 to 1.12.3 (#806)
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.12.2 to 1.12.3.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.2...v1.12.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 13:23:10 +00:00
albanobattistella
148dcdaee7 Update messages_it_IT.properties (#816) 2024-02-17 13:22:15 +00:00
Anthony Stirling
a5f0777892 Update ConfigInitializer.java for auto settings removal 2024-02-17 13:10:00 +00:00
Ludy
010426d488 Document (#803)
* Update HowToAddNewLanguage.md

* Update HowToUseOCR.md

* Update LocalRunGuide.md

* Update README.md

* Update LocalRunGuide.md

* Update README.md

---------

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

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

* HTML corrections

* JS corrections

* JAVA corrections

* remove tab

* CSS corrections 2

* JS corrections 2

* back to the roots

* max-linie 127

* add slash hr|br

* return bootstrap-icons.css

* return bootstrap-icons.min.css

* return bootstrap.min.css

* Update bootstrap-icons.css

* Update bootstrap-icons.min.css

* Update bootstrap-icons.min.css

* Update bootstrap.min.css

* CSS corrections

* HTML corrections

* JS corrections

* JAVA corrections

* remove tab

* CSS corrections 2

* JS corrections 2

* back to the roots

* max-linie 127

* add slash hr|br

* return bootstrap-icons.css

* Update bootstrap-icons.css

* Bootstrap CSS

* Update prism.css
2024-02-16 21:49:06 +00:00
Anthony Stirling
68f582bcb9 Merge pull request #811 from NeilJared/main
Update messages_es_ES.properties
2024-02-14 21:27:59 +00:00
NeilJared
639aed7120 Update messages_es_ES.properties
Updated es_ES translation
2024-02-14 12:22:47 +01:00
NeilJared
994bb4d1d2 Update messages_es_ES.properties
Minor bug fixes: es_ES translation
2024-02-14 11:57:34 +01:00
Anthony Stirling
80b11a55fa Merge pull request #804 from Ludy87/missing_pageSelectionPrompt
add missing propertie pageSelectionPrompt
2024-02-11 22:43:08 +00:00
Ludy87
3cfb554623 missing propertie pageSelectionPrompt 2024-02-11 21:10:04 +01:00
Eric
e84f9c5946 Merge pull request #796 from Ludy87/languages
Languages
2024-02-11 14:41:15 -05:00
Ludy87
17cc31d6e7 add placeholder translation 2024-02-11 14:32:34 -05:00
Ludy87
0c6e10a6dd add separator 2024-02-11 14:32:34 -05:00
Ludy87
297c57631f remove dublicate -> change showJS.tags to autoRedact.tags 2024-02-11 14:32:34 -05:00
Ludy87
bd4e252bb6 added missing strings 2024-02-11 14:32:33 -05:00
Ludy87
0ce34c70bc Update messages_de_DE.properties 2024-02-11 14:32:33 -05:00
Ludy87
4df75cfba1 Update messages_de_DE.properties 2024-02-11 14:32:33 -05:00
Ludy87
2aa435bcfb double spaces removed 2024-02-11 14:32:33 -05:00
Ludy87
0a4a9e6947 Removed unnecessary characters and added DE missing strings 2024-02-11 14:32:32 -05:00
Anthony Stirling
d5860d0b55 Update PdfToTextOrRTFRequest.java 2024-02-11 18:47:43 +00:00
Eric
6a487ce514 Merge pull request #802 from Stirling-Tools/normalize_files
refactor: normalize all files (strip trailing whitespace + convert CRLF to LF)
2024-02-11 13:11:37 -05:00
sbplat
4f3b85e66b refactor: normalize files that contained both CRLF and LF 2024-02-11 12:14:21 -05:00
sbplat
370cd97e05 misc: update .git-blame-ignore-revs to ignore normalize files commit in blame 2024-02-11 11:54:03 -05:00
sbplat
55d4fda01b refactor: normalize files 2024-02-11 11:47:00 -05:00
270 changed files with 28845 additions and 25402 deletions

View File

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

51
.github/scripts/check_duplicates.py vendored Normal file
View File

@@ -0,0 +1,51 @@
import sys
def find_duplicate_keys(file_path):
"""
Finds duplicate keys in a properties file and returns their occurrences.
This function reads a properties file, identifies any keys that occur more than
once, and returns a dictionary with these keys and the line numbers of their occurrences.
Parameters:
file_path (str): The path to the properties file to be checked.
Returns:
dict: A dictionary where each key is a duplicated key in the file, and the value is a list
of line numbers where the key occurs.
"""
with open(file_path, "r", encoding="utf-8") as file:
lines = file.readlines()
keys = {}
duplicates = {}
for line_number, line in enumerate(lines, start=1):
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key = line.split("=", 1)[0].strip()
if key in keys:
# If the key already exists, add the current line number
duplicates.setdefault(key, []).append(line_number)
# Also add the first instance of the key if not already done
if keys[key] not in duplicates[key]:
duplicates[key].insert(0, keys[key])
else:
# Store the line number of the first instance of the key
keys[key] = line_number
return duplicates
if __name__ == "__main__":
failed = False
for ar in sys.argv[1:]:
duplicates = find_duplicate_keys(ar)
if duplicates:
for key, lines in duplicates.items():
lines_str = ", ".join(map(str, lines))
print(f"{key} duplicated in {ar} on lines {lines_str}")
failed = True
if failed:
sys.exit(1)

84
.github/scripts/check_tabulator.py vendored Normal file
View File

@@ -0,0 +1,84 @@
"""check_tabulator.py"""
import argparse
import sys
def check_tabs(file_path):
"""
Checks for tabs in the specified file.
Args:
file_path (str): The path to the file to be checked.
Returns:
bool: True if tabs are found, False otherwise.
"""
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
if "\t" in content:
print(f"Tab found in {file_path}")
return True
return False
def replace_tabs_with_spaces(file_path, replace_with=" "):
"""
Replaces tabs with a specified number of spaces in the file.
Args:
file_path (str): The path to the file where tabs will be replaced.
replace_with (str): The character(s) to replace tabs with. Defaults to two spaces.
"""
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
updated_content = content.replace("\t", replace_with)
with open(file_path, "w", encoding="utf-8") as file:
file.write(updated_content)
def main():
"""
Main function to replace tabs with spaces in the provided files.
The replacement character and files to check are taken from command line arguments.
"""
# Create ArgumentParser instance
parser = argparse.ArgumentParser(
description="Replace tabs in files with specified characters."
)
# Define optional argument `--replace_with`
parser.add_argument(
"--replace_with",
default=" ",
help="Character(s) to replace tabs with. Default is two spaces.",
)
# Define argument for file paths
parser.add_argument("files", metavar="FILE", nargs="+", help="Files to process.")
# Parse arguments
args = parser.parse_args()
# Extract replacement characters and files from the parsed arguments
replace_with = args.replace_with
files_checked = args.files
error = False
for file_path in files_checked:
if check_tabs(file_path):
replace_tabs_with_spaces(file_path, replace_with)
error = True
if error:
print("Error: Originally found tabs in HTML files, now replaced.")
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()

67
.github/scripts/gradle_to_chart.py vendored Normal file
View File

@@ -0,0 +1,67 @@
import re
import yaml
# Paths to the files
chart_yaml_path = "chart/stirling-pdf/Chart.yaml"
gradle_path = "build.gradle"
def get_chart_version(path):
"""
Reads the appVersion from Chart.yaml.
Args:
path (str): The file path to the Chart.yaml.
Returns:
str: The appVersion if found, otherwise an empty string.
"""
with open(path, encoding="utf-8") as file:
chart_yaml = yaml.safe_load(file)
return chart_yaml.get("appVersion", "")
def get_gradle_version(path):
"""
Extracts the version from build.gradle.
Args:
path (str): The file path to the build.gradle.
Returns:
str: The version if found, otherwise an empty string.
"""
with open(path, encoding="utf-8") as file:
for line in file:
if "version =" in line:
# Extracts the value after 'version ='
return re.search(r'version\s*=\s*[\'"](.+?)[\'"]', line).group(1)
return ""
def update_chart_version(path, new_version):
"""
Updates the appVersion in Chart.yaml with a new version.
Args:
path (str): The file path to the Chart.yaml.
new_version (str): The new version to update to.
"""
with open(path, encoding="utf-8") as file:
chart_yaml = yaml.safe_load(file)
chart_yaml["appVersion"] = new_version
with open(path, "w", encoding="utf-8") as file:
yaml.safe_dump(chart_yaml, file)
# Main logic
chart_version = get_chart_version(chart_yaml_path)
gradle_version = get_gradle_version(gradle_path)
if chart_version != gradle_version:
print(
f"Versions do not match. Updating Chart.yaml from {chart_version} to {gradle_version}."
)
update_chart_version(chart_yaml_path, gradle_version)
else:
print("Versions match. No update required.")

View File

@@ -3,8 +3,14 @@ name: "Build repo"
on:
push:
branches: [ "main" ]
paths-ignore:
- ".github/**"
- "**/*.md"
pull_request:
branches: [ "main" ]
paths-ignore:
- ".github/**"
- "**/*.md"
jobs:
build:

View File

@@ -32,17 +32,30 @@ jobs:
run: |
mv build/reports/dependency-license/index.json src/main/resources/static/3rdPartyLicenses.json
- name: Check for Changes
id: git-check
- name: Set up git config
run: |
git config --global user.email "GitHub Action <action@github.com>"
git config --global user.name "GitHub Action <action@github.com>"
- name: Run git add
run: |
git add src/main/resources/static/3rdPartyLicenses.json
git diff --staged --exit-code || echo "changes=true" >> $GITHUB_ENV
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Commit and Push Changes
if: env.changes == 'true'
run: |
git config --global user.name 'Stirling-PDF-Bot'
git config --global user.email 'Stirling-PDF-Bot@stirlingtools.com'
git commit -m "Update 3rd Party Licenses"
git push
- name: Create Pull Request
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: update-3rd-party-licenses
title: "Update 3rd Party Licenses"
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
draft: false
delete-branch: true

View File

@@ -3,9 +3,10 @@ name: Push Docker Image with VersionNumber
on:
workflow_dispatch:
push:
branches:
branches:
- master
- main
permissions:
contents: read
packages: write
@@ -15,13 +16,13 @@ jobs:
steps:
- uses: actions/checkout@v3.5.2
- name: Set up JDK 17
uses: actions/setup-java@v3.11.0
with:
java-version: '17'
distribution: 'temurin'
- uses: gradle/gradle-build-action@v2.4.2
env:
@@ -32,11 +33,11 @@ jobs:
- name: Make Gradle wrapper executable
run: chmod +x gradlew
- name: Get version number
id: versionNumber
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
with:
@@ -53,7 +54,7 @@ jobs:
- name: Convert repository owner to lowercase
id: repoowner
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')"
- name: Generate tags
id: meta
uses: docker/metadata-action@v4.4.0
@@ -82,7 +83,7 @@ jobs:
cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args:
build-args:
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
@@ -99,7 +100,7 @@ jobs:
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-ultra-lite
uses: docker/build-push-action@v4.0.0
@@ -112,40 +113,6 @@ jobs:
cache-to: type=gha,mode=max
tags: ${{ steps.meta2.outputs.tags }}
labels: ${{ steps.meta2.outputs.labels }}
build-args:
build-args:
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
- name: Generate tags lite
id: meta3
uses: docker/metadata-action@v4.4.0
if: github.ref != 'refs/heads/main'
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-lite,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=latest-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-lite
uses: docker/build-push-action@v4.0.0
if: github.ref != 'refs/heads/main'
with:
context: .
file: ./Dockerfile-lite
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.meta3.outputs.tags }}
labels: ${{ steps.meta3.outputs.labels }}
build-args:
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
- name: Build and Push Helm Chart
run: |
helm package chart/stirling-pdf
helm push stirling-pdf-chart-1.0.0.tgz oci://registry-1.docker.io/frooodle
platforms: linux/amd64,linux/arm64/v8

View File

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

View File

@@ -3,8 +3,9 @@ name: Update Swagger
on:
workflow_dispatch:
push:
branches:
branches:
- master
jobs:
push:
@@ -12,13 +13,13 @@ jobs:
steps:
- uses: actions/checkout@v3.5.2
- name: Set up JDK 17
uses: actions/setup-java@v3.11.0
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
@@ -34,4 +35,4 @@ jobs:
run: |
curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}"
env:
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}

51
.github/workflows/sync_versions.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Sync Versions
on:
push:
branches:
- main
paths:
- "build.gradle"
permissions:
contents: write
pull-requests: write
jobs:
sync-versions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
- name: Set up Python
uses: actions/setup-python@v5.0.0
with:
python-version: '3.x'
- name: Install dependencies
run: pip install pyyaml
- name: Sync versions
run: python .github/scripts/gradle_to_chart.py
- name: Set up git config
run: |
git config --global user.email "GitHub Action <action@github.com>"
git config --global user.name "GitHub Action <action@github.com>"
- name: Run git add
run: |
git add .
git diff --staged --quiet || git commit -m ":floppy_disk: Sync Versions
> Made via sync_versions.yml" || echo "no changes"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update files
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: sync_version
title: ":floppy_disk: Update Version"
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
draft: false
delete-branch: true

View File

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

252
.gitignore vendored
View File

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

37
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,37 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.1
hooks:
- id: ruff
args:
- --fix
- --line-length=127
files: ^((.github/scripts)/.+)?[^/]+\.py$
- id: ruff-format
files: ^((.github/scripts)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
args:
- --ignore-words-list=
- --skip="./.*,*.csv,*.json,*.ambr"
- --quiet-level=2
files: \.(properties|html|css|js|py|md)$
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile)
- repo: local
hooks:
- id: check-duplicate-properties-keys
name: Check Duplicate Properties Keys
entry: python .github/scripts/check_duplicates.py
language: python
files: ^(src)/.+\.properties$
- repo: local
hooks:
- id: check-html-tabs
name: Check HTML for tabs
# args: ["--replace_with= "]
entry: python .github/scripts/check_tabulator.py
language: python
exclude: ^src/main/resources/static/pdfjs/
files: ^.*(\.html|\.css|\.js)$

View File

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

View File

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

View File

@@ -7,10 +7,10 @@ ARG VERSION_TAG
ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \
VERSION_TAG=$VERSION_TAG \
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
# PUID=1000 \
# PGID=1000 \
# UMASK=022 \
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
PUID=1000 \
PGID=1000 \
UMASK=022
# Copy necessary files
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
@@ -18,16 +18,10 @@ COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY pipeline /pipeline
COPY build/libs/*.jar app.jar
# Create user and group using Alpine's addgroup and adduser
#RUN addgroup -g $PGID stirlingpdfgroup && \
# adduser -u $PUID -G stirlingpdfgroup -s /bin/sh -D stirlingpdfuser && \
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
# Set up necessary directories and permissions
#RUN mkdir -p /scripts /configs /customFiles && \
# chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
RUN mkdir /configs /logs /customFiles && \
# Set font cache and permissions
#RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
chmod +x /scripts/*.sh && \
apk add --no-cache \
ca-certificates \
@@ -35,10 +29,16 @@ RUN mkdir /configs /logs /customFiles && \
tini \
bash \
curl \
su-exec \
shadow \
openjdk17-jre && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
# User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar
# Set environment variables
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI

View File

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

View File

@@ -1,13 +1,5 @@
## User Guide for Local Directory Scanning and File Processing
### Whilst Pipelines are in alpha...
You must enable this alpha functionality by setting
```yaml
system:
enableAlphaFunctionality: true
```
To true like in the above for your `/config/settings.yml` file, after restarting Stirling-PDF you should see both UI and folder scanning enabled.
### Setting Up Watched Folders:
- Create a folder where you want your files to be monitored. This is your 'watched folder'.
- The default directory for this is `./pipeline/watchedFolders/`

View File

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

View File

@@ -2,12 +2,12 @@
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
## My OCR used to work and now doesnt!
Please update your tesseract docker volume path version from 4.00 to 5
## My OCR used to work and now doesn't!
The paths have changed for the tessadata locations on new docker images, please use ``/usr/share/tessdata`` (Others should still work for backwards compatibility but might not)
## How does the OCR Work
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition.
All credit goes to them for this awesome work!
All credit goes to them for this awesome work!
## Language Packs
@@ -27,7 +27,7 @@ Depending on your requirements, you can choose the appropriate language pack for
#### Docker
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
#### Docker Compose
Modify your `docker-compose.yml` file to include the following volume configuration:

88
Jenkinsfile vendored
View File

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

View File

@@ -42,10 +42,10 @@ 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
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ openjdk-17-jdk python3 python3-pip
```
For Fedora-based systems use this command:
For Fedora-based systems use this command:
```bash
sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-17-openjdk python3 python3-pip
@@ -65,7 +65,7 @@ sudo make install
```
### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for patern recognition functionality.
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality.
Install the following software:
@@ -95,14 +95,14 @@ For Debian-based systems, you can use the following command:
```bash
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages
```
For Fedora:
```bash
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
```
### Step 4: Clone and Build Stirling-PDF
@@ -140,7 +140,7 @@ Manual:
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/tessdata`
3.
3.
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.
@@ -264,10 +264,10 @@ sudo systemctl restart stirlingpdf.service
Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme.
You can do this in the terminal by using the `export` command or -D arguements to java -jar command:
You can do this in the terminal by using the `export` command or -D argument to java -jar command:
```bash
export APP_HOME_NAME="Stirling PDF"
or
-DAPP_HOME_NAME="Stirling PDF"
-DAPP_HOME_NAME="Stirling PDF"
```

View File

@@ -1,13 +1,6 @@
# Pipeline Configuration and Usage Tutorial
## Whilst Pipelines are in alpha...
You must enable this alpha functionality by setting
```yaml
system:
enableAlphaFunctionality: true
```
To true like in the above for your `/config/settings.yml` file, after restarting Stirling-PDF you should see both UI and folder scanning enabled.
- Configure the pipeline config file and input files to run files against it
- For reuse, download the config file and re-upload it when needed, or place it in /pipeline/defaultWebUIConfigs/ to auto-load in the web UI for all users
## Steps to Configure and Use Your Pipeline
@@ -40,3 +33,12 @@ To true like in the above for your `/config/settings.yml` file, after restarting
10. **Note on Web UI Limitations**
- The current web UI version does not support operations that require multiple different types of inputs, such as adding a separate image to a PDF.
### Current Limitations
- Cannot have more than one of the same operation
- Cannot input additional files via UI
- All files and operations run in serial mode

105
README.md
View File

@@ -1,34 +1,35 @@
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
</p>
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ></p>
<h1 align="center">Stirling-PDF</h1>
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ)
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
[![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex)
[![Github Sponser](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle)
[![Github Sponsor](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle)
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
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.
This is a robust, locally hosted web-based PDF manipulation tool using Docker. It enables you to carry out various operations on PDF files, including splitting, merging, converting, reorganizing, adding images, rotating, compressing, and more. Originally developed entirely by ChatGPT, this locally hosted web application has evolved to encompass a comprehensive set of features, addressing all your PDF requirements.
Stirling PDF makes no outbound calls for any record keeping or tracking.
Stirling PDF does not initiate any outbound calls for record-keeping or tracking purposes.
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
![stirling-home](images/stirling-home.png)
![stirling-home](images/stirling-home.jpg)
## Features
- Dark mode support.
- Custom download options (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example)
- Parallel file processing and downloads
- API for integration with external scripts
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
## **PDF Features**
### **Page Operations**
- View and modify PDFs - View multi page PDFs with custom viewing sorting and searching. Plus on page edit features like annotate, draw and adding text and images. (Using PDF.js with Joxit and Liberation.Liberation fonts)
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
- Merge multiple PDFs together into a single resultant file.
@@ -45,6 +46,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Convert PDF to a single page.
### **Conversion Operations**
- Convert PDFs to and from images.
- Convert any common file to PDF (using LibreOffice).
- Convert PDF to Word/Powerpoint/Others (using LibreOffice).
@@ -53,6 +55,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Markdown to PDF.
### **Security & Permissions**
- Add and remove passwords.
- Change/set PDF Permissions.
- Add watermark(s).
@@ -61,6 +64,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Auto-redact text.
### **Other Operations**
- Add/Generate/Write signatures.
- Repair PDFs.
- Detect and remove blank pages.
@@ -77,11 +81,11 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Flatten PDFs.
- Get all information on a PDF to view or export as JSON.
For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
Demo of the app is available [here](https://stirlingpdf.io). username: demo, password: demo
## Technologies used
- Spring Boot + Thymeleaf
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
@@ -94,19 +98,21 @@ Demo of the app is available [here](https://stirlingpdf.io). username: demo, pas
## How to use
### Locally
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md
### Docker / Podman
https://hub.docker.com/r/frooodle/s-pdf
Stirling PDF has 3 different versions, a Full version, Lite, and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space.
Stirling PDF has 2 different versions, a Full version and ultra-Lite version. Depending on the types of features you use you may want a smaller image to save on space.
To see what the different versions offer please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md)
For people that don't mind about space optimization just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-lite?label=Stirling-PDF%20Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
Docker Run
```bash
docker run -d \
-p 8080:8080 \
@@ -114,6 +120,7 @@ docker run -d \
-v /location/of/extraConfigs:/configs \
-v /location/of/logs:/logs \
-e DOCKER_ENABLE_SECURITY=false \
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
--name stirling-pdf \
frooodle/s-pdf:latest
@@ -122,7 +129,9 @@ docker run -d \
-v /location/of/customFiles:/customFiles \
```
Docker Compose
```yaml
version: '3.3'
services:
@@ -137,59 +146,66 @@ services:
# - /location/of/logs:/logs/
environment:
- DOCKER_ENABLE_SECURITY=false
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
```
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
## Enable OCR/Compression feature
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md
## Supported Languages
Stirling PDF currently supports 26!
- English (English) (en_GB)
- English (US) (en_US)
- Arabic (العربية) (ar_AR)
- German (Deutsch) (de_DE)
- French (Français) (fr_FR)
- Spanish (Español) (es_ES)
- Simplified Chinese (简体中文) (zh_CN)
- Traditional Chinese (繁體中文) (zh_TW)
- Catalan (Català) (ca_CA)
- Italian (Italiano) (it_IT)
- Swedish (Svenska) (sv_SE)
- Polish (Polski) (pl_PL)
- Romanian (Română) (ro_RO)
- Korean (한국어) (ko_KR)
- Portuguese Brazilian (Português) (pt_BR)
- Russian (Русский) (ru_RU)
- Basque (Euskara) (eu_ES)
- Japanese (日本語) (ja_JP)
- Dutch (Nederlands) (nl_NL)
- Greek (el_GR)
- Turkish (Türkçe) (tr_TR)
- Indonesia (Bahasa Indonesia) (id_ID)
- Hindi (हिंदी) (hi_IN)
- Hungarian (Magyar) (hu_HU)
- Bulgarian (Български) (bg_BG)
- Sebian Latin alphabet (Srpski) (sr-Latn-RS)
| Language | Progress |
| ------------------------------------------- | -------------------------------------- |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| Arabic (العربية) (ar_AR) | ![58%](https://geps.dev/progress/58) |
| German (Deutsch) (de_DE) | ![95%](https://geps.dev/progress/95) |
| French (Français) (fr_FR) | ![99%](https://geps.dev/progress/99) |
| Spanish (Español) (es_ES) | ![95%](https://geps.dev/progress/95) |
| Simplified Chinese (简体中文) (zh_CN) | ![99%](https://geps.dev/progress/99) |
| Traditional Chinese (繁體中文) (zh_TW) | ![97%](https://geps.dev/progress/97) |
| Catalan (Català) (ca_CA) | ![65%](https://geps.dev/progress/65) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Swedish (Svenska) (sv_SE) | ![58%](https://geps.dev/progress/58) |
| Polish (Polski) (pl_PL) | ![60%](https://geps.dev/progress/60) |
| Romanian (Română) (ro_RO) | ![58%](https://geps.dev/progress/58) |
| Korean (한국어) (ko_KR) | ![92%](https://geps.dev/progress/92) |
| Portuguese Brazilian (Português) (pt_BR) | ![74%](https://geps.dev/progress/74) |
| Russian (Русский) (ru_RU) | ![92%](https://geps.dev/progress/92) |
| Basque (Euskara) (eu_ES) | ![76%](https://geps.dev/progress/76) |
| Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) |
| Dutch (Nederlands) (nl_NL) | ![92%](https://geps.dev/progress/92) |
| Greek (Ελληνικά) (el_GR) | ![77%](https://geps.dev/progress/77) |
| Turkish (Türkçe) (tr_TR) | ![99%](https://geps.dev/progress/99) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![87%](https://geps.dev/progress/87) |
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) |
| Hungarian (Magyar) (hu_HU) | ![87%](https://geps.dev/progress/87) |
| Bulgarian (Български) (bg_BG) | ![82%](https://geps.dev/progress/82) |
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![89%](https://geps.dev/progress/89) |
## Contributing (creating issues, translations, fixing bugs, etc.)
Please see our [Contributing Guide](CONTRIBUTING.md)!
## Customisation
Stirling PDF allows easy customization of the app.
Includes things like
- Custom application name
- Custom slogans, icons, images, and even custom HTML (via file overrides)
There are two options for this, either using the generated settings file ``settings.yml``
This file is located in the ``/configs`` directory and follows standard YAML formatting
Environment variables are also supported and would override the settings file
For example in the settings.yml you have
```yaml
system:
defaultLocale: 'en-US'
@@ -198,6 +214,7 @@ system:
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
The Current list of settings is
```yaml
security:
enableLogin: false # set to 'true' to enable login
@@ -220,23 +237,31 @@ endpoints:
metrics:
enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable
```
### Extra notes
- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
- customStaticFilePath. Customise static files such as the app logo by placing files in the /customFiles/static/ directory. An example of customising app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
### Environment only parameters
- ``SYSTEM_ROOTURIPATH`` ie set to ``/pdf-app`` to Set the application's root URI to ``localhost:8080/pdf-app``
- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values
- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login)
- ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS`` to download calibre onto stirling-pdf enabling pdf to/from book and advanced html conversion
## API
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/Stirling-Tools/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
## Login authentication
![stirling-login](images/login-light.png)
### Prerequisites:
- User must have the folder ./configs volumed within docker so that it is retained during updates.
- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
@@ -252,20 +277,22 @@ To add new users go to the bottom of Account settings and hit 'Admin Settings',
For API usage you must provide a header with 'X-API-Key' and the associated API key for that user.
## FAQ
### Q1: What are your planned features?
- Progress bar/Tracking
- Full custom logic pipelines to combine multiple operations together.
- Folder support with auto scanning to perform operations on
- Redact text (Via UI not just automated way)
- Add Forms
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
- Fill forms mannual and automatic
- Fill forms manually or automatically
### Q2: Why is my application downloading .htm files?
This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files.
### Q3: Why is my download timing out
NGINX has timeout values by default so if you are running Stirling-PDF behind NGINX you may need to set a timeout value such as adding the config ``proxy_read_timeout 3600;``

View File

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

View File

@@ -1,18 +1,18 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.3'
id 'org.springdoc.openapi-gradle-plugin' version '1.8.0'
id "io.swagger.swaggerhub" version "1.3.2"
id 'edu.sc.seis.launch4j' version '3.0.5'
id 'com.diffplug.spotless' version '6.25.0'
id 'com.github.jk1.dependency-license-report' version '2.5'
id 'com.github.jk1.dependency-license-report' version '2.6'
}
import com.github.jk1.license.render.*
group = 'stirling.software'
version = '0.21.0'
version = '0.22.8'
sourceCompatibility = '17'
repositories {
@@ -20,7 +20,6 @@ repositories {
}
licenseReport {
renderers = [new JsonReportRenderer()]
}
@@ -48,7 +47,6 @@ openApi {
outputFileName = "SwaggerDoc.json"
}
launch4j {
icon = "${projectDir}/src/main/resources/static/favicon.ico"
@@ -87,26 +85,26 @@ spotless {
dependencies {
//security updates
implementation 'ch.qos.logback:logback-classic:1.4.14'
implementation 'ch.qos.logback:logback-core:1.4.14'
implementation 'org.springframework:spring-webmvc:6.1.2'
implementation 'ch.qos.logback:logback-classic:1.5.3'
implementation 'ch.qos.logback:logback-core:1.5.3'
implementation 'org.springframework:spring-webmvc:6.1.5'
implementation("io.github.pixee:java-security-toolkit:1.1.3")
implementation("io.github.pixee:java-security-toolkit:1.1.2")
implementation 'org.yaml:snakeyaml:2.2'
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.2'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.2'
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.4'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4'
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.2'
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.2"
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.4"
//2.2.x requires rebuild of DB file.. need migration path
implementation "com.h2database:h2:2.1.214"
}
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.4'
// Batik
implementation 'org.apache.xmlgraphics:batik-all:1.17'
@@ -138,29 +136,31 @@ dependencies {
implementation ('com.opencsv:opencsv:5.9') {
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation ('org.apache.pdfbox:pdfbox:3.0.1'){
implementation ('org.apache.pdfbox:pdfbox:3.0.2'){
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation ('org.apache.pdfbox:xmpbox:3.0.1'){
implementation ('org.apache.pdfbox:xmpbox:3.0.2'){
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77'
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.2'
implementation 'io.micrometer:micrometer-core:1.12.2'
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.4'
implementation 'io.micrometer:micrometer-core:1.12.4'
implementation group: 'com.google.zxing', name: 'core', version: '3.5.3'
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation 'org.commonmark:commonmark:0.21.0'
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.21.0'
implementation 'org.commonmark:commonmark:0.22.0'
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.22.0'
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
developmentOnly("org.springframework.boot:spring-boot-devtools:3.2.2")
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.28'
implementation 'com.fathzer:javaluator:3.0.3'
developmentOnly("org.springframework.boot:spring-boot-devtools:3.2.4")
compileOnly 'org.projectlombok:lombok:1.18.32'
annotationProcessor 'org.projectlombok:lombok:1.18.32'
}
tasks.withType(JavaCompile) {

View File

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

View File

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

View File

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

View File

@@ -19,8 +19,8 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs173">
<linearGradient
id="XMLID_5_"
gradientUnits="userSpaceOnUse"
@@ -37,7 +37,7 @@
style="stop-color:#C2C2C9"
id="stop158" />
</linearGradient>
</defs><sodipodi:namedview
id="namedview171"
pagecolor="#ffffff"

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,31 +0,0 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Lite-Security
image: frooodle/s-pdf:latest-lite
deploy:
resources:
limits:
memory: 2G
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
interval: 5s
timeout: 10s
retries: 16
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "true"
restart: on-failure:5

View File

@@ -1,30 +0,0 @@
version: '3.3'
services:
stirling-pdf:
container_name: Stirling-PDF-Lite
image: frooodle/s-pdf:latest-lite
deploy:
resources:
limits:
memory: 2G
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
interval: 5s
timeout: 10s
retries: 16
ports:
- 8080:8080
volumes:
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF-Lite
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "true"
restart: on-failure:5

View File

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

View File

@@ -15,7 +15,7 @@ services:
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:

View File

@@ -15,7 +15,7 @@ services:
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:

182
gradlew.bat vendored
View File

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

BIN
images/stirling-home.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

View File

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

View File

@@ -0,0 +1,122 @@
"""A script to update language progress status in README.md based on
properties file comparison.
This script compares default properties file with others in a directory to
determine language progress.
It then updates README.md based on provided progress list.
Author: Ludy87
Example:
To use this script, simply run it from command line:
$ python counter_translation.py
"""
import os
import glob
import re
from typing import List, Tuple
def write_readme(progress_list: List[Tuple[str, int]]) -> None:
"""
Updates the progress status in the README.md file based
on the provided progress list.
Parameters:
progress_list (List[Tuple[str, int]]): A list of tuples containing
language and progress percentage.
Returns:
None
"""
with open("README.md", "r", encoding="utf-8") as file:
content = file.read()
lines = content.split("\n")
for i, line in enumerate(lines[2:], start=2):
for progress in progress_list:
language, value = progress
if language in line:
match = re.search(r"\!\[(\d+(\.\d+)?)%\]\(.*\)", line)
if match:
lines[i] = line.replace(
match.group(0),
f"![{value}%](https://geps.dev/progress/{value})",
)
new_content = "\n".join(lines)
with open("README.md", "w", encoding="utf-8") as file:
file.write(new_content)
def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
"""
Compares the default properties file with other
properties files in the directory.
Parameters:
default_file_path (str): The path to the default properties file.
files_directory (str): The directory containing other properties files.
Returns:
List[Tuple[str, int]]: A list of tuples containing
language and progress percentage.
"""
file_paths = glob.glob(os.path.join(files_directory, "messages_*.properties"))
num_lines = sum(1 for _ in open(default_file_path, encoding="utf-8"))
result_list = []
for file_path in file_paths:
language = (
os.path.basename(file_path)
.split("messages_", 1)[1]
.split(".properties", 1)[0]
)
fails = 0
if "en_GB" in language or "en_US" in language:
result_list.append(("en_GB", 100))
result_list.append(("en_US", 100))
continue
with open(default_file_path, "r", encoding="utf-8") as default_file, open(
file_path, "r", encoding="utf-8"
) as file:
for _ in range(5):
next(default_file)
try:
next(file)
except StopIteration:
fails = num_lines
for _, (line_default, line_file) in enumerate(
zip(default_file, file), start=6
):
try:
if (
line_default.split("=", 1)[1].strip()
== line_file.split("=", 1)[1].strip()
):
fails += 1
except IndexError:
pass
result_list.append(
(
language,
int((num_lines - fails) * 100 / num_lines),
)
)
unique_data = list(set(result_list))
unique_data.sort(key=lambda x: x[1], reverse=True)
return unique_data
if __name__ == "__main__":
directory = os.path.join(os.getcwd(), "src", "main", "resources")
reference_file = os.path.join(directory, "messages_en_GB.properties")
write_readme(compare_files(reference_file, directory))

View File

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

View File

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

View File

@@ -1,6 +1,29 @@
#!/bin/sh
# Update the user and group IDs as per environment variables
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
usermod -o -u "$PUID" stirlingpdfuser || true
fi
if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then
groupmod -o -g "$PGID" stirlingpdfgroup || true
fi
umask "$UMASK" || true
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
apk add --no-cache calibre@testing
fi
/scripts/download-security-jar.sh
# Run the main command
exec "$@"
echo "Setting permissions and ownership for necessary directories..."
if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar; then
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar || true
# If chown succeeds, execute the command as stirlingpdfuser
exec su-exec stirlingpdfuser "$@"
else
# If chown fails, execute the command without changing the user context
echo "[WARN] Chown failed, running as host user"
exec "$@"
fi

View File

@@ -13,18 +13,47 @@ if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then
cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true;
fi
# Update the user and group IDs as per environment variables
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
usermod -o -u "$PUID" stirlingpdfuser || true
fi
if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then
groupmod -o -g "$PGID" stirlingpdfgroup || true
fi
umask "$UMASK" || true
# Check if TESSERACT_LANGS environment variable is set and is not empty
if [[ -n "$TESSERACT_LANGS" ]]; then
# Convert comma-separated values to a space-separated list
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
# Install each language pack
for LANG in $LANGS; do
apt-get install -y "tesseract-ocr-$LANG"
if [[ $LANG =~ $pattern ]]; then
apk add --no-cache "tesseract-ocr-data-$LANG"
else
echo "Skipping invalid language code"
fi
done
fi
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
apk add --no-cache calibre@testing
fi
/scripts/download-security-jar.sh
# Run the main command
exec "$@"
echo "Setting permissions and ownership for necessary directories..."
# Attempt to change ownership of directories and files
if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar; then
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar || true
# If chown succeeds, execute the command as stirlingpdfuser
exec su-exec stirlingpdfuser "$@"
else
# If chown fails, execute the command without changing the user context
echo "[WARN] Chown failed, running as host user"
exec "$@"
fi

View File

@@ -2,7 +2,7 @@ import argparse
import sys
import cv2
import numpy as np
import os
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)
@@ -49,9 +49,9 @@ def auto_rotate(image, angle_threshold=1):
angles = []
for rho, theta in lines[:, 0]:
angles.append((theta * 180) / np.pi - 90)
angle = np.median(angles)
if abs(angle) < angle_threshold:
return image
@@ -65,16 +65,16 @@ def auto_rotate(image, angle_threshold=1):
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)
@@ -110,7 +110,7 @@ if __name__ == "__main__":
parser.add_argument("--min_contour_area", type=int, default=500, help="Sets the minimum contour area threshold for a photo (default: 500).")
parser.add_argument("--angle_threshold", type=int, default=10, help="Sets the minimum absolute angle required for the image to be rotated (default: 10).")
parser.add_argument("--border_size", type=int, default=0, help="Sets the size of the border added and removed to prevent white borders in the output (default: 0).")
args = parser.parse_args()
split_photos(args.input_file, args.output_directory, tolerance=args.tolerance, min_area=args.min_area, min_contour_area=args.min_contour_area, angle_threshold=args.angle_threshold, border_size=args.border_size)

View File

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

View File

@@ -79,9 +79,10 @@ public class AppConfig {
@Bean(name = "bookAndHtmlFormatsInstalled")
public boolean bookAndHtmlFormatsInstalled() {
return applicationProperties
.getSystem()
.getCustomApplications()
.isInstallBookAndHtmlFormats();
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
if (installOps == null) {
installOps = System.getenv("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
}
return "true".equalsIgnoreCase(installOps);
}
}

View File

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

View File

@@ -129,7 +129,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Other", "sign");
addEndpointToGroup("Other", "flatten");
addEndpointToGroup("Other", "repair");
addEndpointToGroup("Other", "remove-blanks");
addEndpointToGroup("Other", REMOVE_BLANKS);
addEndpointToGroup("Other", "remove-annotations");
addEndpointToGroup("Other", "compare");
addEndpointToGroup("Other", "add-page-numbers");
@@ -140,7 +140,6 @@ public class EndpointConfiguration {
// CLI
addEndpointToGroup("CLI", "compress-pdf");
addEndpointToGroup("CLI", "extract-image-scans");
addEndpointToGroup("CLI", "remove-blanks");
addEndpointToGroup("CLI", "repair");
addEndpointToGroup("CLI", "pdf-to-pdfa");
addEndpointToGroup("CLI", "file-to-pdf");
@@ -162,13 +161,13 @@ public class EndpointConfiguration {
// python
addEndpointToGroup("Python", "extract-image-scans");
addEndpointToGroup("Python", "remove-blanks");
addEndpointToGroup("Python", REMOVE_BLANKS);
addEndpointToGroup("Python", "html-to-pdf");
addEndpointToGroup("Python", "url-to-pdf");
// openCV
addEndpointToGroup("OpenCV", "extract-image-scans");
addEndpointToGroup("OpenCV", "remove-blanks");
addEndpointToGroup("OpenCV", REMOVE_BLANKS);
// LibreOffice
addEndpointToGroup("LibreOffice", "repair");
@@ -218,6 +217,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections");
addEndpointToGroup("Java", REMOVE_BLANKS);
// Javascript
addEndpointToGroup("Javascript", "pdf-organizer");
@@ -244,4 +244,6 @@ public class EndpointConfiguration {
}
}
}
private static final String REMOVE_BLANKS = "remove-blanks";
}

View File

@@ -1,69 +0,0 @@
package stirling.software.SPDF.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@Component
public class PostStartupProcesses {
@Autowired ApplicationProperties applicationProperties;
@Autowired
@Qualifier("RunningInDocker")
private boolean runningInDocker;
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
private static final Logger logger = LoggerFactory.getLogger(PostStartupProcesses.class);
@PostConstruct
public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException {
List<List<String>> commands = new ArrayList<>();
// Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable
if (bookAndHtmlFormatsInstalled) {
List<String> tmpList = new ArrayList<>();
tmpList = new ArrayList<>();
tmpList.addAll(Arrays.asList("apk add --no-cache calibre"));
commands.add(tmpList);
}
if (!commands.isEmpty()) {
// Run the command
if (runningInDocker) {
List<String> tmpList = new ArrayList<>();
for (List<String> list : commands) {
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.INSTALL_APP, true)
.runCommandWithOutputHandling(list);
logger.info("RC for app installs {}", returnCode.getRc());
}
} else {
logger.info(
"Not running inside Docker so skipping automated install process with command.");
}
} else {
if (runningInDocker) {
logger.info("No custom apps to install.");
}
}
}
}

View File

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

View File

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

View File

@@ -82,7 +82,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter()
.write(
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected");
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternatively you can disable authentication if this is unexpected");
return;
}
}

View File

@@ -176,6 +176,10 @@ public class UserService implements UserServiceInterface {
return userRepository.findByUsername(username);
}
public Optional<User> findByUsernameIgnoreCase(String username) {
return userRepository.findByUsernameIgnoreCase(username);
}
public void changeUsername(User user, String newUsername) {
user.setUsername(newUsername);
userRepository.save(user);
@@ -194,4 +198,8 @@ public class UserService implements UserServiceInterface {
public boolean isPasswordCorrect(User user, String currentPassword) {
return passwordEncoder.matches(currentPassword, user.getPassword());
}
public boolean isUsernameValid(String username) {
return username.matches("[a-zA-Z0-9]+");
}
}

View File

@@ -38,7 +38,7 @@ public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
PDDocument mergedDoc = new PDDocument();
for (PDDocument doc : documents) {
for (PDPage page : doc.getPages()) {

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.pdfbox.Loader;
@@ -50,7 +51,9 @@ public class RearrangePagesPDFController {
String[] pageOrderArr = pagesToDelete.split(",");
List<Integer> pagesToRemove =
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages(), false);
Collections.sort(pagesToRemove);
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i);
@@ -192,7 +195,7 @@ public class RearrangePagesPDFController {
if (sortType != null && sortType.length() > 0) {
newPageOrder = processSortTypes(sortType, totalPages);
} else {
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
}
logger.info("newPageOrder = " + newPageOrder);
logger.info("totalPages = " + totalPages);

View File

@@ -49,10 +49,16 @@ public class SplitPDFController {
// open the pdf document
PDDocument document = Loader.loadPDF(file.getBytes());
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
System.out.println(
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
if (!pageNumbers.contains(document.getNumberOfPages() - 1))
pageNumbers.add(document.getNumberOfPages() - 1);
logger.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
@@ -65,7 +71,7 @@ public class SplitPDFController {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.debug("Adding page {} to split document", i);
logger.info("Adding page {} to split document", i);
}
previousPageNumber = splitPoint + 1;

View File

@@ -53,8 +53,21 @@ public class SplitPdfBySectionsController {
// Process the PDF based on split parameters
int horiz = request.getHorizontalDivisions() + 1;
int verti = request.getVerticalDivisions() + 1;
boolean merge = request.isMerge();
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
if (merge) {
MergeController mergeController = new MergeController();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mergeController.mergeDocuments(splitDocuments).save(baos);
return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(),
filename + "_split.pdf",
MediaType.APPLICATION_OCTET_STREAM);
}
for (PDDocument doc : splitDocuments) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
doc.save(baos);
@@ -65,9 +78,6 @@ public class SplitPdfBySectionsController {
sourceDocument.close();
Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {

View File

@@ -10,6 +10,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -28,7 +31,6 @@ import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.api.user.UpdateUserDetails;
import stirling.software.SPDF.model.api.user.UsernameAndPass;
@Controller
@@ -50,67 +52,25 @@ public class UserController {
return "redirect:/login?registered=true";
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-username-and-password")
public RedirectView changeUsernameAndPassword(
Principal principal,
@ModelAttribute UpdateUserDetails requestModel,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
String currentPassword = requestModel.getPassword();
String newPassword = requestModel.getNewPassword();
String newUsername = requestModel.getNewUsername();
if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated");
}
Optional<User> userOpt = userService.findByUsername(principal.getName());
if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/change-creds?messageType=userNotFound");
}
User user = userOpt.get();
if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/change-creds?messageType=incorrectPassword");
}
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
return new RedirectView("/change-creds?messageType=usernameExists");
}
userService.changePassword(user, newPassword);
if (newUsername != null
&& newUsername.length() > 0
&& !user.getUsername().equals(newUsername)) {
userService.changeUsername(user, newUsername);
}
userService.changeFirstUse(user, false);
// Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView("/login?messageType=credsUpdated");
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-username")
public RedirectView changeUsername(
Principal principal,
@RequestParam String currentPassword,
@RequestParam String newUsername,
@RequestParam(name = "currentPassword") String currentPassword,
@RequestParam(name = "newUsername") String newUsername,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
if (!userService.isUsernameValid(newUsername)) {
return new RedirectView("/account?messageType=invalidUsername");
}
if (principal == null) {
return new RedirectView("/account?messageType=notAuthenticated");
}
Optional<User> userOpt = userService.findByUsername(principal.getName());
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/account?messageType=userNotFound");
@@ -118,6 +78,10 @@ public class UserController {
User user = userOpt.get();
if (user.getUsername().equals(newUsername)) {
return new RedirectView("/account?messageType=usernameExists");
}
if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/account?messageType=incorrectPassword");
}
@@ -133,15 +97,48 @@ public class UserController {
// Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView("/login?messageType=credsUpdated");
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED);
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-password-on-login")
public RedirectView changePasswordOnLogin(
Principal principal,
@RequestParam(name = "currentPassword") String currentPassword,
@RequestParam(name = "newPassword") String newPassword,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated");
}
Optional<User> userOpt = userService.findByUsername(principal.getName());
if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/change-creds?messageType=userNotFound");
}
User user = userOpt.get();
if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/change-creds?messageType=incorrectPassword");
}
userService.changePassword(user, newPassword);
userService.changeFirstUse(user, false);
// Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED);
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-password")
public RedirectView changePassword(
Principal principal,
@RequestParam String currentPassword,
@RequestParam String newPassword,
@RequestParam(name = "currentPassword") String currentPassword,
@RequestParam(name = "newPassword") String newPassword,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
@@ -166,7 +163,7 @@ public class UserController {
// Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView("/login?messageType=credsUpdated");
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED);
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@@ -192,12 +189,24 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/saveUser")
public RedirectView saveUser(
@RequestParam String username,
@RequestParam String password,
@RequestParam String role,
@RequestParam(name = "username") String username,
@RequestParam(name = "password") String password,
@RequestParam(name = "role") String role,
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
boolean forceChange) {
if (!userService.isUsernameValid(username)) {
return new RedirectView("/addUsers?messageType=invalidUsername");
}
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
if (userOpt.isPresent()) {
User user = userOpt.get();
if (user != null && user.getUsername().equalsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=usernameExists");
}
}
if (userService.usernameExists(username)) {
return new RedirectView("/addUsers?messageType=usernameExists");
}
@@ -219,18 +228,39 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/deleteUser/{username}")
public String deleteUser(@PathVariable String username, Authentication authentication) {
public RedirectView deleteUser(
@PathVariable(name = "username") String username, Authentication authentication) {
if (!userService.usernameExists(username)) {
return new RedirectView("/addUsers?messageType=deleteUsernameExists");
}
// Get the currently authenticated username
String currentUsername = authentication.getName();
// Check if the provided username matches the current session's username
if (currentUsername.equals(username)) {
throw new IllegalArgumentException("Cannot delete currently logined in user.");
return new RedirectView("/addUsers?messageType=deleteCurrentUser");
}
invalidateUserSessions(username);
userService.deleteUser(username);
return "redirect:/addUsers";
return new RedirectView("/addUsers");
}
@Autowired private SessionRegistry sessionRegistry;
private void invalidateUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation session :
sessionRegistry.getAllSessions(principal, false)) {
session.expireNow();
}
}
}
}
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@@ -261,4 +291,6 @@ public class UserController {
}
return ResponseEntity.ok(apiKey);
}
private static final String LOGIN_MESSAGETYPE_CREDSUPDATED = "/login?messageType=credsUpdated";
}

View File

@@ -37,7 +37,7 @@ public class ConvertBookToPDFController {
if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
"bookAndHtmlFormatsInstalled flag is False, this functionality is not available");
}
if (fileInput == null) {

View File

@@ -45,7 +45,7 @@ public class ConvertPDFToBookController {
if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
"bookAndHtmlFormatsInstalled flag is False, this functionality is not available");
}
if (fileInput == null) {

View File

@@ -29,18 +29,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertPDFToOffice {
@PostMapping(consumes = "multipart/form-data", value = "/pdf/html")
@Operation(
summary = "Convert PDF to HTML",
description =
"This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile request)
throws Exception {
MultipartFile inputFile = request.getFileInput();
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
@Operation(
summary = "Convert PDF to Presentation format",

View File

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

View File

@@ -130,7 +130,7 @@ public class AutoRenameController {
// Sanitize the header string by removing characters not allowed in a filename.
if (header != null && header.length() < 255) {
header = header.replaceAll("[/\\\\?%*:|\"<>]", "");
header = header.replaceAll("[/\\\\?%*:|\"<>]", "").trim();
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
} else {
logger.info("File has no good title to be found");

View File

@@ -58,7 +58,7 @@ public class AutoSplitPdfController {
PDDocument document = Loader.loadPDF(file.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
List<PDDocument> splitDocuments = new ArrayList<>();
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();

View File

@@ -59,7 +59,7 @@ public class BlankPageController {
List<Integer> pagesToKeepIndex = new ArrayList<>();
int pageIndex = 0;
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
for (PDPage page : pages) {
logger.info("checking page " + pageIndex);
textStripper.setStartPage(pageIndex + 1);

View File

@@ -2,9 +2,7 @@ package stirling.software.SPDF.controller.api.misc;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -75,194 +73,208 @@ public class CompressController {
long inputFileSize = Files.size(tempInputFile);
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Determine initial optimization level based on expected size reduction, only if in
// autoMode
if (autoMode) {
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
if (sizeReductionRatio > 0.7) {
optimizeLevel = 1;
} else if (sizeReductionRatio > 0.5) {
optimizeLevel = 2;
} else if (sizeReductionRatio > 0.35) {
optimizeLevel = 3;
} else {
optimizeLevel = 3;
}
}
boolean sizeMet = false;
while (!sizeMet && optimizeLevel <= 4) {
// Prepare the Ghostscript command
List<String> command = new ArrayList<>();
command.add("gs");
command.add("-sDEVICE=pdfwrite");
command.add("-dCompatibilityLevel=1.4");
switch (optimizeLevel) {
case 1:
command.add("-dPDFSETTINGS=/prepress");
break;
case 2:
command.add("-dPDFSETTINGS=/printer");
break;
case 3:
command.add("-dPDFSETTINGS=/ebook");
break;
case 4:
command.add("-dPDFSETTINGS=/screen");
break;
default:
command.add("-dPDFSETTINGS=/default");
}
command.add("-dNOPAUSE");
command.add("-dQUIET");
command.add("-dBATCH");
command.add("-sOutputFile=" + tempOutputFile.toString());
command.add(tempInputFile.toString());
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
.runCommandWithOutputHandling(command);
// Check if file size is within expected size or not auto mode so instantly finish
long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize <= expectedOutputSize || !autoMode) {
sizeMet = true;
} else {
// Increase optimization level for next iteration
optimizeLevel++;
if (autoMode && optimizeLevel > 3) {
System.out.println("Skipping level 4 due to bad results in auto mode");
sizeMet = true;
} else if (optimizeLevel == 5) {
Path tempOutputFile = null;
byte[] pdfBytes;
try {
tempOutputFile = Files.createTempFile("output_", ".pdf");
// Determine initial optimization level based on expected size reduction, only if in
// autoMode
if (autoMode) {
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
if (sizeReductionRatio > 0.7) {
optimizeLevel = 1;
} else if (sizeReductionRatio > 0.5) {
optimizeLevel = 2;
} else if (sizeReductionRatio > 0.35) {
optimizeLevel = 3;
} else {
System.out.println(
"Increasing ghostscript optimisation level to " + optimizeLevel);
optimizeLevel = 3;
}
}
}
if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize > expectedOutputSize) {
try (PDDocument doc = Loader.loadPDF(new File(tempOutputFile.toString()))) {
long previousFileSize = 0;
double scaleFactor = 1.0;
while (true) {
for (PDPage page : doc.getPages()) {
PDResources res = page.getResources();
boolean sizeMet = false;
while (!sizeMet && optimizeLevel <= 4) {
// Prepare the Ghostscript command
List<String> command = new ArrayList<>();
command.add("gs");
command.add("-sDEVICE=pdfwrite");
command.add("-dCompatibilityLevel=1.4");
for (COSName name : res.getXObjectNames()) {
PDXObject xobj = res.getXObject(name);
if (xobj instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) xobj;
switch (optimizeLevel) {
case 1:
command.add("-dPDFSETTINGS=/prepress");
break;
case 2:
command.add("-dPDFSETTINGS=/printer");
break;
case 3:
command.add("-dPDFSETTINGS=/ebook");
break;
case 4:
command.add("-dPDFSETTINGS=/screen");
break;
default:
command.add("-dPDFSETTINGS=/default");
}
// Get the image in BufferedImage format
BufferedImage bufferedImage = image.getImage();
command.add("-dNOPAUSE");
command.add("-dQUIET");
command.add("-dBATCH");
command.add("-sOutputFile=" + tempOutputFile.toString());
command.add(tempInputFile.toString());
// Calculate the new dimensions
int newWidth = (int) (bufferedImage.getWidth() * scaleFactor);
int newHeight = (int) (bufferedImage.getHeight() * scaleFactor);
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
.runCommandWithOutputHandling(command);
// If the new dimensions are zero, skip this iteration
if (newWidth == 0 || newHeight == 0) {
continue;
// Check if file size is within expected size or not auto mode so instantly finish
long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize <= expectedOutputSize || !autoMode) {
sizeMet = true;
} else {
// Increase optimization level for next iteration
optimizeLevel++;
if (autoMode && optimizeLevel > 4) {
System.out.println("Skipping level 5 due to bad results in auto mode");
sizeMet = true;
} else {
System.out.println(
"Increasing ghostscript optimisation level to " + optimizeLevel);
}
}
}
if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile);
byte[] fileBytes = Files.readAllBytes(tempOutputFile);
if (outputFileSize > expectedOutputSize) {
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
long previousFileSize = 0;
double scaleFactorConst = 0.9f;
double scaleFactor = 0.9f;
while (true) {
for (PDPage page : doc.getPages()) {
PDResources res = page.getResources();
if (res != null && res.getXObjectNames() != null) {
for (COSName name : res.getXObjectNames()) {
PDXObject xobj = res.getXObject(name);
if (xobj != null && xobj instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject) xobj;
// Get the image in BufferedImage format
BufferedImage bufferedImage = image.getImage();
// Calculate the new dimensions
int newWidth =
(int)
(bufferedImage.getWidth()
* scaleFactorConst);
int newHeight =
(int)
(bufferedImage.getHeight()
* scaleFactorConst);
// If the new dimensions are zero, skip this iteration
if (newWidth == 0 || newHeight == 0) {
continue;
}
// Otherwise, proceed with the scaling
Image scaledImage =
bufferedImage.getScaledInstance(
newWidth,
newHeight,
Image.SCALE_SMOOTH);
// Convert the scaled image back to a BufferedImage
BufferedImage scaledBufferedImage =
new BufferedImage(
newWidth,
newHeight,
BufferedImage.TYPE_INT_RGB);
scaledBufferedImage
.getGraphics()
.drawImage(scaledImage, 0, 0, null);
// Compress the scaled image
ByteArrayOutputStream compressedImageStream =
new ByteArrayOutputStream();
ImageIO.write(
scaledBufferedImage,
"jpeg",
compressedImageStream);
byte[] imageBytes = compressedImageStream.toByteArray();
compressedImageStream.close();
PDImageXObject compressedImage =
PDImageXObject.createFromByteArray(
doc,
imageBytes,
image.getCOSObject().toString());
// Replace the image in the resources with the
// compressed
// version
res.put(name, compressedImage);
}
}
// Otherwise, proceed with the scaling
Image scaledImage =
bufferedImage.getScaledInstance(
newWidth, newHeight, Image.SCALE_SMOOTH);
// Convert the scaled image back to a BufferedImage
BufferedImage scaledBufferedImage =
new BufferedImage(
newWidth,
newHeight,
BufferedImage.TYPE_INT_RGB);
scaledBufferedImage
.getGraphics()
.drawImage(scaledImage, 0, 0, null);
// Compress the scaled image
ByteArrayOutputStream compressedImageStream =
new ByteArrayOutputStream();
ImageIO.write(
scaledBufferedImage, "jpeg", compressedImageStream);
byte[] imageBytes = compressedImageStream.toByteArray();
compressedImageStream.close();
// Convert compressed image back to PDImageXObject
ByteArrayInputStream bais =
new ByteArrayInputStream(imageBytes);
PDImageXObject compressedImage =
PDImageXObject.createFromByteArray(
doc,
imageBytes,
image.getCOSObject().toString());
// Replace the image in the resources with the compressed
// version
res.put(name, compressedImage);
}
}
}
// save the document to tempOutputFile again
doc.save(tempOutputFile.toString());
// save the document to tempOutputFile again
doc.save(tempOutputFile.toString());
long currentSize = Files.size(tempOutputFile);
// Check if the overall PDF size is still larger than expectedOutputSize
if (currentSize > expectedOutputSize) {
// Log the current file size and scaleFactor
long currentSize = Files.size(tempOutputFile);
// Check if the overall PDF size is still larger than expectedOutputSize
if (currentSize > expectedOutputSize) {
// Log the current file size and scaleFactor
System.out.println(
"Current file size: "
+ FileUtils.byteCountToDisplaySize(currentSize));
System.out.println("Current scale factor: " + scaleFactor);
System.out.println(
"Current file size: "
+ FileUtils.byteCountToDisplaySize(currentSize));
System.out.println("Current scale factor: " + scaleFactor);
// The file is still too large, reduce scaleFactor and try again
scaleFactor *= 0.9; // reduce scaleFactor by 10%
// Avoid scaleFactor being too small, causing the image to shrink to 0
if (scaleFactor < 0.2 || previousFileSize == currentSize) {
throw new RuntimeException(
"Could not reach the desired size without excessively degrading image quality, lowest size recommended is "
+ FileUtils.byteCountToDisplaySize(currentSize)
+ ", "
+ currentSize
+ " bytes");
// The file is still too large, reduce scaleFactor and try again
scaleFactor *= 0.9f; // reduce scaleFactor by 10%
// Avoid scaleFactor being too small, causing the image to shrink to
// 0
if (scaleFactor < 0.2f || previousFileSize == currentSize) {
throw new RuntimeException(
"Could not reach the desired size without excessively degrading image quality, lowest size recommended is "
+ FileUtils.byteCountToDisplaySize(currentSize)
+ ", "
+ currentSize
+ " bytes");
}
previousFileSize = currentSize;
} else {
// The file is small enough, break the loop
break;
}
previousFileSize = currentSize;
} else {
// The file is small enough, break the loop
break;
}
}
}
}
// Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile);
// Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) {
// Log the occurrence
logger.warn(
"Optimized file is larger than the original. Returning the original file instead.");
// Read the original file again
pdfBytes = Files.readAllBytes(tempInputFile);
}
} finally {
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
}
// Read the optimized PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) {
// Log the occurrence
logger.warn(
"Optimized file is larger than the original. Returning the original file instead.");
// Read the original file again
pdfBytes = Files.readAllBytes(tempInputFile);
}
// Clean up the temporary files
Files.delete(tempInputFile);
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())

View File

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

View File

@@ -60,6 +60,7 @@ public class FakeScanControllerWIP {
PDDocument document = Loader.loadPDF(inputFile.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
ImageIO.write(image, "png", new File("scanned-" + (page + 1) + ".png"));

View File

@@ -41,7 +41,7 @@ public class OCRController {
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata";
String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();

View File

@@ -88,7 +88,7 @@ public class StampController {
// Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
for (int pageIndex : pageNumbers) {
int zeroBasedIndex = pageIndex - 1;

View File

@@ -36,7 +36,7 @@ public class ApiDocService {
private String getApiDocsUrl() {
String contextPath = servletContext.getContextPath();
String port = SPdfApplication.getPort();
String port = SPdfApplication.getStaticPort();
return "http://localhost:" + port + contextPath + "/v1/api-docs";
}

View File

@@ -12,7 +12,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -50,9 +49,6 @@ public class PipelineController {
@PostMapping("/handleData")
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
throws JsonMappingException, JsonProcessingException {
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
MultipartFile[] files = request.getFileInput();
String jsonString = request.getJson();

View File

@@ -26,7 +26,6 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;
@@ -36,7 +35,6 @@ public class PipelineDirectoryProcessor {
private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class);
@Autowired private ObjectMapper objectMapper;
@Autowired private ApiDocService apiDocService;
@Autowired private ApplicationProperties applicationProperties;
final String watchedFoldersDir = "./pipeline/watchedFolders/";
final String finishedFoldersDir = "./pipeline/finishedFolders/";
@@ -45,9 +43,6 @@ public class PipelineDirectoryProcessor {
@Scheduled(fixedRate = 60000)
public void scanFolders() {
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
return;
}
Path watchedFolderPath = Paths.get(watchedFoldersDir);
if (!Files.exists(watchedFolderPath)) {
try {

View File

@@ -64,7 +64,7 @@ public class PipelineProcessor {
private String getBaseUrl() {
String contextPath = servletContext.getContextPath();
String port = SPdfApplication.getPort();
String port = SPdfApplication.getStaticPort();
return "http://localhost:" + port + contextPath + "/";
}

View File

@@ -83,6 +83,7 @@ public class RedactController {
if (convertPDFToImage) {
PDDocument imageDocument = new PDDocument();
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));

View File

@@ -54,33 +54,32 @@ public class SanitizeController {
boolean removeLinks = request.isRemoveLinks();
boolean removeFonts = request.isRemoveFonts();
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
if (removeJavaScript) {
sanitizeJavaScript(document);
}
if (removeEmbeddedFiles) {
sanitizeEmbeddedFiles(document);
}
if (removeMetadata) {
sanitizeMetadata(document);
}
if (removeLinks) {
sanitizeLinks(document);
}
if (removeFonts) {
sanitizeFonts(document);
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_sanitized.pdf");
PDDocument document = Loader.loadPDF(inputFile.getBytes());
if (removeJavaScript) {
sanitizeJavaScript(document);
}
if (removeEmbeddedFiles) {
sanitizeEmbeddedFiles(document);
}
if (removeMetadata) {
sanitizeMetadata(document);
}
if (removeLinks) {
sanitizeLinks(document);
}
if (removeFonts) {
sanitizeFonts(document);
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_sanitized.pdf");
}
private void sanitizeJavaScript(PDDocument document) throws IOException {

View File

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

View File

@@ -82,7 +82,7 @@ public class OtherWebController {
}
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata";
String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,16 @@ public class TextFinder extends PDFTextStripper {
private final boolean wholeWordSearch;
private final List<PDFText> textOccurrences = new ArrayList<>();
private class MatchInfo {
int startIndex;
int matchLength;
MatchInfo(int startIndex, int matchLength) {
this.startIndex = startIndex;
this.matchLength = matchLength;
}
}
public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch)
throws IOException {
this.searchText = searchText.toLowerCase();
@@ -27,36 +37,37 @@ public class TextFinder extends PDFTextStripper {
setSortByPosition(true);
}
private List<Integer> findOccurrencesInText(String searchText, String content) {
List<Integer> indexes = new ArrayList<>();
private List<MatchInfo> findOccurrencesInText(String searchText, String content) {
List<MatchInfo> matches = new ArrayList<>();
Pattern pattern;
if (useRegex) {
// Use regex-based search
pattern =
wholeWordSearch
? Pattern.compile("(\\b|_|\\.)" + searchText + "(\\b|_|\\.)")
? Pattern.compile("\\b" + searchText + "\\b")
: Pattern.compile(searchText);
} else {
// Use normal text search
pattern =
wholeWordSearch
? Pattern.compile(
"(\\b|_|\\.)" + Pattern.quote(searchText) + "(\\b|_|\\.)")
? Pattern.compile("\\b" + Pattern.quote(searchText) + "\\b")
: Pattern.compile(Pattern.quote(searchText));
}
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
indexes.add(matcher.start());
matches.add(new MatchInfo(matcher.start(), matcher.end() - matcher.start()));
}
return indexes;
return matches;
}
@Override
protected void writeString(String text, List<TextPosition> textPositions) {
for (Integer index : findOccurrencesInText(searchText, text.toLowerCase())) {
if (index + searchText.length() <= textPositions.size()) {
for (MatchInfo match : findOccurrencesInText(searchText, text.toLowerCase())) {
int index = match.startIndex;
if (index + match.matchLength <= textPositions.size()) {
// Initial values based on the first character
TextPosition first = textPositions.get(index);
float minX = first.getX();
@@ -65,7 +76,7 @@ public class TextFinder extends PDFTextStripper {
float maxY = first.getY() + first.getHeight();
// Loop over the rest of the characters and adjust bounding box values
for (int i = index; i < index + searchText.length(); i++) {
for (int i = index; i < index + match.matchLength; i++) {
TextPosition position = textPositions.get(i);
minX = Math.min(minX, position.getX());
minY = Math.min(minY, position.getY());

View File

@@ -7,6 +7,8 @@ import org.springframework.data.jpa.repository.JpaRepository;
import stirling.software.SPDF.model.User;
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findByUsernameIgnoreCase(String username);
Optional<User> findByUsername(String username);
User findByApiKey(String apiKey);

View File

@@ -12,11 +12,12 @@ import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import com.fathzer.soft.javaluator.DoubleEvaluator;
import io.github.pixee.security.HostValidator;
import io.github.pixee.security.Urls;
@@ -115,91 +116,115 @@ public class GeneralUtils {
return null;
}
public static List<Integer> parsePageString(String pageOrder, int totalPages) {
return parsePageString(pageOrder, totalPages, false);
}
public static List<Integer> parsePageString(
String pageOrder, int totalPages, boolean isOneBased) {
if (pageOrder == null || pageOrder.isEmpty()) {
return Collections.singletonList(1);
public static List<Integer> parsePageList(String pages, int totalPages, boolean oneBased) {
if (pages == null) {
return List.of(1); // Default to first page if input is null
}
if (pageOrder.matches("\\d+")) {
// Convert the single number string to an integer and return it in a list
return Collections.singletonList(Integer.parseInt(pageOrder));
try {
return parsePageList(pages.split(","), totalPages, oneBased);
} catch (NumberFormatException e) {
return List.of(1); // Default to first page if input is invalid
}
return parsePageList(pageOrder.split(","), totalPages, isOneBased);
}
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
return parsePageList(pageOrderArr, totalPages, false);
public static List<Integer> parsePageList(String[] pages, int totalPages) {
return parsePageList(pages, totalPages, false);
}
public static List<Integer> parsePageList(
String[] pageOrderArr, int totalPages, boolean isOneBased) {
List<Integer> newPageOrder = new ArrayList<>();
public static List<Integer> parsePageList(String[] pages, int totalPages, boolean oneBased) {
List<Integer> result = new ArrayList<>();
int offset = oneBased ? 1 : 0;
for (String page : pages) {
if ("all".equalsIgnoreCase(page)) {
int adjustmentFactor = isOneBased ? 1 : 0;
// loop through the page order array
for (String element : pageOrderArr) {
if ("all".equalsIgnoreCase(element)) {
for (int i = 0; i < totalPages; i++) {
newPageOrder.add(i + adjustmentFactor);
result.add(i + offset);
}
// As all pages are already added, no need to check further
break;
} else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
// Handle page order as a function
int coefficient = 0;
int constant = 0;
boolean coefficientExists = false;
boolean constantExists = false;
if (element.contains("n")) {
String[] parts = element.split("n");
if (!"".equals(parts[0]) && parts[0] != null) {
coefficient = Integer.parseInt(parts[0]);
coefficientExists = true;
}
if (parts.length > 1 && !"".equals(parts[1]) && parts[1] != null) {
constant = Integer.parseInt(parts[1]);
constantExists = true;
}
} else if (element.contains("+")) {
constant = Integer.parseInt(element.replace("+", ""));
constantExists = true;
}
for (int i = 1; i <= totalPages; i++) {
int pageNum = coefficientExists ? coefficient * i : i;
pageNum += constantExists ? constant : 0;
if (pageNum <= totalPages && pageNum > 0) {
newPageOrder.add(pageNum - adjustmentFactor);
}
}
} else 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 - adjustmentFactor);
} else if (page.contains(",")) {
// Split the string into parts, could be single pages or ranges
String[] parts = page.split(",");
for (String part : parts) {
result.addAll(handlePart(part, totalPages, offset));
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - adjustmentFactor);
result.addAll(handlePart(page, totalPages, offset));
}
}
return new ArrayList<>(
new java.util.LinkedHashSet<>(result)); // Remove duplicates and maintain order
}
return newPageOrder;
public static List<Integer> evaluateNFunc(String expression, int maxValue) {
List<Integer> results = new ArrayList<>();
DoubleEvaluator evaluator = new DoubleEvaluator();
// Validate the expression
if (!expression.matches("[0-9n+\\-*/() ]+")) {
throw new IllegalArgumentException("Invalid expression");
}
int n = 0;
while (true) {
// Replace 'n' with the current value of n, correctly handling numbers before 'n'
String sanitizedExpression = insertMultiplicationBeforeN(expression, n);
Double result = evaluator.evaluate(sanitizedExpression);
// Check if the result is null or not within bounds
if (result == null || result <= 0 || result.intValue() > maxValue) {
if (n != 0) break;
} else {
results.add(result.intValue());
}
n++;
}
return results;
}
private static String insertMultiplicationBeforeN(String expression, int nValue) {
// Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n")
String withMultiplication = expression.replaceAll("(\\d)n", "$1*n");
// Now replace 'n' with its current value
return withMultiplication.replaceAll("n", String.valueOf(nValue));
}
private static List<Integer> handlePart(String part, int totalPages, int offset) {
List<Integer> partResult = new ArrayList<>();
// First check for n-syntax because it should not be processed as a range
if (part.contains("n")) {
partResult = evaluateNFunc(part, totalPages);
// Adjust the results according to the offset
for (int i = 0; i < partResult.size(); i++) {
int adjustedValue = partResult.get(i) - 1 + offset;
partResult.set(i, adjustedValue);
}
} else if (part.contains("-")) {
// Process ranges only if it's not n-syntax
String[] rangeParts = part.split("-");
try {
int start = Integer.parseInt(rangeParts[0]);
int end = Integer.parseInt(rangeParts[1]);
for (int i = start; i <= end; i++) {
if (i >= 1 && i <= totalPages) {
partResult.add(i - 1 + offset);
}
}
} catch (NumberFormatException e) {
// Range is invalid, ignore this part
}
} else {
// This is a single page number
try {
int pageNum = Integer.parseInt(part.trim());
if (pageNum >= 1 && pageNum <= totalPages) {
partResult.add(pageNum - 1 + offset);
}
} catch (NumberFormatException ignored) {
// Ignore invalid numbers
}
}
return partResult;
}
public static boolean createDir(String path) {

View File

@@ -25,6 +25,71 @@ import io.github.pixee.security.Filenames;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
public class PDFToFile {
public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile)
throws IOException, InterruptedException {
if (!"application/pdf".equals(inputFile.getContentType())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
// Get the original PDF file name without the extension
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
Path tempInputFile = null;
Path tempOutputDir = null;
byte[] fileBytes;
String fileName = "temp.file";
try {
// Save the uploaded file to a temporary location
tempInputFile = Files.createTempFile("input_", ".pdf");
Files.copy(
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Prepare the output directory
tempOutputDir = Files.createTempDirectory("output_");
// Run the pdftohtml command with complex output
List<String> command =
new ArrayList<>(
Arrays.asList(
"pdftohtml", "-c", tempInputFile.toString(), pdfBaseName));
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)
.runCommandWithOutputHandling(command, tempOutputDir.toFile());
// Get output files
List<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
// Return output files in a ZIP archive
fileName = pdfBaseName + "ToHtml.zip";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
for (File outputFile : outputFiles) {
ZipEntry entry = new ZipEntry(outputFile.getName());
zipOutputStream.putNextEntry(entry);
FileInputStream fis = new FileInputStream(outputFile);
IOUtils.copy(fis, zipOutputStream);
fis.close();
zipOutputStream.closeEntry();
}
zipOutputStream.close();
fileBytes = byteArrayOutputStream.toByteArray();
} finally {
// Clean up the temporary files
if (tempInputFile != null) Files.delete(tempInputFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
}
return WebResponseUtils.bytesToWebResponse(
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
}
public ResponseEntity<byte[]> processPdfToOfficeFormat(
MultipartFile inputFile, String outputFormat, String libreOfficeFilter)
throws IOException, InterruptedException {
@@ -39,17 +104,7 @@ public class PDFToFile {
// Validate output format
List<String> allowedFormats =
Arrays.asList(
"doc",
"docx",
"odt",
"ppt",
"pptx",
"odp",
"rtf",
"html",
"xml",
"txt:Text");
Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "xml", "txt:Text");
if (!allowedFormats.contains(outputFormat)) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}

View File

@@ -214,6 +214,7 @@ public class PdfUtils {
throws IOException, Exception {
try (PDDocument document = Loader.loadPDF(inputStream)) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
int pageCount = document.getNumberOfPages();
// Create a ByteArrayOutputStream to save the image(s) to

View File

@@ -24,6 +24,7 @@ public class ProcessExecutor {
public enum Processes {
LIBRE_OFFICE,
PDFTOHTML,
OCR_MY_PDF,
PYTHON_OPENCV,
GHOSTSCRIPT,
@@ -45,6 +46,7 @@ public class ProcessExecutor {
int semaphoreLimit =
switch (key) {
case LIBRE_OFFICE -> 1;
case PDFTOHTML -> 1;
case OCR_MY_PDF -> 2;
case PYTHON_OPENCV -> 8;
case GHOSTSCRIPT -> 16;
@@ -56,6 +58,7 @@ public class ProcessExecutor {
long timeoutMinutes =
switch (key) {
case LIBRE_OFFICE -> 30;
case PDFTOHTML -> 5;
case OCR_MY_PDF -> 30;
case PYTHON_OPENCV -> 30;
case GHOSTSCRIPT -> 5;
@@ -169,27 +172,35 @@ public class ProcessExecutor {
errorReaderThread.join();
outputReaderThread.join();
if (!liveUpdates) {
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
messages += outputMessage;
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
messages += outputMessage;
if (!liveUpdates) {
logger.info("Command output:\n" + outputMessage);
}
}
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
messages += errorMessage;
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
messages += errorMessage;
if (!liveUpdates) {
logger.warn("Command error output:\n" + errorMessage);
if (exitCode != 0) {
throw new IOException(
"Command process failed with exit code "
+ exitCode
+ ". Error message: "
+ errorMessage);
}
}
} else if (exitCode != 0) {
throw new IOException("Command process failed with exit code " + exitCode);
if (exitCode != 0) {
throw new IOException(
"Command process failed with exit code "
+ exitCode
+ ". Error message: "
+ errorMessage);
}
}
if (exitCode != 0) {
throw new IOException(
"Command process failed with exit code "
+ exitCode
+ "\nLogs: "
+ messages);
}
} finally {
semaphore.release();

View File

@@ -22,7 +22,7 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/}
spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.encoding=UTF-8
server.connection-timeout=${SYSTEM_CONNECTIONTIMEOUTMINUTES:5m}
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:300000}

View File

@@ -1,6 +1,6 @@
____ _____ ___ ____ _ ___ _ _ ____ ____ ____ _____
____ _____ ___ ____ _ ___ _ _ ____ ____ ____ _____
/ ___|_ _|_ _| _ \| | |_ _| \ | |/ ___| | _ \| _ \| ___|
\___ \ | | | || |_) | | | || \| | | _ _____| |_) | | | | |_
___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _|
|____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_|
\___ \ | | | || |_) | | | || \| | | _ _____| |_) | | | | |_
___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _|
|____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_|
Powered by Spring Boot ${spring-boot.version}

View File

@@ -1,11 +0,0 @@
fileToPDF.fileTypesList=Microsoft Word: (DOC, DOCX, DOT, DOTX) <br> \
Microsoft Excel: (CSV, XLS, XLSX, XLT, XLTX, SLK, DIF) <br> \
Microsoft PowerPoint: (PPT, PPTX) <br> \
OpenDocument Formats: (ODT, OTT, ODS, OTS, ODP, OTP, ODG, OTG) <br> \
Plain Text: (TXT, TEXT, XML) <br> \
Rich Text Format: (RTF) <br> \
Images: (BMP, GIF, JPEG, PNG, TIF, PBM, PGM, PPM, RAS, XBM, XPM, SVG, SVM, WMF) <br> \
HTML: (HTML) <br> \
Lotus Word Pro: (LWP) <br> \
StarOffice formats: (SDA, SDC, SDD, SDW, STC, STD, STI, STW, SXD, SXG, SXI, SXW) <br> \
Other formats: (DBF, FODS, VSD, VOR, VOR3, VOR4, UOP, PCT, PS, PDF)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
###########
###########
# Generic #
###########
# the direction that the language is written (ltr=left to right, rtl = right to left)
@@ -11,14 +11,17 @@ imgPrompt=Изберете изображение(я)
genericSubmit=Подайте
processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла
pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
goToPage=Давай
true=Вярно
false=Невярно
unknown=Непознат
save=Съхранете
saveToBrowser=Save to Browser
close=Затворете
filesSelected=избрани файлове
noFavourites=Няма добавени любими
downloadComplete=Download Complete
bored=Отекчени сте да чакате?
alphabet=Азбука
downloadPdf=Изтеглете PDF
@@ -42,7 +45,7 @@ red=Червено
green=Зелено
blue=Синьо
custom=Персонализиране...
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
poweredBy=Powered by
yes=Yes
no=No
@@ -51,16 +54,32 @@ notAuthenticatedMessage=Потребителят не е автентикира
userNotFoundMessage=Потребителят не е намерен
incorrectPasswordMessage=Текущата парола е неправилна.
usernameExistsMessage=Новият потребител вече съществува.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
error=Error
oops=Oops!
help=Help
goHomepage=Go to Homepage
joinDiscord=Join our Discord server
seeDockerHub=See Docker Hub
visitGithub=Visit Github Repository
donate=Donate
color=Color
sponsor=Sponsor
###############
# Pipeline #
###############
pipeline.header=Pipeline Menu (Alpha)
pipeline.header=Pipeline Menu (Beta)
pipeline.uploadButton=Upload Custom
pipeline.configureButton=Configure
pipeline.defaultOption=Custom
pipeline.submitButton=Submit
pipeline.help=Pipeline Help
pipeline.scanHelp=Folder Scanning Help
######################
# Pipeline Options #
@@ -106,7 +125,7 @@ settings.accountSettings=Настройки на акаунта
changeCreds.title=Промяна на идентификационните данни
changeCreds.header=Актуализирайте данните за акаунта си
changeCreds.changeUserAndPassword=Използвате идентификационни данни за вход по подразбиране. Моля, въведете нова парола (и потребителско име, ако искате)
changeCreds.changePassword=You are using default login credentials. Please enter a new password
changeCreds.newUsername=Ново потребителско име
changeCreds.oldPassword=Текуща парола
changeCreds.newPassword=Нова парола
@@ -120,7 +139,7 @@ account.accountSettings=Настройки на акаунта
account.adminSettings=Настройки на администратора - Преглед и добавяне на потребители
account.userControlSettings=Настройки за потребителски контрол
account.changeUsername=Промени потребител
account.changeUsername=Промени потребител
account.newUsername=Ново потребителско име
account.password=Парола за потвърждение
account.oldPassword=Стара парола
account.newPassword=Нова парола
@@ -141,12 +160,15 @@ adminUserSettings.header=Настройки за администраторск
adminUserSettings.admin=Администратор
adminUserSettings.user=Потребител
adminUserSettings.addUser=Добавяне на нов потребител
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
adminUserSettings.roles=Роли
adminUserSettings.role=Роля
adminUserSettings.actions=Действия
adminUserSettings.apiUser=Ограничен API потребител
adminUserSettings.extraApiUser=Additional Limited API User
adminUserSettings.webOnlyUser=Само за уеб-потребител
adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
adminUserSettings.submit=Съхранете потребителя
@@ -362,11 +384,11 @@ PdfToSinglePage.tags=единична страница
home.showJS.title=Показване на Javascript
home.showJS.desc=Търси и показва всеки JS, инжектиран в PDF
showJS.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
showJS.tags=JS
home.autoRedact.title=Автоматично редактиране
home.autoRedact.desc=Автоматично редактира (зачернява) текст в PDF въз основа на въведен текст
showJS.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
autoRedact.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
home.tableExtraxt.title=PDF to CSV
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
@@ -391,6 +413,15 @@ home.AddStampRequest.desc=Add text or add image stamps at set locations
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDF to Book
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
###########################
# #
# WEB PAGES #
@@ -398,6 +429,7 @@ AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Cust
###########################
#login
login.title=Вход
login.header=Вход
login.signin=Впишете се
login.rememberme=Запомни ме
login.invalid=Невалидно потребителско име или парола.
@@ -410,7 +442,7 @@ autoRedact.title=Автоматично редактиране
autoRedact.header=Автоматично редактиране
autoRedact.colorLabel=Цвят
autoRedact.textsToRedactLabel=Текст за редактиране (разделен с редове)
autoRedact.textsToRedactPlaceholder=например: \nПоверително \nСтрого секретно
autoRedact.textsToRedactPlaceholder=например: \nПоверително \nСтрого секретно
autoRedact.useRegexLabel=Използване на Regex
autoRedact.wholeWordSearchLabel=Търсене на цялата дума
autoRedact.customPaddingLabel=Персонализирана допълнителна подложка
@@ -435,6 +467,7 @@ pdfToSinglePage.submit=Преобразуване към единична стр
pageExtracter.title=Extract Pages
pageExtracter.header=Extract Pages
pageExtracter.submit=Extract
pageExtracter.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#getPdfInfo
@@ -497,7 +530,8 @@ AddStampRequest.overrideY=Override Y Coordinate
AddStampRequest.customMargin=Custom Margin
AddStampRequest.customColor=Custom Text Color
AddStampRequest.submit=Submit
#sanitizePDF
sanitizePDF.title=Дезинфектирай PDF
sanitizePDF.header=Дезинфектира PDF файл
@@ -621,6 +655,18 @@ compare.document.1=Документ 1
compare.document.2=Документ 2
compare.submit=Сравнявай
#BookToPDF
BookToPDF.title=Books and Comics to PDF
BookToPDF.header=Book to PDF
BookToPDF.credit=Uses Calibre
BookToPDF.submit=Convert
#PDFToBook
PDFToBook.title=PDF to Book
PDFToBook.header=PDF to Book
PDFToBook.selectText.1=Format
PDFToBook.credit=Uses Calibre
PDFToBook.submit=Convert
#sign
sign.title=Подпишете
@@ -656,7 +702,7 @@ ScannerImageSplit.selectText.8=Задава минималния праг на
ScannerImageSplit.selectText.9=Размер на рамката:
ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1).
#OCR
ocr.title=OCR / Почистване на сканиране
ocr.header=Почистващи сканирания / OCR (оптично разпознаване на знаци)
@@ -724,11 +770,23 @@ merge.submit=Обединяване
pdfOrganiser.title=Организатор на страници
pdfOrganiser.header=Организатор на PDF страници
pdfOrganiser.submit=Пренареждане на страниците
pdfOrganiser.mode=Mode
pdfOrganiser.mode.1=Custom Page Order
pdfOrganiser.mode.2=Reverse Order
pdfOrganiser.mode.3=Duplex Sort
pdfOrganiser.mode.4=Booklet Sort
pdfOrganiser.mode.5=Side Stitch Booklet Sort
pdfOrganiser.mode.6=Odd-Even Split
pdfOrganiser.mode.7=Remove First
pdfOrganiser.mode.8=Remove Last
pdfOrganiser.mode.9=Remove First and Last
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
#multiTool
multiTool.title=PDF Мулти инструмент
multiTool.header=PDF Мулти инструмент
multiTool.uploadPrompts=Please Upload PDF
#view pdf
viewPdf.title=View PDF
@@ -739,6 +797,7 @@ pageRemover.title=Премахване на страници
pageRemover.header=Премахване на PDF страници
pageRemover.pagesToDelete=Страници за изтриване (Въведете списък с номера на страници, разделени със запетая) :
pageRemover.submit=Изтриване на страници
pageRemover.placeholder=(e.g. 1,2,6 or 1-10,15-30)
#rotate
@@ -748,17 +807,17 @@ rotate.selectAngle=Изберете ъгъл на въртене (кратно
rotate.submit=Завъртане
#merge
#split-pdfs
split.title=Разделяне на PDF
split.header=Разделяне на PDF
split.desc.1=Числата, които избирате, са номера на страницата, на която искате да направите разделяне
split.desc.2=Така че избирането на 1,3,7-8 ще раздели документ от 10 страници на 6 отделни PDF файла с:
split.desc.2=Така че избирането на 1,3,7-9 ще раздели документ от 10 страници на 6 отделни PDF файла с:
split.desc.3=Документ #1: Страница 1
split.desc.4=Документ #2: Страница 2 и 3
split.desc.5=Документ #3: Страница 4, 5 и 6
split.desc.6=Документ #4: Страница 7
split.desc.7=Документ #5: Страница 8
split.desc.8=Документ #6: Страница 9 и 10
split.desc.5=Документ #3: Страница 4, 5, 6 и 7
split.desc.6=Документ #4: Страница 8
split.desc.7=Документ #5: Страница 9
split.desc.8=Документ #6: Страница 10
split.splitPages=Въведете страници за разделяне:
split.submit=Разделяне
@@ -775,8 +834,8 @@ imageToPDF.selectText.2=Автоматично завъртане на PDF
imageToPDF.selectText.3=Файлова логика с много (Активирано само ако работите с множество изображения)
imageToPDF.selectText.4=Сливане към един PDF
imageToPDF.selectText.5=Преобразуване към отделни PDF файлове
#pdfToImage
pdfToImage.title=PDF към Изображение
pdfToImage.header=PDF към Изображение
@@ -826,6 +885,8 @@ watermark.selectText.7=Непрозрачност (0% - 100%):
watermark.selectText.8=Тип воден знак:
watermark.selectText.9=Изображение за воден знак:
watermark.submit=Добавяне на воден знак
watermark.type.1=Text
watermark.type.2=Image
#Change permissions
@@ -866,7 +927,6 @@ changeMetadata.keywords=Ключови думи:
changeMetadata.modDate=Дата на промяна (гггг/ММ/дд ЧЧ:мм:сс):
changeMetadata.producer=Продуцент:
changeMetadata.subject=Тема:
changeMetadata.title=Заглавие:
changeMetadata.trapped=В капан:
changeMetadata.selectText.4=Други метаданни:
changeMetadata.selectText.5=Добавяне на персонализиране метаданни
@@ -878,6 +938,7 @@ pdfToPDFA.title=PDF към PDF/A
pdfToPDFA.header=PDF към PDF/A
pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване.
pdfToPDFA.submit=Преобразуване
pdfToPDFA.tip=Currently does not work for multiple inputs at once
#PDFToWord
@@ -907,7 +968,7 @@ PDFToText.submit=Преобразуване
#PDFToHTML
PDFToHTML.title=PDF към HTML
PDFToHTML.header=PDF към HTML
PDFToHTML.credit=Тази услуга използва LibreOffice за преобразуване на файлове.
PDFToHTML.credit=Тази услуга използва pdftohtml за преобразуване на файлове.
PDFToHTML.submit=Преобразуване
@@ -924,6 +985,7 @@ PDFToCSV.prompt=Choose page to extract table
PDFToCSV.submit=????????
#split-by-size-or-count
split-by-size-or-count.title=Split PDF by Size or Count
split-by-size-or-count.header=Split PDF by Size or Count
split-by-size-or-count.type.label=Select Split Type
split-by-size-or-count.type.size=By Size
@@ -958,7 +1020,7 @@ split-by-sections.vertical.label=Vertical Divisions
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One PDF
#licenses
licenses.nav=Licenses
@@ -969,3 +1031,16 @@ licenses.version=Version
licenses.license=License
# error
error.sorry=Sorry for the issue!
error.needHelp=Need help / Found an issue?
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
error.404.1=We can't seem to find the page you're looking for.
error.404.2=Something went wrong
error.github=Submit a ticket on GitHub
error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
###########
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
@@ -17,16 +17,18 @@ true=True
false=False
unknown=Unknown
save=Save
saveToBrowser=Save to Browser
close=Close
filesSelected=files selected
noFavourites=No favourites added
downloadComplete=Download Complete
bored=Bored Waiting?
alphabet=Alphabet
downloadPdf=Download PDF
text=Text
font=Font
selectFillter=-- Select --
pageNum=Page Number
pageNum=Page Number
sizes.small=Small
sizes.medium=Medium
sizes.large=Large
@@ -43,7 +45,7 @@ red=Red
green=Green
blue=Blue
custom=Custom...
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
poweredBy=Powered by
yes=Yes
no=No
@@ -52,16 +54,32 @@ notAuthenticatedMessage=User not authenticated.
userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
error=Error
oops=Oops!
help=Help
goHomepage=Go to Homepage
joinDiscord=Join our Discord server
seeDockerHub=See Docker Hub
visitGithub=Visit Github Repository
donate=Donate
color=Color
sponsor=Sponsor
###############
# Pipeline #
###############
pipeline.header=Pipeline Menu (Alpha)
pipeline.header=Pipeline Menu (Beta)
pipeline.uploadButton=Upload Custom
pipeline.configureButton=Configure
pipeline.defaultOption=Custom
pipeline.submitButton=Submit
pipeline.help=Pipeline Help
pipeline.scanHelp=Folder Scanning Help
######################
# Pipeline Options #
@@ -107,7 +125,7 @@ settings.accountSettings=Account Settings
changeCreds.title=Change Credentials
changeCreds.header=Update Your Account Details
changeCreds.changeUserAndPassword=You are using default login credentials. Please enter a new password (and username if wanted)
changeCreds.changePassword=You are using default login credentials. Please enter a new password
changeCreds.newUsername=New Username
changeCreds.oldPassword=Current Password
changeCreds.newPassword=New Password
@@ -120,8 +138,8 @@ account.title=Account Settings
account.accountSettings=Account Settings
account.adminSettings=Admin Settings - View and Add Users
account.userControlSettings=User Control Settings
account.changeUsername=New Username
account.changeUsername=Change Username
account.newUsername=New Username
account.password=Confirmation Password
account.oldPassword=Old password
account.newPassword=New Password
@@ -142,13 +160,16 @@ adminUserSettings.header=Admin User Control Settings
adminUserSettings.admin=Admin
adminUserSettings.user=User
adminUserSettings.addUser=Add New User
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters.
adminUserSettings.roles=Roles
adminUserSettings.role=Role
adminUserSettings.actions=Actions
adminUserSettings.apiUser=Limited API User
adminUserSettings.extraApiUser=Additional Limited API User
adminUserSettings.webOnlyUser=Web Only User
adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.forceChange = Force user to change username/password on login
adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User
#############
@@ -172,7 +193,7 @@ merge.tags=merge,Page operations,Back end,server side
home.split.title=Split
home.split.desc=Split PDFs into multiple documents
split.tags=Page operations,divide,Multi Page,cut,server side
split.tags=Page operations,divide,Multi Page,cut,server side
home.rotate.title=Rotate
home.rotate.desc=Easily rotate your PDFs.
@@ -313,7 +334,7 @@ home.add-page-numbers.desc=Add Page numbers throughout a document in a set locat
add-page-numbers.tags=paginate,label,organize,index
home.auto-rename.title=Auto Rename PDF File
home.auto-rename.desc=Auto renames a PDF file based on its detected header
home.auto-rename.desc=Auto renames a PDF file based on its detected header
auto-rename.tags=auto-detect,header-based,organize,relabel
home.adjust-contrast.title=Adjust Colors/Contrast
@@ -367,7 +388,7 @@ showJS.tags=JS
home.autoRedact.title=Auto Redact
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
showJS.tags=Redact,Hide,black out,black,marker,hidden
autoRedact.tags=Redact,Hide,black out,black,marker,hidden
home.tableExtraxt.title=PDF to CSV
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
@@ -392,6 +413,15 @@ home.AddStampRequest.desc=Add text or add image stamps at set locations
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
home.PDFToBook.title=PDF to Book
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
###########################
# #
# WEB PAGES #
@@ -399,6 +429,7 @@ AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Cust
###########################
#login
login.title=Sign in
login.header=Sign in
login.signin=Sign in
login.rememberme=Remember me
login.invalid=Invalid username or password.
@@ -411,7 +442,7 @@ autoRedact.title=Auto Redact
autoRedact.header=Auto Redact
autoRedact.colorLabel=Colour
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
autoRedact.useRegexLabel=Use Regex
autoRedact.wholeWordSearchLabel=Whole Word Search
autoRedact.customPaddingLabel=Custom Extra Padding
@@ -436,6 +467,7 @@ pdfToSinglePage.submit=Convert To Single Page
pageExtracter.title=Extract Pages
pageExtracter.header=Extract Pages
pageExtracter.submit=Extract
pageExtracter.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#getPdfInfo
@@ -498,7 +530,8 @@ AddStampRequest.overrideY=Override Y Coordinate
AddStampRequest.customMargin=Custom Margin
AddStampRequest.customColor=Custom Text Color
AddStampRequest.submit=Submit
#sanitizePDF
sanitizePDF.title=Sanitize PDF
sanitizePDF.header=Sanitize a PDF file
@@ -584,18 +617,18 @@ scalePages.submit=Submit
#certSign
certSign.title=Certificate Signing
certSign.header=Sign a PDF with your certificate (Work in progress)
certSign.selectPDF=Select a PDF File for Signing:
certSign.selectPDF=Select a PDF File for Signing:
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
certSign.certType=Certificate Type
certSign.password=Enter Your Keystore or Private Key Password (If Any):
certSign.password=Enter Your Keystore or Private Key Password (If Any):
certSign.showSig=Show Signature
certSign.reason=Reason
certSign.location=Location
certSign.name=Name
certSign.name=Name
certSign.submit=Sign PDF
@@ -622,6 +655,18 @@ compare.document.1=Document 1
compare.document.2=Document 2
compare.submit=Compare
#BookToPDF
BookToPDF.title=Books and Comics to PDF
BookToPDF.header=Book to PDF
BookToPDF.credit=Uses Calibre
BookToPDF.submit=Convert
#PDFToBook
PDFToBook.title=PDF to Book
PDFToBook.header=PDF to Book
PDFToBook.selectText.1=Format
PDFToBook.credit=Uses Calibre
PDFToBook.submit=Convert
#sign
sign.title=Sign
@@ -657,7 +702,7 @@ ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a pho
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).
#OCR
ocr.title=OCR / Scan Cleanup
ocr.header=Cleanup Scans / OCR (Optical Character Recognition)
@@ -701,7 +746,7 @@ compress.selectText.1=Manual Mode - From 1 to 4
compress.selectText.2=Optimization level:
compress.selectText.3=4 (Terrible for text images)
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB)
compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB)
compress.submit=Compress
@@ -725,11 +770,23 @@ merge.submit=Merge
pdfOrganiser.title=Page Organiser
pdfOrganiser.header=PDF Page Organiser
pdfOrganiser.submit=Rearrange Pages
pdfOrganiser.mode=Mode
pdfOrganiser.mode.1=Custom Page Order
pdfOrganiser.mode.2=Reverse Order
pdfOrganiser.mode.3=Duplex Sort
pdfOrganiser.mode.4=Booklet Sort
pdfOrganiser.mode.5=Side Stitch Booklet Sort
pdfOrganiser.mode.6=Odd-Even Split
pdfOrganiser.mode.7=Remove First
pdfOrganiser.mode.8=Remove Last
pdfOrganiser.mode.9=Remove First and Last
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
#multiTool
multiTool.title=PDF Multi Tool
multiTool.header=PDF Multi Tool
multiTool.uploadPrompts=Please Upload PDF
#view pdf
viewPdf.title=View PDF
@@ -740,6 +797,7 @@ pageRemover.title=Page Remover
pageRemover.header=PDF Page remover
pageRemover.pagesToDelete=Pages to delete (Enter a comma-separated list of page numbers) :
pageRemover.submit=Delete Pages
pageRemover.placeholder=(e.g. 1,2,6 or 1-10,15-30)
#rotate
@@ -749,17 +807,17 @@ rotate.selectAngle=Select rotation angle (in multiples of 90 degrees):
rotate.submit=Rotate
#merge
#split-pdfs
split.title=Split PDF
split.header=Split PDF
split.desc.1=The numbers you select are the page number you wish to do a split on
split.desc.2=As such selecting 1,3,7-8 would split a 10 page document into 6 separate PDFS with:
split.desc.2=As such selecting 1,3,7-9 would split a 10 page document into 6 separate PDFS with:
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.desc.5=Document #3: Page 4, 5, 6 and 7
split.desc.6=Document #4: Page 8
split.desc.7=Document #5: Page 9
split.desc.8=Document #6: Page 10
split.splitPages=Enter pages to split on:
split.submit=Split
@@ -776,8 +834,8 @@ imageToPDF.selectText.2=Auto rotate PDF
imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images)
imageToPDF.selectText.4=Merge into single PDF
imageToPDF.selectText.5=Convert to separate PDFs
#pdfToImage
pdfToImage.title=PDF to Image
pdfToImage.header=PDF to Image
@@ -827,6 +885,8 @@ watermark.selectText.7=Opacity (0% - 100%):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.submit=Add Watermark
watermark.type.1=Text
watermark.type.2=Image
#Change permissions
@@ -867,7 +927,6 @@ changeMetadata.keywords=Keywords:
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Producer:
changeMetadata.subject=Subject:
changeMetadata.title=Title:
changeMetadata.trapped=Trapped:
changeMetadata.selectText.4=Other Metadata:
changeMetadata.selectText.5=Add Custom Metadata Entry
@@ -879,6 +938,7 @@ pdfToPDFA.title=PDF To PDF/A
pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once
#PDFToWord
@@ -908,7 +968,7 @@ PDFToText.submit=Convert
#PDFToHTML
PDFToHTML.title=PDF to HTML
PDFToHTML.header=PDF to HTML
PDFToHTML.credit=This service uses LibreOffice for file conversion.
PDFToHTML.credit=This service uses pdftohtml for file conversion.
PDFToHTML.submit=Convert
@@ -925,6 +985,7 @@ PDFToCSV.prompt=Choose page to extract table
PDFToCSV.submit=Extract
#split-by-size-or-count
split-by-size-or-count.title=Split PDF by Size or Count
split-by-size-or-count.header=Split PDF by Size or Count
split-by-size-or-count.type.label=Select Split Type
split-by-size-or-count.type.size=By Size
@@ -959,7 +1020,7 @@ split-by-sections.vertical.label=Vertical Divisions
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One PDF
#licenses
licenses.nav=Licenses
@@ -970,3 +1031,16 @@ licenses.version=Version
licenses.license=License
# error
error.sorry=Sorry for the issue!
error.needHelp=Need help / Found an issue?
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
error.404.1=We can't seem to find the page you're looking for.
error.404.2=Something went wrong
error.github=Submit a ticket on GitHub
error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post

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