Compare commits
134 Commits
ghostscrip
...
v0.36.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1c5384a37 | ||
|
|
5607f7079e | ||
|
|
3b8723975d | ||
|
|
41a3d28c90 | ||
|
|
f7afe73cb4 | ||
|
|
73e5246191 | ||
|
|
1f39481efe | ||
|
|
5ac2260d78 | ||
|
|
bae83a281c | ||
|
|
dd2aae60ad | ||
|
|
30ee33002d | ||
|
|
24717dde19 | ||
|
|
509a305985 | ||
|
|
13572a7f18 | ||
|
|
ebd0ddc6ad | ||
|
|
43c4ec1089 | ||
|
|
1ccdc1697b | ||
|
|
2297c5dc95 | ||
|
|
859c9942e6 | ||
|
|
c723696bd0 | ||
|
|
fe198458ca | ||
|
|
378aca4460 | ||
|
|
9870e6ad7c | ||
|
|
40b5904726 | ||
|
|
de3f59cf44 | ||
|
|
4ae7c83357 | ||
|
|
f127271709 | ||
|
|
f899088c75 | ||
|
|
d55e007f93 | ||
|
|
ccdcb05e65 | ||
|
|
e6c2ad8c9c | ||
|
|
32aa623c8b | ||
|
|
23888c5d2c | ||
|
|
2f23eb69c6 | ||
|
|
446cedf26e | ||
|
|
f950e25dad | ||
|
|
65a86a6e97 | ||
|
|
7f4134f52d | ||
|
|
55969697b8 | ||
|
|
86662d9cf6 | ||
|
|
2437460c73 | ||
|
|
a7960d992c | ||
|
|
c98cd8117f | ||
|
|
50c5efac87 | ||
|
|
0952245b23 | ||
|
|
83ddfdf152 | ||
|
|
08777c78b1 | ||
|
|
c6980e9693 | ||
|
|
fc514ee65b | ||
|
|
44be2b99d2 | ||
|
|
549824c91f | ||
|
|
1c0d1efda6 | ||
|
|
9d8d90bf2f | ||
|
|
a7e7d57b23 | ||
|
|
e014bb023b | ||
|
|
1c5dfc46a0 | ||
|
|
11273b1589 | ||
|
|
dd5af46906 | ||
|
|
c20d37518d | ||
|
|
eb20f51958 | ||
|
|
97d28ac6d2 | ||
|
|
026fe8150d | ||
|
|
c3f88f716c | ||
|
|
67f983f00d | ||
|
|
9167f12296 | ||
|
|
93e190fdeb | ||
|
|
82bebf5c62 | ||
|
|
bb3f076e6d | ||
|
|
64dfa4b841 | ||
|
|
0f6f3f305a | ||
|
|
58c7d7b9a8 | ||
|
|
ef8231de3a | ||
|
|
c1c3eba398 | ||
|
|
52693541d9 | ||
|
|
1639e0fc4c | ||
|
|
0652299bec | ||
|
|
1d6511b043 | ||
|
|
a400fe6015 | ||
|
|
b47df3d252 | ||
|
|
cb6e1cd94e | ||
|
|
6ee6254f5a | ||
|
|
f2c9549ba1 | ||
|
|
58278c07ff | ||
|
|
4d017610b8 | ||
|
|
dcafc0d487 | ||
|
|
2ec8c97737 | ||
|
|
397a07afe8 | ||
|
|
b072c39fd9 | ||
|
|
1bc6b4149c | ||
|
|
5a5a8bb7ba | ||
|
|
b6eca59f23 | ||
|
|
7108424a92 | ||
|
|
400965ffc8 | ||
|
|
1895a04394 | ||
|
|
f8f137a30a | ||
|
|
f6a2d4784b | ||
|
|
526dc9f911 | ||
|
|
cce9f74eb9 | ||
|
|
0e3865618d | ||
|
|
d888ed1ae0 | ||
|
|
99d1b46d97 | ||
|
|
32e46eeb73 | ||
|
|
b7da84d257 | ||
|
|
1c1ead5d62 | ||
|
|
6ff53aa5b3 | ||
|
|
8d60b08cd9 | ||
|
|
64cf5167c0 | ||
|
|
de4637e8d4 | ||
|
|
3c0a8071dc | ||
|
|
04ccdf6f76 | ||
|
|
db02fba31f | ||
|
|
5b6f649e4e | ||
|
|
de23bb702c | ||
|
|
25e564154e | ||
|
|
3633a979d3 | ||
|
|
99d481d69f | ||
|
|
a5ba6c403a | ||
|
|
b2e6d89d16 | ||
|
|
b59d2d15b4 | ||
|
|
61e750646c | ||
|
|
de9c21b3de | ||
|
|
b32d6cb858 | ||
|
|
d832a90de0 | ||
|
|
212e521238 | ||
|
|
0915e72a3d | ||
|
|
ee5013651f | ||
|
|
4aa44e6fc0 | ||
|
|
41c743a9f8 | ||
|
|
833b3c45c6 | ||
|
|
654bc94d44 | ||
|
|
86fa404c90 | ||
|
|
f4082e3f96 | ||
|
|
c93a48b40d | ||
|
|
75e10efcbd |
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 💬 Discord Server
|
- name: 💬 Discord Server
|
||||||
url: https://discord.gg/Cn8pWhQRxZ
|
url: https://discord.gg/HYmhKj45pU
|
||||||
about: You can join our Discord server for real time discussion and support
|
about: You can join our Discord server for real time discussion and support
|
||||||
|
|||||||
91
.github/workflows/multiOSReleases.yml
vendored
Normal file
91
.github/workflows/multiOSReleases.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
name: Test Installers Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
jobs:
|
||||||
|
build-installers:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win
|
||||||
|
ext: exe
|
||||||
|
#- os: macos-latest
|
||||||
|
# platform: mac
|
||||||
|
# ext: dmg
|
||||||
|
#- os: ubuntu-latest
|
||||||
|
# platform: linux
|
||||||
|
# ext: deb
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: "21"
|
||||||
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: 8.7
|
||||||
|
|
||||||
|
# Install Windows dependencies
|
||||||
|
- name: Install WiX Toolset
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: |
|
||||||
|
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
|
||||||
|
.\wix.exe /install /quiet
|
||||||
|
|
||||||
|
# Install Linux dependencies
|
||||||
|
- name: Install Linux Dependencies
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y fakeroot rpm
|
||||||
|
|
||||||
|
# Get version number
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get version number mac
|
||||||
|
id: versionNumberMac
|
||||||
|
run: echo "versionNumberMac=$(./gradlew printMacVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Build installer
|
||||||
|
- name: Build Installer
|
||||||
|
run: ./gradlew build jpackage -x test --info
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: false
|
||||||
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
|
|
||||||
|
# Rename and collect artifacts based on OS
|
||||||
|
- name: Prepare artifacts
|
||||||
|
id: prepare
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||||
|
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
|
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
|
||||||
|
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
|
else
|
||||||
|
mv "build/jpackage/stirling-pdf_${{ steps.versionNumber.outputs.versionNumber }}-1_amd64.deb" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload installer as artifact for testing
|
||||||
|
- name: Upload Installer Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Stirling-PDF-${{ matrix.platform }}-installer.{{ matrix.ext }}
|
||||||
|
path: Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
59
.github/workflows/releaseArtifacts.yml
vendored
59
.github/workflows/releaseArtifacts.yml
vendored
@@ -35,36 +35,37 @@ jobs:
|
|||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
||||||
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Rename binarie
|
- name: Rename binarie
|
||||||
if: matrix.file_suffix != ''
|
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
|
||||||
|
|
||||||
- name: Upload Assets binarie
|
- name: Upload Assets binarie
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
name: Stirling-PDF${{ matrix.file_suffix }}.exe
|
name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
- name: Rename jar binaries
|
- name: Rename jar binaries
|
||||||
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload Assets jar binaries
|
- name: Upload Assets jar binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
path: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
name: Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@@ -72,4 +73,44 @@ jobs:
|
|||||||
- name: Upload jar binaries to release
|
- name: Upload jar binaries to release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
files: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
|
||||||
|
push-ui:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: "17"
|
||||||
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: 8.7
|
||||||
|
|
||||||
|
- name: Generate exe
|
||||||
|
run: ./gradlew clean createExe
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: false
|
||||||
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload Assets binarie
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./build/launch4j/Stirling-PDF.exe
|
||||||
|
name: Stirling-PDF.exe
|
||||||
|
overwrite: true
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: ./build/launch4j/Stirling-PDF.exe
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -161,3 +161,4 @@ out/
|
|||||||
.pytest_cache
|
.pytest_cache
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
**/jcef-bundle/
|
||||||
@@ -11,7 +11,7 @@ Stirling-PDF is built using:
|
|||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- PDFBox
|
- PDFBox
|
||||||
- LibreOffice
|
- LibreOffice
|
||||||
- OcrMyPdf
|
- qpdf
|
||||||
- HTML, CSS, JavaScript
|
- HTML, CSS, JavaScript
|
||||||
- Docker
|
- Docker
|
||||||
- PDF.js
|
- PDF.js
|
||||||
@@ -243,7 +243,7 @@ To run Stirling-PDF locally:
|
|||||||
|
|
||||||
Important notes:
|
Important notes:
|
||||||
|
|
||||||
- Local testing doesn't include features that depend on external tools like OCRmyPDF, LibreOffice, or Python scripts.
|
- Local testing doesn't include features that depend on external tools like qpdf, LibreOffice, or Python scripts.
|
||||||
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
|
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
|
||||||
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
|
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
|
qpdf \
|
||||||
shadow \
|
shadow \
|
||||||
su-exec \
|
su-exec \
|
||||||
openssl \
|
openssl \
|
||||||
@@ -40,7 +41,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
# pdftohtml
|
# pdftohtml
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced features)
|
# OCR MY PDF (unpaper for descew and other advanced features)
|
||||||
ocrmypdf \
|
|
||||||
tesseract-ocr-data-eng \
|
tesseract-ocr-data-eng \
|
||||||
# CV
|
# CV
|
||||||
py3-opencv \
|
py3-opencv \
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
# pdftohtml
|
# pdftohtml
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
ocrmypdf \
|
qpdf \
|
||||||
tesseract-ocr-data-eng \
|
tesseract-ocr-data-eng \
|
||||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||||
# CV
|
# CV
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.20.3
|
FROM alpine:3.21.0
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript | Unoconv | Ghostscript |
|
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | qpdf | Java | Javascript | Unoconv | tesseract |
|
||||||
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- |
|
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- |
|
||||||
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | |
|
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | |
|
||||||
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | |
|
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | |
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | |
|
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | |
|
||||||
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
||||||
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | | | |
|
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | | | |
|
||||||
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | ✔️ |
|
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | |
|
||||||
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | |
|
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | |
|
||||||
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
||||||
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
||||||
@@ -34,13 +34,13 @@
|
|||||||
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | |
|
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | |
|
||||||
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | |
|
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | |
|
||||||
| compare | | | | ✔️ | | | | | | | ✔️ | | |
|
| compare | | | | ✔️ | | | | | | | ✔️ | | |
|
||||||
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | ✔️ |
|
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
|
||||||
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
||||||
| extract-images | | | | ✔️ | | | | | | ✔️ | | | |
|
| extract-images | | | | ✔️ | | | | | | ✔️ | | | |
|
||||||
| flatten | | | | ✔️ | | | | | | | ✔️ | | |
|
| flatten | | | | ✔️ | | | | | | | ✔️ | | |
|
||||||
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | |
|
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | |
|
||||||
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
|
| ocr-pdf | | | | ✔️ | ✔️ | | | | | | | | ✔ |
|
||||||
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
||||||
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | ✔️ |
|
| repair | | | | ✔️ | ✔️ | | | ✔️ | ✔ | | | | |
|
||||||
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | |
|
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | |
|
||||||
| sign | | | | ✔️ | | | | | | | ✔️ | | |
|
| sign | | | | ✔️ | | | | | | | ✔️ | | |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ The paths have changed for the tessdata locations on new Docker images. Please u
|
|||||||
|
|
||||||
## How does the OCR Work
|
## How does the OCR Work
|
||||||
|
|
||||||
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF), which in turn uses Tesseract for its text recognition. All credit goes to them for this awesome work!
|
Stirling-PDF uses Tesseract for its text recognition. All credit goes to them for this awesome work!
|
||||||
|
|
||||||
## Language Packs
|
## Language Packs
|
||||||
|
|
||||||
@@ -52,8 +52,6 @@ Add the following to your existing Docker run command:
|
|||||||
|
|
||||||
### Non-Docker Setup
|
### Non-Docker Setup
|
||||||
|
|
||||||
If you are not using Docker, you need to install the OCR components, including the `ocrmypdf` app. You can see the [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html).
|
|
||||||
|
|
||||||
For Debian-based systems, install languages with this command:
|
For Debian-based systems, install languages with this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -83,8 +81,7 @@ rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
|
|||||||
|
|
||||||
For Windows:
|
For Windows:
|
||||||
|
|
||||||
Ensure ocrmypdf in installed with
|
You must ensure tesseract is installed
|
||||||
``pip install ocrmypdf``
|
|
||||||
|
|
||||||
Additional languages must be downloaded manually:
|
Additional languages must be downloaded manually:
|
||||||
Download desired .traineddata files from tessdata or tessdata_fast
|
Download desired .traineddata files from tessdata or tessdata_fast
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ nix-env -iA nixpkgs.jbig2enc
|
|||||||
|
|
||||||
### Step 3: Install Additional Software
|
### Step 3: Install Additional Software
|
||||||
|
|
||||||
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and OpenCV for pattern recognition functionality.
|
Next we need to install LibreOffice for conversions, qpdf for OCR, and OpenCV for pattern recognition functionality.
|
||||||
|
|
||||||
Install the following software:
|
Install the following software:
|
||||||
|
|
||||||
@@ -81,27 +81,27 @@ Install the following software:
|
|||||||
- unoconv
|
- unoconv
|
||||||
- pngquant
|
- pngquant
|
||||||
- unpaper
|
- unpaper
|
||||||
- ocrmypdf
|
- qpdf
|
||||||
- opencv-python-headless
|
- opencv-python-headless
|
||||||
|
|
||||||
For Debian-based systems, you can use the following command:
|
For Debian-based systems, you can use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
|
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
|
||||||
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages
|
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages
|
||||||
```
|
```
|
||||||
|
|
||||||
For Fedora:
|
For Fedora:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
|
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
|
||||||
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
||||||
```
|
```
|
||||||
|
|
||||||
For Nix:
|
For Nix:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.ocrmypdf nixpkgs.poppler_utils
|
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.qpdf nixpkgs.poppler_utils
|
||||||
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -146,7 +146,6 @@ The easiest method is to use the language packs provided by your repositories. S
|
|||||||
|
|
||||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
||||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
||||||
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.
|
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
|
||||||
|
|
||||||
|
|||||||
87
README.md
87
README.md
@@ -2,7 +2,7 @@
|
|||||||
<h1 align="center">Stirling-PDF</h1>
|
<h1 align="center">Stirling-PDF</h1>
|
||||||
|
|
||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
[](https://discord.gg/Cn8pWhQRxZ)
|
[](https://discord.gg/HYmhKj45pU)
|
||||||
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
||||||
- Dark mode support
|
- Dark mode support
|
||||||
- Custom download options
|
- Custom download options
|
||||||
- Parallel file processing and downloads
|
- Parallel file processing and downloads
|
||||||
@@ -79,15 +79,15 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
|||||||
- Detect and remove blank pages
|
- Detect and remove blank pages
|
||||||
- Compare two PDFs and show differences in text
|
- Compare two PDFs and show differences in text
|
||||||
- Add images to PDFs
|
- Add images to PDFs
|
||||||
- Compress PDFs to decrease their filesize (using OCRMyPDF)
|
- Compress PDFs to decrease their filesize (using qpdf)
|
||||||
- Extract images from PDF
|
- Extract images from PDF
|
||||||
- Remove images from PDF
|
- Remove images from PDF
|
||||||
- Extract images from scans
|
- Extract images from scans
|
||||||
- Remove annotations
|
- Remove annotations
|
||||||
- Add page numbers
|
- Add page numbers
|
||||||
- Auto rename file by detecting PDF header text
|
- Auto rename file by detecting PDF header text
|
||||||
- OCR on PDF (using OCRMyPDF)
|
- OCR on PDF (using tesseract)
|
||||||
- PDF/A conversion (using OCRMyPDF)
|
- PDF/A conversion (using libreoffice)
|
||||||
- Edit metadata
|
- Edit metadata
|
||||||
- Flatten PDFs
|
- Flatten PDFs
|
||||||
- Get all information on a PDF to view or export as JSON
|
- Get all information on a PDF to view or export as JSON
|
||||||
@@ -102,7 +102,7 @@ A demo of the app is available [here](https://stirlingpdf.io).
|
|||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
|
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
|
||||||
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
||||||
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
|
- [qpdf](https://github.com/qpdf/qpdf)
|
||||||
- HTML, CSS, JavaScript
|
- HTML, CSS, JavaScript
|
||||||
- Docker
|
- Docker
|
||||||
- [PDF.js](https://github.com/mozilla/pdf.js)
|
- [PDF.js](https://github.com/mozilla/pdf.js)
|
||||||
@@ -187,47 +187,48 @@ Certain functionality like `Sign` supports pre-saved files stored at `/customFil
|
|||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
Stirling-PDF currently supports 37 languages!
|
Stirling-PDF currently supports 38 languages!
|
||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
||||||
|
|
||||||
@@ -240,7 +241,7 @@ Stirling PDF offers a Enterprise edition of its software, This is the same great
|
|||||||
### Whats included
|
### Whats included
|
||||||
|
|
||||||
- Prioritised Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from same email domain registered with us)
|
- Prioritised Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from same email domain registered with us)
|
||||||
- Prioritised Enhancements to Stirling-PDF where applicable
|
- Prioritised Enhancements to Stirling-PDF where applicable
|
||||||
- Base SSO support
|
- Base SSO support
|
||||||
- Advanced SSO such as automated login handling (Coming very soon)
|
- Advanced SSO such as automated login handling (Coming very soon)
|
||||||
- SAML SSO (Coming very soon)
|
- SAML SSO (Coming very soon)
|
||||||
@@ -404,7 +405,7 @@ To access your account settings, go to Account Settings in the settings cog menu
|
|||||||
|
|
||||||
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
|
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
|
||||||
|
|
||||||
For API usage, you must provide a header with `X-API-Key` and the associated API key for that user.
|
For API usage, you must provide a header with `X-API-KEY` and the associated API key for that user.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ The 'Fat' container contains all those found in 'Full' with security jar along w
|
|||||||
| Libre | | ✔️ |
|
| Libre | | ✔️ |
|
||||||
| Python | | ✔️ |
|
| Python | | ✔️ |
|
||||||
| OpenCV | | ✔️ |
|
| OpenCV | | ✔️ |
|
||||||
| OCRmyPDF | | ✔️ |
|
| qpdf | | ✔️ |
|
||||||
|
|
||||||
| Operation | Ultra-Lite | Full |
|
| Operation | Ultra-Lite | Full |
|
||||||
| ---------------------- | ---------- | ---- |
|
| ---------------------- | ---------- | ---- |
|
||||||
|
|||||||
191
build.gradle
191
build.gradle
@@ -8,6 +8,7 @@ plugins {
|
|||||||
id "com.diffplug.spotless" version "6.25.0"
|
id "com.diffplug.spotless" version "6.25.0"
|
||||||
id "com.github.jk1.dependency-license-report" version "2.9"
|
id "com.github.jk1.dependency-license-report" version "2.9"
|
||||||
//id "nebula.lint" version "19.0.3"
|
//id "nebula.lint" version "19.0.3"
|
||||||
|
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -21,10 +22,13 @@ ext {
|
|||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.12.0"
|
||||||
lombokVersion = "1.18.36"
|
lombokVersion = "1.18.36"
|
||||||
bouncycastleVersion = "1.79"
|
bouncycastleVersion = "1.79"
|
||||||
|
springSecuritySamlVersion = "6.4.1"
|
||||||
|
openSamlVersion = "4.3.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.34.0"
|
version = "0.36.1"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@@ -38,6 +42,9 @@ repositories {
|
|||||||
maven {
|
maven {
|
||||||
url 'https://build.shibboleth.net/maven/releases'
|
url 'https://build.shibboleth.net/maven/releases'
|
||||||
}
|
}
|
||||||
|
maven { url "https://build.shibboleth.net/maven/releases" }
|
||||||
|
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@@ -61,6 +68,12 @@ sourceSets {
|
|||||||
exclude "stirling/software/SPDF/model/User.java"
|
exclude "stirling/software/SPDF/model/User.java"
|
||||||
exclude "stirling/software/SPDF/repository/**"
|
exclude "stirling/software/SPDF/repository/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
||||||
|
exclude "stirling/software/SPDF/UI/impl/**"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,16 +84,153 @@ openApi {
|
|||||||
outputFileName = "SwaggerDoc.json"
|
outputFileName = "SwaggerDoc.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//0.11.5 to 2024.11.5
|
||||||
|
def getMacVersion(String version) {
|
||||||
|
def currentYear = java.time.Year.now().getValue()
|
||||||
|
def versionParts = version.split("\\.", 2)
|
||||||
|
return "${currentYear}.${versionParts.length > 1 ? versionParts[1] : versionParts[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
jpackage {
|
||||||
|
input = "build/libs"
|
||||||
|
|
||||||
|
appName = "Stirling-PDF"
|
||||||
|
appVersion = project.version
|
||||||
|
vendor = "Stirling-Software"
|
||||||
|
appDescription = "Stirling PDF - Your Local PDF Editor"
|
||||||
|
|
||||||
|
mainJar = "Stirling-PDF-${project.version}.jar"
|
||||||
|
mainClass = "org.springframework.boot.loader.launch.JarLauncher"
|
||||||
|
|
||||||
|
icon = "src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// JVM Options
|
||||||
|
javaOptions = [
|
||||||
|
"-DBROWSER_OPEN=true",
|
||||||
|
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
||||||
|
"-Djava.awt.headless=false",
|
||||||
|
"-Dapple.awt.UIElement=true",
|
||||||
|
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
||||||
|
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
|
||||||
|
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
destination = "${projectDir}/build/jpackage"
|
||||||
|
|
||||||
|
// Windows-specific configuration
|
||||||
|
windows {
|
||||||
|
launcherAsService = false
|
||||||
|
appVersion = project.version
|
||||||
|
winConsole = false
|
||||||
|
winDirChooser = true
|
||||||
|
winMenu = true
|
||||||
|
winShortcut = true
|
||||||
|
winPerUserInstall = true
|
||||||
|
winMenuGroup = "Stirling Software"
|
||||||
|
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
|
||||||
|
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
|
||||||
|
winUpdateUrl = "https://github.com/Stirling-Tools/Stirling-PDF/releases"
|
||||||
|
type = "exe"
|
||||||
|
installDir = "C:/Program Files/Stirling-PDF"
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS-specific configuration
|
||||||
|
mac {
|
||||||
|
appVersion = getMacVersion(project.version.toString())
|
||||||
|
icon = "src/main/resources/static/favicon.icns"
|
||||||
|
type = "dmg"
|
||||||
|
macPackageIdentifier = "com.stirling.software.pdf"
|
||||||
|
macPackageName = "Stirling-PDF"
|
||||||
|
macAppCategory = "public.app-category.productivity"
|
||||||
|
macSign = false // Enable signing
|
||||||
|
macAppStore = false // Not targeting App Store initially
|
||||||
|
|
||||||
|
//installDir = "Applications"
|
||||||
|
|
||||||
|
// Add license and other documentation to DMG
|
||||||
|
/*macDmgContent = [
|
||||||
|
"README.md",
|
||||||
|
"LICENSE",
|
||||||
|
"CHANGELOG.md"
|
||||||
|
]*/
|
||||||
|
|
||||||
|
// Enable Mac-specific entitlements
|
||||||
|
//macEntitlements = "entitlements.plist" // You'll need to create this file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux-specific configuration
|
||||||
|
linux {
|
||||||
|
appVersion = project.version
|
||||||
|
icon = "src/main/resources/static/favicon.png"
|
||||||
|
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
||||||
|
|
||||||
|
// Debian package configuration
|
||||||
|
//linuxPackageName = "stirlingpdf"
|
||||||
|
linuxDebMaintainer = "support@stirlingpdf.com"
|
||||||
|
linuxMenuGroup = "Office;PDF;Productivity"
|
||||||
|
linuxAppCategory = "Office"
|
||||||
|
linuxAppRelease = "1"
|
||||||
|
linuxPackageDeps = true
|
||||||
|
|
||||||
|
installDir = "/opt/Stirling-PDF"
|
||||||
|
|
||||||
|
// RPM-specific settings
|
||||||
|
//linuxRpmLicenseType = "MIT"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common additional options
|
||||||
|
//jLinkOptions = [
|
||||||
|
// "--strip-debug",
|
||||||
|
// "--compress=2",
|
||||||
|
// "--no-header-files",
|
||||||
|
// "--no-man-pages"
|
||||||
|
//]
|
||||||
|
|
||||||
|
// Add any additional modules required
|
||||||
|
/*addModules = [
|
||||||
|
"java.base",
|
||||||
|
"java.desktop",
|
||||||
|
"java.logging",
|
||||||
|
"java.sql",
|
||||||
|
"java.xml",
|
||||||
|
"jdk.crypto.ec"
|
||||||
|
]*/
|
||||||
|
|
||||||
|
// Add copyright and license information
|
||||||
|
copyright = "Copyright © 2024 Stirling Software"
|
||||||
|
licenseFile = "LICENSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
launch4j {
|
launch4j {
|
||||||
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
outfile="Stirling-PDF.exe"
|
outfile="Stirling-PDF.exe"
|
||||||
headerType="console"
|
|
||||||
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
|
headerType = "gui"
|
||||||
|
} else {
|
||||||
|
headerType = "console"
|
||||||
|
}
|
||||||
jarTask = tasks.bootJar
|
jarTask = tasks.bootJar
|
||||||
|
|
||||||
errTitle="Encountered error, Do you have Java 21?"
|
errTitle="Encountered error, Do you have Java 21?"
|
||||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||||
variables=["BROWSER_OPEN=true"]
|
|
||||||
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
|
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
|
||||||
|
} else {
|
||||||
|
variables=["BROWSER_OPEN=true"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
jreMinVersion="17"
|
jreMinVersion="17"
|
||||||
|
|
||||||
mutexName="Stirling-PDF"
|
mutexName="Stirling-PDF"
|
||||||
@@ -120,6 +270,13 @@ configurations.all {
|
|||||||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
||||||
|
implementation "me.friwi:jcefmaven:127.3.1"
|
||||||
|
implementation "org.openjfx:javafx-controls:21"
|
||||||
|
implementation "org.openjfx:javafx-swing:21"
|
||||||
|
}
|
||||||
|
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.2.0"
|
implementation "org.springframework:spring-webmvc:6.2.0"
|
||||||
|
|
||||||
@@ -139,21 +296,22 @@ dependencies {
|
|||||||
|
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||||
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
|
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.4.1'
|
implementation "org.springframework.session:spring-session-core:$springBootVersion"
|
||||||
|
|
||||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||||
// Don't upgrade h2database
|
// Don't upgrade h2database
|
||||||
runtimeOnly "com.h2database:h2:2.3.232"
|
runtimeOnly "com.h2database:h2:2.3.232"
|
||||||
constraints {
|
constraints {
|
||||||
implementation "org.opensaml:opensaml-core"
|
implementation "org.opensaml:opensaml-core:$openSamlVersion"
|
||||||
implementation "org.opensaml:opensaml-saml-api"
|
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
|
||||||
implementation "org.opensaml:opensaml-saml-impl"
|
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
|
||||||
}
|
}
|
||||||
implementation "org.springframework.security:spring-security-saml2-service-provider"
|
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
||||||
|
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
|
||||||
implementation 'com.coveo:saml-client:5.0.0'
|
implementation 'com.coveo:saml-client:5.0.0'
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +343,7 @@ dependencies {
|
|||||||
// Image metadata extractor
|
// Image metadata extractor
|
||||||
implementation "com.drewnoakes:metadata-extractor:2.19.0"
|
implementation "com.drewnoakes:metadata-extractor:2.19.0"
|
||||||
|
|
||||||
implementation "commons-io:commons-io:2.17.0"
|
implementation "commons-io:commons-io:2.18.0"
|
||||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||||
//general PDF
|
//general PDF
|
||||||
|
|
||||||
@@ -267,7 +425,14 @@ jar {
|
|||||||
tasks.named("test") {
|
tasks.named("test") {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
task printVersion {
|
task printVersion {
|
||||||
println project.version
|
doLast {
|
||||||
|
println project.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task printMacVersion {
|
||||||
|
doLast {
|
||||||
|
println getMacVersion(project.version.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,24 +48,6 @@ Feature: API Validation
|
|||||||
And the response status code should be 200
|
And the response status code should be 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ocr @negative
|
|
||||||
Scenario: Process PDF with text and OCR with type normal
|
|
||||||
Given I generate a PDF file as "fileInput"
|
|
||||||
And the pdf contains 3 pages with random text
|
|
||||||
And the request data includes
|
|
||||||
| parameter | value |
|
|
||||||
| languages | eng |
|
|
||||||
| sidecar | false |
|
|
||||||
| deskew | true |
|
|
||||||
| clean | true |
|
|
||||||
| cleanFinal | true |
|
|
||||||
| ocrType | Normal |
|
|
||||||
| ocrRenderType | hocr |
|
|
||||||
| removeImagesAfter| false |
|
|
||||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
|
||||||
Then the response status code should be 500
|
|
||||||
|
|
||||||
@ocr @positive
|
@ocr @positive
|
||||||
Scenario: Process PDF with OCR
|
Scenario: Process PDF with OCR
|
||||||
Given I generate a PDF file as "fileInput"
|
Given I generate a PDF file as "fileInput"
|
||||||
@@ -83,26 +65,6 @@ Feature: API Validation
|
|||||||
Then the response content type should be "application/pdf"
|
Then the response content type should be "application/pdf"
|
||||||
And the response file should have size greater than 0
|
And the response file should have size greater than 0
|
||||||
And the response status code should be 200
|
And the response status code should be 200
|
||||||
|
|
||||||
@ocr @positive
|
|
||||||
Scenario: Process PDF with OCR with sidecar
|
|
||||||
Given I generate a PDF file as "fileInput"
|
|
||||||
And the request data includes
|
|
||||||
| parameter | value |
|
|
||||||
| languages | eng |
|
|
||||||
| sidecar | true |
|
|
||||||
| deskew | true |
|
|
||||||
| clean | true |
|
|
||||||
| cleanFinal | true |
|
|
||||||
| ocrType | Force |
|
|
||||||
| ocrRenderType | hocr |
|
|
||||||
| removeImagesAfter| false |
|
|
||||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
|
||||||
Then the response content type should be "application/octet-stream"
|
|
||||||
And the response file should have extension ".zip"
|
|
||||||
And the response ZIP should contain 2 files
|
|
||||||
And the response file should have size greater than 0
|
|
||||||
And the response status code should be 200
|
|
||||||
|
|
||||||
|
|
||||||
@libre @positive
|
@libre @positive
|
||||||
@@ -145,7 +107,7 @@ Feature: API Validation
|
|||||||
And the response file should have extension ".pdf"
|
And the response file should have extension ".pdf"
|
||||||
And the response file should have size greater than 100
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
@compress @ghostscript @positive
|
@compress @qpdf @positive
|
||||||
Scenario: Compress
|
Scenario: Compress
|
||||||
Given I use an example file at "exampleFiles/ghost3.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/ghost3.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
And the request data includes
|
||||||
@@ -156,7 +118,7 @@ Feature: API Validation
|
|||||||
And the response file should have extension ".pdf"
|
And the response file should have extension ".pdf"
|
||||||
And the response file should have size greater than 100
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
@compress @ghostscript @positive
|
@compress @qpdf @positive
|
||||||
Scenario: Compress
|
Scenario: Compress
|
||||||
Given I use an example file at "exampleFiles/ghost2.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/ghost2.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
And the request data includes
|
||||||
@@ -169,7 +131,7 @@ Feature: API Validation
|
|||||||
And the response file should have size greater than 100
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
|
|
||||||
@compress @ghostscript @positive
|
@compress @qpdf @positive
|
||||||
Scenario: Compress
|
Scenario: Compress
|
||||||
Given I use an example file at "exampleFiles/ghost1.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/ghost1.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
And the request data includes
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ import shutil
|
|||||||
import re
|
import re
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
API_HEADERS = {
|
||||||
|
'X-API-KEY': '123456789'
|
||||||
|
}
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# GIVEN #
|
# GIVEN #
|
||||||
#########
|
#########
|
||||||
@@ -227,7 +231,7 @@ def save_generated_pdf(context, filename):
|
|||||||
def step_send_get_request(context, endpoint):
|
def step_send_get_request(context, endpoint):
|
||||||
base_url = "http://localhost:8080"
|
base_url = "http://localhost:8080"
|
||||||
full_url = f"{base_url}{endpoint}"
|
full_url = f"{base_url}{endpoint}"
|
||||||
response = requests.get(full_url)
|
response = requests.get(full_url, headers=API_HEADERS)
|
||||||
context.response = response
|
context.response = response
|
||||||
|
|
||||||
@when('I send a GET request to "{endpoint}" with parameters')
|
@when('I send a GET request to "{endpoint}" with parameters')
|
||||||
@@ -235,7 +239,7 @@ def step_send_get_request_with_params(context, endpoint):
|
|||||||
base_url = "http://localhost:8080"
|
base_url = "http://localhost:8080"
|
||||||
params = {row['parameter']: row['value'] for row in context.table}
|
params = {row['parameter']: row['value'] for row in context.table}
|
||||||
full_url = f"{base_url}{endpoint}"
|
full_url = f"{base_url}{endpoint}"
|
||||||
response = requests.get(full_url, params=params)
|
response = requests.get(full_url, params=params, headers=API_HEADERS)
|
||||||
context.response = response
|
context.response = response
|
||||||
|
|
||||||
@when('I send the API request to the endpoint "{endpoint}"')
|
@when('I send the API request to the endpoint "{endpoint}"')
|
||||||
@@ -256,7 +260,7 @@ def step_send_api_request(context, endpoint):
|
|||||||
print(f"form_data {file.name} with {mime_type}")
|
print(f"form_data {file.name} with {mime_type}")
|
||||||
form_data.append((key, (file.name, file, mime_type)))
|
form_data.append((key, (file.name, file, mime_type)))
|
||||||
|
|
||||||
response = requests.post(url, files=form_data)
|
response = requests.post(url, files=form_data, headers=API_HEADERS)
|
||||||
context.response = response
|
context.response = response
|
||||||
|
|
||||||
########
|
########
|
||||||
|
|||||||
34
exampleYmlFiles/test_cicd.yml
Normal file
34
exampleYmlFiles/test_cicd.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Security-Fat
|
||||||
|
image: stirlingtools/stirling-pdf:latest-fat
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /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-fat with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
|
||||||
|
restart: on-failure:5
|
||||||
@@ -42,14 +42,19 @@ ignore = [
|
|||||||
'addPageNumbers.selectText.3',
|
'addPageNumbers.selectText.3',
|
||||||
'alphabet',
|
'alphabet',
|
||||||
'certSign.name',
|
'certSign.name',
|
||||||
|
'fileChooser.dragAndDrop',
|
||||||
'home.pipeline.title',
|
'home.pipeline.title',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
'legal.impressum',
|
||||||
'licenses.version',
|
'licenses.version',
|
||||||
'pipeline.title',
|
'pipeline.title',
|
||||||
'pipelineOptions.pipelineHeader',
|
'pipelineOptions.pipelineHeader',
|
||||||
'pro',
|
'pro',
|
||||||
'sponsor',
|
'sponsor',
|
||||||
'text',
|
'text',
|
||||||
|
'validateSignature.cert.bits',
|
||||||
|
'validateSignature.cert.version',
|
||||||
|
'validateSignature.status',
|
||||||
'watermark.type.1',
|
'watermark.type.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -61,7 +66,6 @@ ignore = [
|
|||||||
[es_ES]
|
[es_ES]
|
||||||
ignore = [
|
ignore = [
|
||||||
'adminUserSettings.roles',
|
'adminUserSettings.roles',
|
||||||
'color',
|
|
||||||
'error',
|
'error',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'no',
|
'no',
|
||||||
@@ -73,6 +77,11 @@ ignore = [
|
|||||||
'language.direction',
|
'language.direction',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[fa_IR]
|
||||||
|
ignore = [
|
||||||
|
'language.direction',
|
||||||
|
]
|
||||||
|
|
||||||
[fr_FR]
|
[fr_FR]
|
||||||
ignore = [
|
ignore = [
|
||||||
'AddStampRequest.alphabet',
|
'AddStampRequest.alphabet',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
translation_key="pdfToPDFA.credit"
|
translation_key="pdfToPDFA.credit"
|
||||||
old_value="OCRmyPDF"
|
old_value="qpdf"
|
||||||
new_value="ghostscript"
|
new_value="liibreoffice"
|
||||||
|
|
||||||
for file in ../src/main/resources/messages_*.properties; do
|
for file in ../src/main/resources/messages_*.properties; do
|
||||||
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"
|
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package stirling.software.SPDF.EE;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Lazy
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class EEAppConfig {
|
public class EEAppConfig {
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ public class LicenseKeyChecker {
|
|||||||
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
|
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
|
||||||
this.licenseService = licenseService;
|
this.licenseService = licenseService;
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.checkLicense();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds
|
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
|
||||||
public void checkLicensePeriodically() {
|
public void checkLicensePeriodically() {
|
||||||
checkLicense();
|
checkLicense();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -8,6 +9,9 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -19,13 +23,16 @@ import org.springframework.core.env.Environment;
|
|||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import io.github.pixee.security.SystemCommand;
|
import io.github.pixee.security.SystemCommand;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
|
@Slf4j
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||||
@@ -67,36 +74,19 @@ public class SPdfApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
baseUrlStatic = this.baseUrl;
|
|
||||||
// Check if the BROWSER_OPEN environment variable is set to true
|
|
||||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
|
||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
|
||||||
if (browserOpen) {
|
|
||||||
try {
|
|
||||||
String url = baseUrl + ":" + getStaticPort();
|
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
|
||||||
Runtime rt = Runtime.getRuntime();
|
|
||||||
if (os.contains("win")) {
|
|
||||||
// For Windows
|
|
||||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
|
||||||
} else if (os.contains("mac")) {
|
|
||||||
SystemCommand.runCommand(rt, "open " + url);
|
|
||||||
} else if (os.contains("nix") || os.contains("nux")) {
|
|
||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("Running configs {}", applicationProperties.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, InterruptedException {
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
|
||||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
|
System.setProperty("java.awt.headless", "false");
|
||||||
|
app.setHeadless(false);
|
||||||
|
props.put("java.awt.headless", "false");
|
||||||
|
props.put("spring.main.web-application-type", "servlet");
|
||||||
|
}
|
||||||
|
|
||||||
app.setAdditionalProfiles("default");
|
app.setAdditionalProfiles("default");
|
||||||
app.addInitializers(new ConfigInitializer());
|
app.addInitializers(new ConfigInitializer());
|
||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
@@ -120,14 +110,20 @@ public class SPdfApplication {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
||||||
}
|
}
|
||||||
|
Properties finalProps = new Properties();
|
||||||
|
|
||||||
if (!propertyFiles.isEmpty()) {
|
if (!propertyFiles.isEmpty()) {
|
||||||
app.setDefaultProperties(
|
finalProps.putAll(
|
||||||
Collections.singletonMap(
|
Collections.singletonMap(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
propertyFiles.get("spring.config.additional-location")));
|
propertyFiles.get("spring.config.additional-location")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.isEmpty()) {
|
||||||
|
finalProps.putAll(props);
|
||||||
|
}
|
||||||
|
app.setDefaultProperties(finalProps);
|
||||||
|
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
// Ensure directories are created
|
// Ensure directories are created
|
||||||
@@ -147,6 +143,46 @@ public class SPdfApplication {
|
|||||||
logger.info("Navigate to {}", url);
|
logger.info("Navigate to {}", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private WebBrowser webBrowser;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
baseUrlStatic = this.baseUrl;
|
||||||
|
String url = baseUrl + ":" + getStaticPort();
|
||||||
|
if (webBrowser != null
|
||||||
|
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
|
webBrowser.initWebUI(url);
|
||||||
|
} else {
|
||||||
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
|
if (browserOpen) {
|
||||||
|
try {
|
||||||
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
if (os.contains("win")) {
|
||||||
|
// For Windows
|
||||||
|
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
|
} else if (os.contains("mac")) {
|
||||||
|
SystemCommand.runCommand(rt, "open " + url);
|
||||||
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
|
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error opening browser: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("Running configs {}", applicationProperties.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void cleanup() {
|
||||||
|
if (webBrowser != null) {
|
||||||
|
webBrowser.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
public static String getStaticBaseUrl() {
|
||||||
return baseUrlStatic;
|
return baseUrlStatic;
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/main/java/stirling/software/SPDF/UI/WebBrowser.java
Normal file
7
src/main/java/stirling/software/SPDF/UI/WebBrowser.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.UI;
|
||||||
|
|
||||||
|
public interface WebBrowser {
|
||||||
|
void initWebUI(String url);
|
||||||
|
|
||||||
|
void cleanup();
|
||||||
|
}
|
||||||
354
src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java
Normal file
354
src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
package stirling.software.SPDF.UI.impl;
|
||||||
|
|
||||||
|
import java.awt.AWTException;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.MenuItem;
|
||||||
|
import java.awt.PopupMenu;
|
||||||
|
import java.awt.SystemTray;
|
||||||
|
import java.awt.TrayIcon;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.awt.event.WindowStateListener;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.Timer;
|
||||||
|
|
||||||
|
import org.cef.CefApp;
|
||||||
|
import org.cef.CefClient;
|
||||||
|
import org.cef.CefSettings;
|
||||||
|
import org.cef.browser.CefBrowser;
|
||||||
|
import org.cef.callback.CefBeforeDownloadCallback;
|
||||||
|
import org.cef.callback.CefDownloadItem;
|
||||||
|
import org.cef.callback.CefDownloadItemCallback;
|
||||||
|
import org.cef.handler.CefDownloadHandlerAdapter;
|
||||||
|
import org.cef.handler.CefLoadHandlerAdapter;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.friwi.jcefmaven.CefAppBuilder;
|
||||||
|
import me.friwi.jcefmaven.EnumProgress;
|
||||||
|
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
||||||
|
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
||||||
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = "STIRLING_PDF_DESKTOP_UI",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public class DesktopBrowser implements WebBrowser {
|
||||||
|
private static CefApp cefApp;
|
||||||
|
private static CefClient client;
|
||||||
|
private static CefBrowser browser;
|
||||||
|
private static JFrame frame;
|
||||||
|
private static LoadingWindow loadingWindow;
|
||||||
|
private static volatile boolean browserInitialized = false;
|
||||||
|
private static TrayIcon trayIcon;
|
||||||
|
private static SystemTray systemTray;
|
||||||
|
|
||||||
|
public DesktopBrowser() {
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
loadingWindow = new LoadingWindow(null, "Initializing...");
|
||||||
|
loadingWindow.setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initWebUI(String url) {
|
||||||
|
CompletableFuture.runAsync(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
CefAppBuilder builder = new CefAppBuilder();
|
||||||
|
configureCefSettings(builder);
|
||||||
|
builder.setProgressHandler(createProgressHandler());
|
||||||
|
|
||||||
|
// Build and initialize CEF
|
||||||
|
cefApp = builder.build();
|
||||||
|
client = cefApp.createClient();
|
||||||
|
|
||||||
|
// Set up download handler
|
||||||
|
setupDownloadHandler();
|
||||||
|
|
||||||
|
// Create browser and frame on EDT
|
||||||
|
SwingUtilities.invokeAndWait(
|
||||||
|
() -> {
|
||||||
|
browser = client.createBrowser(url, false, false);
|
||||||
|
setupMainFrame();
|
||||||
|
setupLoadHandler();
|
||||||
|
|
||||||
|
// Show the frame immediately but transparent
|
||||||
|
frame.setVisible(true);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error initializing JCEF browser: ", e);
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureCefSettings(CefAppBuilder builder) {
|
||||||
|
CefSettings settings = builder.getCefSettings();
|
||||||
|
settings.cache_path = new File("jcef-bundle").getAbsolutePath();
|
||||||
|
settings.root_cache_path = new File("jcef-bundle").getAbsolutePath();
|
||||||
|
settings.persist_session_cookies = true;
|
||||||
|
settings.windowless_rendering_enabled = false;
|
||||||
|
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
|
||||||
|
|
||||||
|
builder.setAppHandler(
|
||||||
|
new MavenCefAppHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
|
||||||
|
log.info("CEF state changed: " + state);
|
||||||
|
if (state == CefApp.CefAppState.TERMINATED) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDownloadHandler() {
|
||||||
|
client.addDownloadHandler(
|
||||||
|
new CefDownloadHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public boolean onBeforeDownload(
|
||||||
|
CefBrowser browser,
|
||||||
|
CefDownloadItem downloadItem,
|
||||||
|
String suggestedName,
|
||||||
|
CefBeforeDownloadCallback callback) {
|
||||||
|
callback.Continue("", true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownloadUpdated(
|
||||||
|
CefBrowser browser,
|
||||||
|
CefDownloadItem downloadItem,
|
||||||
|
CefDownloadItemCallback callback) {
|
||||||
|
if (downloadItem.isComplete()) {
|
||||||
|
log.info("Download completed: " + downloadItem.getFullPath());
|
||||||
|
} else if (downloadItem.isCanceled()) {
|
||||||
|
log.info("Download canceled: " + downloadItem.getFullPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsoleProgressHandler createProgressHandler() {
|
||||||
|
return new ConsoleProgressHandler() {
|
||||||
|
@Override
|
||||||
|
public void handleProgress(EnumProgress state, float percent) {
|
||||||
|
Objects.requireNonNull(state, "state cannot be null");
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
if (loadingWindow != null) {
|
||||||
|
switch (state) {
|
||||||
|
case LOCATING:
|
||||||
|
loadingWindow.setStatus("Locating Files...");
|
||||||
|
loadingWindow.setProgress(0);
|
||||||
|
break;
|
||||||
|
case DOWNLOADING:
|
||||||
|
if (percent >= 0) {
|
||||||
|
loadingWindow.setStatus(
|
||||||
|
String.format(
|
||||||
|
"Downloading additional files: %.0f%%",
|
||||||
|
percent));
|
||||||
|
loadingWindow.setProgress((int) percent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EXTRACTING:
|
||||||
|
loadingWindow.setStatus("Extracting files...");
|
||||||
|
loadingWindow.setProgress(60);
|
||||||
|
break;
|
||||||
|
case INITIALIZING:
|
||||||
|
loadingWindow.setStatus("Initializing UI...");
|
||||||
|
loadingWindow.setProgress(80);
|
||||||
|
break;
|
||||||
|
case INITIALIZED:
|
||||||
|
loadingWindow.setStatus("Finalising startup...");
|
||||||
|
loadingWindow.setProgress(90);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMainFrame() {
|
||||||
|
frame = new JFrame("Stirling-PDF");
|
||||||
|
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
|
frame.setUndecorated(true);
|
||||||
|
frame.setOpacity(0.0f);
|
||||||
|
|
||||||
|
JPanel contentPane = new JPanel(new BorderLayout());
|
||||||
|
contentPane.setDoubleBuffered(true);
|
||||||
|
contentPane.add(browser.getUIComponent(), BorderLayout.CENTER);
|
||||||
|
frame.setContentPane(contentPane);
|
||||||
|
|
||||||
|
frame.addWindowListener(
|
||||||
|
new java.awt.event.WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
|
||||||
|
cleanup();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.setSize(1280, 768);
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
loadIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLoadHandler() {
|
||||||
|
client.addLoadHandler(
|
||||||
|
new CefLoadHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onLoadingStateChange(
|
||||||
|
CefBrowser browser,
|
||||||
|
boolean isLoading,
|
||||||
|
boolean canGoBack,
|
||||||
|
boolean canGoForward) {
|
||||||
|
if (!isLoading && !browserInitialized) {
|
||||||
|
browserInitialized = true;
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
if (loadingWindow != null) {
|
||||||
|
Timer timer =
|
||||||
|
new Timer(
|
||||||
|
500,
|
||||||
|
e -> {
|
||||||
|
loadingWindow.dispose();
|
||||||
|
loadingWindow = null;
|
||||||
|
|
||||||
|
frame.dispose();
|
||||||
|
frame.setOpacity(1.0f);
|
||||||
|
frame.setUndecorated(false);
|
||||||
|
frame.pack();
|
||||||
|
frame.setSize(1280, 800);
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.requestFocus();
|
||||||
|
frame.toFront();
|
||||||
|
browser.getUIComponent()
|
||||||
|
.requestFocus();
|
||||||
|
});
|
||||||
|
timer.setRepeats(false);
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTrayIcon(Image icon) {
|
||||||
|
if (!SystemTray.isSupported()) {
|
||||||
|
log.warn("System tray is not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
systemTray = SystemTray.getSystemTray();
|
||||||
|
|
||||||
|
// Create popup menu
|
||||||
|
PopupMenu popup = new PopupMenu();
|
||||||
|
|
||||||
|
// Create menu items
|
||||||
|
MenuItem showItem = new MenuItem("Show");
|
||||||
|
showItem.addActionListener(
|
||||||
|
e -> {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.setState(Frame.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuItem exitItem = new MenuItem("Exit");
|
||||||
|
exitItem.addActionListener(
|
||||||
|
e -> {
|
||||||
|
cleanup();
|
||||||
|
System.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add menu items to popup menu
|
||||||
|
popup.add(showItem);
|
||||||
|
popup.addSeparator();
|
||||||
|
popup.add(exitItem);
|
||||||
|
|
||||||
|
// Create tray icon
|
||||||
|
trayIcon = new TrayIcon(icon, "Stirling-PDF", popup);
|
||||||
|
trayIcon.setImageAutoSize(true);
|
||||||
|
|
||||||
|
// Add double-click behavior
|
||||||
|
trayIcon.addActionListener(
|
||||||
|
e -> {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.setState(Frame.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add tray icon to system tray
|
||||||
|
systemTray.add(trayIcon);
|
||||||
|
|
||||||
|
// Modify frame behavior to minimize to tray
|
||||||
|
frame.addWindowStateListener(
|
||||||
|
new WindowStateListener() {
|
||||||
|
public void windowStateChanged(WindowEvent e) {
|
||||||
|
if (e.getNewState() == Frame.ICONIFIED) {
|
||||||
|
frame.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (AWTException e) {
|
||||||
|
log.error("Error setting up system tray icon", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadIcon() {
|
||||||
|
try {
|
||||||
|
Image icon = null;
|
||||||
|
String[] iconPaths = {"/static/favicon.ico"};
|
||||||
|
|
||||||
|
for (String path : iconPaths) {
|
||||||
|
if (icon != null) break;
|
||||||
|
try {
|
||||||
|
try (InputStream is = getClass().getResourceAsStream(path)) {
|
||||||
|
if (is != null) {
|
||||||
|
icon = ImageIO.read(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not load icon from " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
frame.setIconImage(icon);
|
||||||
|
setupTrayIcon(icon);
|
||||||
|
} else {
|
||||||
|
log.warn("Could not load icon from any source");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error loading icon", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void cleanup() {
|
||||||
|
if (browser != null) browser.close(true);
|
||||||
|
if (client != null) client.dispose();
|
||||||
|
if (cefApp != null) cefApp.dispose();
|
||||||
|
if (loadingWindow != null) loadingWindow.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java
Normal file
114
src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package stirling.software.SPDF.UI.impl;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class LoadingWindow extends JDialog {
|
||||||
|
private final JProgressBar progressBar;
|
||||||
|
private final JLabel statusLabel;
|
||||||
|
private final JPanel mainPanel;
|
||||||
|
private final JLabel brandLabel;
|
||||||
|
|
||||||
|
public LoadingWindow(Frame parent, String initialUrl) {
|
||||||
|
super(parent, "Initializing Stirling-PDF", true);
|
||||||
|
|
||||||
|
// Initialize components
|
||||||
|
mainPanel = new JPanel();
|
||||||
|
mainPanel.setBackground(Color.WHITE);
|
||||||
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));
|
||||||
|
mainPanel.setLayout(new GridBagLayout());
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
|
||||||
|
// Configure GridBagConstraints
|
||||||
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.weightx = 1.0; // Add horizontal weight
|
||||||
|
gbc.weighty = 0.0; // Add vertical weight
|
||||||
|
|
||||||
|
// Add icon
|
||||||
|
try {
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
|
||||||
|
if (is != null) {
|
||||||
|
Image img = ImageIO.read(is);
|
||||||
|
if (img != null) {
|
||||||
|
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
|
||||||
|
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
||||||
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
gbc.gridy = 0;
|
||||||
|
mainPanel.add(iconLabel, gbc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load icon", e);
|
||||||
|
}
|
||||||
|
// URL Label with explicit size
|
||||||
|
brandLabel = new JLabel(initialUrl);
|
||||||
|
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
brandLabel.setPreferredSize(new Dimension(300, 25));
|
||||||
|
brandLabel.setText("Stirling-PDF");
|
||||||
|
gbc.gridy = 1;
|
||||||
|
mainPanel.add(brandLabel, gbc);
|
||||||
|
|
||||||
|
// Status label with explicit size
|
||||||
|
statusLabel = new JLabel("Initializing...");
|
||||||
|
statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
statusLabel.setPreferredSize(new Dimension(300, 25));
|
||||||
|
gbc.gridy = 2;
|
||||||
|
mainPanel.add(statusLabel, gbc);
|
||||||
|
// Progress bar with explicit size
|
||||||
|
progressBar = new JProgressBar(0, 100);
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setPreferredSize(new Dimension(300, 25));
|
||||||
|
gbc.gridy = 3;
|
||||||
|
mainPanel.add(progressBar, gbc);
|
||||||
|
|
||||||
|
// Set dialog properties
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||||
|
setResizable(false);
|
||||||
|
setUndecorated(false);
|
||||||
|
|
||||||
|
// Set size and position
|
||||||
|
setSize(400, 200);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
setAlwaysOnTop(true);
|
||||||
|
setProgress(0);
|
||||||
|
setStatus("Starting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(final int progress) {
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
progressBar.setValue(Math.min(Math.max(progress, 0), 100));
|
||||||
|
progressBar.setString(progress + "%");
|
||||||
|
mainPanel.revalidate();
|
||||||
|
mainPanel.repaint();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error updating progress", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(final String status) {
|
||||||
|
log.info(status);
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
statusLabel.setText(status != null ? status : "");
|
||||||
|
mainPanel.revalidate();
|
||||||
|
mainPanel.repaint();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error updating status", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,7 +188,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("OpenCV", "extract-image-scans");
|
addEndpointToGroup("OpenCV", "extract-image-scans");
|
||||||
|
|
||||||
// LibreOffice
|
// LibreOffice
|
||||||
addEndpointToGroup("LibreOffice", "repair");
|
addEndpointToGroup("qpdf", "repair");
|
||||||
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||||
@@ -199,10 +199,11 @@ public class EndpointConfiguration {
|
|||||||
// Unoconv
|
// Unoconv
|
||||||
addEndpointToGroup("Unoconv", "file-to-pdf");
|
addEndpointToGroup("Unoconv", "file-to-pdf");
|
||||||
|
|
||||||
// OCRmyPDF
|
// qpdf
|
||||||
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
addEndpointToGroup("qpdf", "compress-pdf");
|
||||||
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
addEndpointToGroup("qpdf", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
|
|
||||||
|
addEndpointToGroup("tesseract", "ocr-pdf");
|
||||||
|
|
||||||
// Java
|
// Java
|
||||||
addEndpointToGroup("Java", "merge-pdfs");
|
addEndpointToGroup("Java", "merge-pdfs");
|
||||||
@@ -248,10 +249,10 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Javascript", "compare");
|
addEndpointToGroup("Javascript", "compare");
|
||||||
addEndpointToGroup("Javascript", "adjust-contrast");
|
addEndpointToGroup("Javascript", "adjust-contrast");
|
||||||
|
|
||||||
// Ghostscript dependent endpoints
|
// qpdf dependent endpoints
|
||||||
addEndpointToGroup("Ghostscript", "compress-pdf");
|
addEndpointToGroup("qpdf", "compress-pdf");
|
||||||
addEndpointToGroup("Ghostscript", "pdf-to-pdfa");
|
addEndpointToGroup("qpdf", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("Ghostscript", "repair");
|
addEndpointToGroup("qpdf", "repair");
|
||||||
|
|
||||||
// Weasyprint dependent endpoints
|
// Weasyprint dependent endpoints
|
||||||
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
||||||
@@ -259,6 +260,9 @@ public class EndpointConfiguration {
|
|||||||
|
|
||||||
// Pdftohtml dependent endpoints
|
// Pdftohtml dependent endpoints
|
||||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||||
|
|
||||||
|
// disabled for now while we resolve issues
|
||||||
|
disableEndpoint("pdf-to-pdfa");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ public class ExternalAppDepConfig {
|
|||||||
private final Map<String, List<String>> commandToGroupMapping =
|
private final Map<String, List<String>> commandToGroupMapping =
|
||||||
new HashMap<>() {
|
new HashMap<>() {
|
||||||
{
|
{
|
||||||
put("gs", List.of("Ghostscript"));
|
|
||||||
put("soffice", List.of("LibreOffice"));
|
put("soffice", List.of("LibreOffice"));
|
||||||
put("ocrmypdf", List.of("OCRmyPDF"));
|
|
||||||
put("weasyprint", List.of("Weasyprint"));
|
put("weasyprint", List.of("Weasyprint"));
|
||||||
put("pdftohtml", List.of("Pdftohtml"));
|
put("pdftohtml", List.of("Pdftohtml"));
|
||||||
put("unoconv", List.of("Unoconv"));
|
put("unoconv", List.of("Unoconv"));
|
||||||
|
put("qpdf", List.of("qpdf"));
|
||||||
|
put("tesseract", List.of("tesseract"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ public class ExternalAppDepConfig {
|
|||||||
public void checkDependencies() {
|
public void checkDependencies() {
|
||||||
|
|
||||||
// Check core dependencies
|
// Check core dependencies
|
||||||
checkDependencyAndDisableGroup("gs");
|
checkDependencyAndDisableGroup("tesseract");
|
||||||
checkDependencyAndDisableGroup("soffice");
|
checkDependencyAndDisableGroup("soffice");
|
||||||
checkDependencyAndDisableGroup("ocrmypdf");
|
checkDependencyAndDisableGroup("qpdf");
|
||||||
checkDependencyAndDisableGroup("weasyprint");
|
checkDependencyAndDisableGroup("weasyprint");
|
||||||
checkDependencyAndDisableGroup("pdftohtml");
|
checkDependencyAndDisableGroup("pdftohtml");
|
||||||
checkDependencyAndDisableGroup("unoconv");
|
checkDependencyAndDisableGroup("unoconv");
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import io.micrometer.common.util.StringUtils;
|
import io.micrometer.common.util.StringUtils;
|
||||||
@@ -23,6 +26,18 @@ public class InitialSetup {
|
|||||||
@Autowired private ApplicationProperties applicationProperties;
|
@Autowired private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
|
public void init() throws IOException {
|
||||||
|
initUUIDKey();
|
||||||
|
|
||||||
|
initSecretKey();
|
||||||
|
|
||||||
|
initEnableCSRFSecurity();
|
||||||
|
|
||||||
|
initLegalUrls();
|
||||||
|
|
||||||
|
initSetAppVersion();
|
||||||
|
}
|
||||||
|
|
||||||
public void initUUIDKey() throws IOException {
|
public void initUUIDKey() throws IOException {
|
||||||
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||||
@@ -32,7 +47,6 @@ public class InitialSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initSecretKey() throws IOException {
|
public void initSecretKey() throws IOException {
|
||||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||||
@@ -42,13 +56,24 @@ public class InitialSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
public void initEnableCSRFSecurity() throws IOException {
|
||||||
|
if (GeneralUtils.isVersionHigher(
|
||||||
|
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
||||||
|
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
||||||
|
if (!csrf) {
|
||||||
|
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
|
||||||
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
|
||||||
|
applicationProperties.getSecurity().setCsrfDisabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void initLegalUrls() throws IOException {
|
public void initLegalUrls() throws IOException {
|
||||||
// Initialize Terms and Conditions
|
// Initialize Terms and Conditions
|
||||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
||||||
if (StringUtils.isEmpty(termsUrl)) {
|
if (StringUtils.isEmpty(termsUrl)) {
|
||||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
||||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
|
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
|
||||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +81,23 @@ public class InitialSetup {
|
|||||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
||||||
if (StringUtils.isEmpty(privacyUrl)) {
|
if (StringUtils.isEmpty(privacyUrl)) {
|
||||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
||||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
|
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
|
||||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initSetAppVersion() throws IOException {
|
||||||
|
|
||||||
|
String appVersion = "0.0.0";
|
||||||
|
Resource resource = new ClassPathResource("version.properties");
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
props.load(resource.getInputStream());
|
||||||
|
appVersion = props.getProperty("version");
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
||||||
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class InitialSecuritySetup {
|
|||||||
initializeAdminUser();
|
initializeAdminUser();
|
||||||
} else {
|
} else {
|
||||||
databaseBackupHelper.exportDatabase();
|
databaseBackupHelper.exportDatabase();
|
||||||
|
userService.migrateOauth2ToSSO();
|
||||||
}
|
}
|
||||||
initializeInternalApiUser();
|
initializeInternalApiUser();
|
||||||
}
|
}
|
||||||
@@ -74,5 +75,6 @@ public class InitialSecuritySetup {
|
|||||||
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||||
}
|
}
|
||||||
|
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ package stirling.software.SPDF.config.security;
|
|||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
@@ -32,7 +34,8 @@ import org.springframework.security.saml2.provider.service.authentication.OpenSa
|
|||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||||
|
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
@@ -41,6 +44,7 @@ import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
|||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
@@ -64,6 +68,7 @@ import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
|||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@DependsOn("runningEE")
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@Autowired private CustomUserDetailsService userDetailsService;
|
||||||
@@ -79,6 +84,10 @@ public class SecurityConfiguration {
|
|||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled")
|
||||||
public boolean loginEnabledValue;
|
public boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("runningEE")
|
||||||
|
public boolean runningEE;
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
||||||
@@ -90,13 +99,14 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
||||||
|
http.csrf(csrf -> csrf.disable());
|
||||||
|
}
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
http.addFilterBefore(
|
http.addFilterBefore(
|
||||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
|
||||||
} else {
|
|
||||||
CookieCsrfTokenRepository cookieRepo =
|
CookieCsrfTokenRepository cookieRepo =
|
||||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||||
CsrfTokenRequestAttributeHandler requestHandler =
|
CsrfTokenRequestAttributeHandler requestHandler =
|
||||||
@@ -106,7 +116,7 @@ public class SecurityConfiguration {
|
|||||||
csrf ->
|
csrf ->
|
||||||
csrf.ignoringRequestMatchers(
|
csrf.ignoringRequestMatchers(
|
||||||
request -> {
|
request -> {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
|
|
||||||
// If there's no API key, don't ignore CSRF
|
// If there's no API key, don't ignore CSRF
|
||||||
// (return false)
|
// (return false)
|
||||||
@@ -245,12 +255,22 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle SAML
|
// Handle SAML
|
||||||
if (applicationProperties.getSecurity().isSaml2Activ()
|
if (applicationProperties.getSecurity().isSaml2Activ()) { // && runningEE
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
// Configure the authentication provider
|
||||||
http.authenticationProvider(samlAuthenticationProvider());
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
http.saml2Login(
|
new OpenSaml4AuthenticationProvider();
|
||||||
saml2 ->
|
authenticationProvider.setResponseAuthenticationConverter(
|
||||||
|
new CustomSaml2ResponseAuthenticationConverter(userService));
|
||||||
|
|
||||||
|
http.authenticationProvider(authenticationProvider)
|
||||||
|
.saml2Login(
|
||||||
|
saml2 -> {
|
||||||
|
try {
|
||||||
saml2.loginPage("/saml2")
|
saml2.loginPage("/saml2")
|
||||||
|
.relyingPartyRegistrationRepository(
|
||||||
|
relyingPartyRegistrations())
|
||||||
|
.authenticationManager(
|
||||||
|
new ProviderManager(authenticationProvider))
|
||||||
.successHandler(
|
.successHandler(
|
||||||
new CustomSaml2AuthenticationSuccessHandler(
|
new CustomSaml2AuthenticationSuccessHandler(
|
||||||
loginAttemptService,
|
loginAttemptService,
|
||||||
@@ -258,44 +278,34 @@ public class SecurityConfiguration {
|
|||||||
userService))
|
userService))
|
||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomSaml2AuthenticationFailureHandler())
|
new CustomSaml2AuthenticationFailureHandler())
|
||||||
.permitAll())
|
.authenticationRequestResolver(
|
||||||
.addFilterBefore(
|
authenticationRequestResolver(
|
||||||
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
relyingPartyRegistrations()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error configuring SAML2 login", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
// CookieCsrfTokenRepository cookieRepo =
|
||||||
} else {
|
// CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||||
CookieCsrfTokenRepository cookieRepo =
|
// CsrfTokenRequestAttributeHandler requestHandler =
|
||||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
// new CsrfTokenRequestAttributeHandler();
|
||||||
CsrfTokenRequestAttributeHandler requestHandler =
|
// requestHandler.setCsrfRequestAttributeName(null);
|
||||||
new CsrfTokenRequestAttributeHandler();
|
// http.csrf(
|
||||||
requestHandler.setCsrfRequestAttributeName(null);
|
// csrf ->
|
||||||
http.csrf(
|
// csrf.csrfTokenRepository(cookieRepo)
|
||||||
csrf ->
|
// .csrfTokenRequestHandler(requestHandler));
|
||||||
csrf.csrfTokenRepository(cookieRepo)
|
// }
|
||||||
.csrfTokenRequestHandler(requestHandler));
|
|
||||||
}
|
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
name = "security.saml2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public AuthenticationProvider samlAuthenticationProvider() {
|
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
|
||||||
new OpenSaml4AuthenticationProvider();
|
|
||||||
authenticationProvider.setResponseAuthenticationConverter(
|
|
||||||
new CustomSaml2ResponseAuthenticationConverter(userService));
|
|
||||||
return authenticationProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client Registration Repository for OAUTH2 OIDC Login
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(
|
||||||
value = "security.oauth2.enabled",
|
value = "security.oauth2.enabled",
|
||||||
@@ -432,11 +442,12 @@ public class SecurityConfiguration {
|
|||||||
havingValue = "true",
|
havingValue = "true",
|
||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
||||||
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
||||||
|
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
||||||
|
|
||||||
|
Resource privateKeyResource = samlConf.getPrivateKey();
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
Resource certificateResource = samlConf.getSpCert();
|
||||||
|
|
||||||
Saml2X509Credential signingCredential =
|
Saml2X509Credential signingCredential =
|
||||||
@@ -445,26 +456,97 @@ public class SecurityConfiguration {
|
|||||||
CertificateUtils.readCertificate(certificateResource),
|
CertificateUtils.readCertificate(certificateResource),
|
||||||
Saml2X509CredentialType.SIGNING);
|
Saml2X509CredentialType.SIGNING);
|
||||||
|
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
|
||||||
|
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
|
||||||
|
|
||||||
RelyingPartyRegistration rp =
|
RelyingPartyRegistration rp =
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
||||||
.signingX509Credentials((c) -> c.add(signingCredential))
|
.signingX509Credentials(c -> c.add(signingCredential))
|
||||||
.assertingPartyMetadata(
|
.assertingPartyMetadata(
|
||||||
(details) ->
|
metadata ->
|
||||||
details.entityId(samlConf.getIdpIssuer())
|
metadata.entityId(samlConf.getIdpIssuer())
|
||||||
.singleSignOnServiceLocation(
|
.singleSignOnServiceLocation(
|
||||||
samlConf.getIdpSingleLoginUrl())
|
samlConf.getIdpSingleLoginUrl())
|
||||||
.verificationX509Credentials(
|
.verificationX509Credentials(
|
||||||
(c) -> c.add(verificationCredential))
|
c -> c.add(verificationCredential))
|
||||||
|
.singleSignOnServiceBinding(
|
||||||
|
Saml2MessageBinding.POST)
|
||||||
.wantAuthnRequestsSigned(true))
|
.wantAuthnRequestsSigned(true))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = "security.saml2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
||||||
|
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||||
|
OpenSaml4AuthenticationRequestResolver resolver =
|
||||||
|
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
||||||
|
resolver.setAuthnRequestCustomizer(
|
||||||
|
customizer -> {
|
||||||
|
log.debug("Customizing SAML Authentication request");
|
||||||
|
|
||||||
|
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||||
|
log.debug("AuthnRequest ID: {}", authnRequest.getID());
|
||||||
|
|
||||||
|
if (authnRequest.getID() == null) {
|
||||||
|
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
|
||||||
|
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
|
||||||
|
log.debug(
|
||||||
|
"AuthnRequest Issuer: {}",
|
||||||
|
authnRequest.getIssuer() != null
|
||||||
|
? authnRequest.getIssuer().getValue()
|
||||||
|
: "null");
|
||||||
|
|
||||||
|
HttpServletRequest request = customizer.getRequest();
|
||||||
|
|
||||||
|
// Log HTTP request details
|
||||||
|
log.debug("HTTP Request Method: {}", request.getMethod());
|
||||||
|
log.debug("Request URI: {}", request.getRequestURI());
|
||||||
|
log.debug("Request URL: {}", request.getRequestURL().toString());
|
||||||
|
log.debug("Query String: {}", request.getQueryString());
|
||||||
|
log.debug("Remote Address: {}", request.getRemoteAddr());
|
||||||
|
|
||||||
|
// Log headers
|
||||||
|
Collections.list(request.getHeaderNames())
|
||||||
|
.forEach(
|
||||||
|
headerName -> {
|
||||||
|
log.debug(
|
||||||
|
"Header - {}: {}",
|
||||||
|
headerName,
|
||||||
|
request.getHeader(headerName));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log SAML specific parameters
|
||||||
|
log.debug("SAML Request Parameters:");
|
||||||
|
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
|
||||||
|
log.debug("RelayState: {}", request.getParameter("RelayState"));
|
||||||
|
|
||||||
|
// Log session debugrmation if exists
|
||||||
|
if (request.getSession(false) != null) {
|
||||||
|
log.debug("Session ID: {}", request.getSession().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log any assertions consumer service details if present
|
||||||
|
if (authnRequest.getAssertionConsumerServiceURL() != null) {
|
||||||
|
log.debug(
|
||||||
|
"AssertionConsumerServiceURL: {}",
|
||||||
|
authnRequest.getAssertionConsumerServiceURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log NameID policy if present
|
||||||
|
if (authnRequest.getNameIDPolicy() != null) {
|
||||||
|
log.debug(
|
||||||
|
"NameIDPolicy Format: {}",
|
||||||
|
authnRequest.getNameIDPolicy().getFormat());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
|
||||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
provider.setUserDetailsService(userDetailsService);
|
provider.setUserDetailsService(userDetailsService);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// Check for API key in the request headers if no authentication exists
|
// Check for API key in the request headers if no authentication exists
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
// Use API key to authenticate. This requires you to have an authentication
|
// Use API key to authenticate. This requires you to have an authentication
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
String identifier = null;
|
String identifier = null;
|
||||||
|
|
||||||
// Check for API key in the request headers
|
// Check for API key in the request headers
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
identifier =
|
identifier =
|
||||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||||
@@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
Role userRole =
|
Role userRole =
|
||||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
if (request.getHeader("X-API-Key") != null) {
|
if (request.getHeader("X-API-KEY") != null) {
|
||||||
// It's an API call
|
// It's an API call
|
||||||
processRequest(
|
processRequest(
|
||||||
userRole.getApiCallsPerDay(),
|
userRole.getApiCallsPerDay(),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
@@ -50,8 +51,19 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void migrateOauth2ToSSO() {
|
||||||
|
userRepository
|
||||||
|
.findByAuthenticationTypeIgnoreCase("OAUTH2")
|
||||||
|
.forEach(
|
||||||
|
user -> {
|
||||||
|
user.setAuthenticationType(AuthenticationType.SSO);
|
||||||
|
userRepository.save(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, IOException {
|
||||||
if (!isUsernameValid(username)) {
|
if (!isUsernameValid(username)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -61,7 +73,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (autoCreateUser) {
|
if (autoCreateUser) {
|
||||||
saveUser(username, AuthenticationType.OAUTH2);
|
saveUser(username, AuthenticationType.SSO);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -378,6 +390,37 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void syncCustomApiUser(String customApiKey) throws IOException {
|
||||||
|
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String username = "CUSTOM_API_USER";
|
||||||
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
|
|
||||||
|
if (!existingUser.isPresent()) {
|
||||||
|
// Create new user with API role
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setPassword(UUID.randomUUID().toString());
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstLogin(false);
|
||||||
|
user.setAuthenticationType(AuthenticationType.WEB);
|
||||||
|
user.setApiKey(customApiKey);
|
||||||
|
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||||
|
userRepository.save(user);
|
||||||
|
databaseBackupHelper.exportDatabase();
|
||||||
|
} else {
|
||||||
|
// Update API key if it has changed
|
||||||
|
User user = existingUser.get();
|
||||||
|
if (!customApiKey.equals(user.getApiKey())) {
|
||||||
|
user.setApiKey(customApiKey);
|
||||||
|
userRepository.save(user);
|
||||||
|
databaseBackupHelper.exportDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTotalUsersCount() {
|
public long getTotalUsersCount() {
|
||||||
return userRepository.count();
|
return userRepository.count();
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
}
|
}
|
||||||
if (userService.usernameExistsIgnoreCase(username)
|
if (userService.usernameExistsIgnoreCase(username)
|
||||||
&& userService.hasPassword(username)
|
&& userService.hasPassword(username)
|
||||||
&& !userService.isAuthenticationTypeByUsername(
|
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
|
||||||
username, AuthenticationType.OAUTH2)
|
|
||||||
&& oAuth.getAutoCreateUser()) {
|
&& oAuth.getAutoCreateUser()) {
|
||||||
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
return;
|
return;
|
||||||
@@ -95,7 +94,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (principal instanceof OAuth2User) {
|
if (principal instanceof OAuth2User) {
|
||||||
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
|
||||||
}
|
}
|
||||||
response.sendRedirect(contextPath + "/");
|
response.sendRedirect(contextPath + "/");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package stirling.software.SPDF.config.security.saml2;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
|
import org.bouncycastle.openssl.PEMKeyPair;
|
||||||
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@@ -28,15 +30,26 @@ public class CertificateUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
|
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
|
||||||
try (PemReader pemReader =
|
try (PEMParser pemParser =
|
||||||
new PemReader(
|
new PEMParser(
|
||||||
new InputStreamReader(
|
new InputStreamReader(
|
||||||
privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
|
privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
PemObject pemObject = pemReader.readPemObject();
|
|
||||||
byte[] decodedKey = pemObject.getContent();
|
Object object = pemParser.readObject();
|
||||||
return (RSAPrivateKey)
|
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
|
||||||
KeyFactory.getInstance("RSA")
|
|
||||||
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
|
if (object instanceof PEMKeyPair) {
|
||||||
|
// Handle traditional RSA private key format
|
||||||
|
PEMKeyPair keypair = (PEMKeyPair) object;
|
||||||
|
return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());
|
||||||
|
} else if (object instanceof PrivateKeyInfo) {
|
||||||
|
// Handle PKCS#8 format
|
||||||
|
return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported key format: "
|
||||||
|
+ (object != null ? object.getClass().getName() : "null"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@@ -20,11 +21,11 @@ import stirling.software.SPDF.model.AuthenticationType;
|
|||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class CustomSaml2AuthenticationSuccessHandler
|
public class CustomSaml2AuthenticationSuccessHandler
|
||||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
private LoginAttemptService loginAttemptService;
|
private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
private ApplicationProperties applicationProperties;
|
private ApplicationProperties applicationProperties;
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
@@ -34,10 +35,12 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
|
log.debug("Starting SAML2 authentication success handling");
|
||||||
|
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
||||||
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
||||||
// Get the saved request
|
log.debug("Authenticated principal found for user: {}", username);
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
SavedRequest savedRequest =
|
SavedRequest savedRequest =
|
||||||
@@ -45,46 +48,77 @@ public class CustomSaml2AuthenticationSuccessHandler
|
|||||||
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Session exists: {}, Saved request exists: {}",
|
||||||
|
session != null,
|
||||||
|
savedRequest != null);
|
||||||
|
|
||||||
if (savedRequest != null
|
if (savedRequest != null
|
||||||
&& !RequestUriUtils.isStaticResource(
|
&& !RequestUriUtils.isStaticResource(
|
||||||
contextPath, savedRequest.getRedirectUrl())) {
|
contextPath, savedRequest.getRedirectUrl())) {
|
||||||
// Redirect to the original destination
|
log.debug(
|
||||||
|
"Valid saved request found, redirecting to original destination: {}",
|
||||||
|
savedRequest.getRedirectUrl());
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
} else {
|
} else {
|
||||||
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
|
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
|
||||||
|
log.debug(
|
||||||
|
"Processing SAML2 authentication with autoCreateUser: {}",
|
||||||
|
saml2.getAutoCreateUser());
|
||||||
|
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
|
log.debug("User {} is blocked due to too many login attempts", username);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
||||||
}
|
}
|
||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
if (userService.usernameExistsIgnoreCase(username)
|
|
||||||
&& userService.hasPassword(username)
|
boolean userExists = userService.usernameExistsIgnoreCase(username);
|
||||||
&& !userService.isAuthenticationTypeByUsername(
|
boolean hasPassword = userExists && userService.hasPassword(username);
|
||||||
username, AuthenticationType.OAUTH2)
|
boolean isSSOUser =
|
||||||
&& saml2.getAutoCreateUser()) {
|
userExists
|
||||||
|
&& userService.isAuthenticationTypeByUsername(
|
||||||
|
username, AuthenticationType.SSO);
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"User status - Exists: {}, Has password: {}, Is SSO user: {}",
|
||||||
|
userExists,
|
||||||
|
hasPassword,
|
||||||
|
isSSOUser);
|
||||||
|
|
||||||
|
if (userExists && hasPassword && !isSSOUser && saml2.getAutoCreateUser()) {
|
||||||
|
log.debug(
|
||||||
|
"User {} exists with password but is not SSO user, redirecting to logout",
|
||||||
|
username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (saml2.getBlockRegistration()
|
if (saml2.getBlockRegistration() && !userExists) {
|
||||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
log.debug("Registration blocked for new user: {}", username);
|
||||||
response.sendRedirect(
|
response.sendRedirect(
|
||||||
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser());
|
log.debug("Processing SSO post-login for user: {}", username);
|
||||||
|
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
|
||||||
|
log.debug("Successfully processed authentication for user: {}", username);
|
||||||
response.sendRedirect(contextPath + "/");
|
response.sendRedirect(contextPath + "/");
|
||||||
return;
|
return;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.debug(
|
||||||
|
"Invalid username detected for user: {}, redirecting to logout",
|
||||||
|
username);
|
||||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
log.debug("Non-SAML2 principal detected, delegating to parent handler");
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package stirling.software.SPDF.config.security.saml2;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.opensaml.core.xml.XMLObject;
|
import org.opensaml.core.xml.XMLObject;
|
||||||
import org.opensaml.core.xml.schema.XSBoolean;
|
|
||||||
import org.opensaml.core.xml.schema.XSString;
|
|
||||||
import org.opensaml.saml.saml2.core.Assertion;
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
import org.opensaml.saml.saml2.core.Attribute;
|
import org.opensaml.saml.saml2.core.Attribute;
|
||||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
import org.opensaml.saml.saml2.core.AttributeStatement;
|
||||||
@@ -30,15 +28,60 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
|
||||||
|
Map<String, List<Object>> attributes = new HashMap<>();
|
||||||
|
|
||||||
|
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
|
||||||
|
for (Attribute attribute : attributeStatement.getAttributes()) {
|
||||||
|
String attributeName = attribute.getName();
|
||||||
|
List<Object> values = new ArrayList<>();
|
||||||
|
|
||||||
|
for (XMLObject xmlObject : attribute.getAttributeValues()) {
|
||||||
|
// Get the text content directly
|
||||||
|
String value = xmlObject.getDOM().getTextContent();
|
||||||
|
if (value != null && !value.trim().isEmpty()) {
|
||||||
|
values.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.isEmpty()) {
|
||||||
|
// Store with both full URI and last part of the URI
|
||||||
|
attributes.put(attributeName, values);
|
||||||
|
String shortName = attributeName.substring(attributeName.lastIndexOf('/') + 1);
|
||||||
|
attributes.put(shortName, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Saml2Authentication convert(ResponseToken responseToken) {
|
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||||
// Extract the assertion from the response
|
|
||||||
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
|
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
|
||||||
|
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
||||||
|
|
||||||
// Extract the NameID
|
// Debug log with actual values
|
||||||
String nameId = assertion.getSubject().getNameID().getValue();
|
log.debug("Extracted SAML Attributes: " + attributes);
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(nameId);
|
// Try to get username/identifier in order of preference
|
||||||
|
String userIdentifier = null;
|
||||||
|
if (hasAttribute(attributes, "username")) {
|
||||||
|
userIdentifier = getFirstAttributeValue(attributes, "username");
|
||||||
|
} else if (hasAttribute(attributes, "emailaddress")) {
|
||||||
|
userIdentifier = getFirstAttributeValue(attributes, "emailaddress");
|
||||||
|
} else if (hasAttribute(attributes, "name")) {
|
||||||
|
userIdentifier = getFirstAttributeValue(attributes, "name");
|
||||||
|
} else if (hasAttribute(attributes, "upn")) {
|
||||||
|
userIdentifier = getFirstAttributeValue(attributes, "upn");
|
||||||
|
} else if (hasAttribute(attributes, "uid")) {
|
||||||
|
userIdentifier = getFirstAttributeValue(attributes, "uid");
|
||||||
|
} else {
|
||||||
|
userIdentifier = assertion.getSubject().getNameID().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rest of your existing code...
|
||||||
|
Optional<User> userOpt = userService.findByUsernameIgnoreCase(userIdentifier);
|
||||||
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
@@ -48,39 +91,27 @@ public class CustomSaml2ResponseAuthenticationConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the SessionIndexes
|
|
||||||
List<String> sessionIndexes = new ArrayList<>();
|
List<String> sessionIndexes = new ArrayList<>();
|
||||||
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
|
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
|
||||||
sessionIndexes.add(authnStatement.getSessionIndex());
|
sessionIndexes.add(authnStatement.getSessionIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the Attributes
|
|
||||||
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
|
||||||
|
|
||||||
// Create the custom principal
|
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
CustomSaml2AuthenticatedPrincipal principal =
|
||||||
new CustomSaml2AuthenticatedPrincipal(nameId, attributes, nameId, sessionIndexes);
|
new CustomSaml2AuthenticatedPrincipal(
|
||||||
|
userIdentifier, attributes, userIdentifier, sessionIndexes);
|
||||||
|
|
||||||
// Create the Saml2Authentication
|
|
||||||
return new Saml2Authentication(
|
return new Saml2Authentication(
|
||||||
principal,
|
principal,
|
||||||
responseToken.getToken().getSaml2Response(),
|
responseToken.getToken().getSaml2Response(),
|
||||||
Collections.singletonList(simpleGrantedAuthority));
|
Collections.singletonList(simpleGrantedAuthority));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
|
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
|
||||||
Map<String, List<Object>> attributes = new HashMap<>();
|
return attributes.containsKey(name) && !attributes.get(name).isEmpty();
|
||||||
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
|
}
|
||||||
for (Attribute attribute : attributeStatement.getAttributes()) {
|
|
||||||
String attributeName = attribute.getName();
|
private String getFirstAttributeValue(Map<String, List<Object>> attributes, String name) {
|
||||||
List<Object> values = new ArrayList<>();
|
List<Object> values = attributes.get(name);
|
||||||
for (XMLObject xmlObject : attribute.getAttributeValues()) {
|
return values != null && !values.isEmpty() ? values.get(0).toString() : null;
|
||||||
log.info("BOOL: " + ((XSBoolean) xmlObject).getValue());
|
|
||||||
values.add(((XSString) xmlObject).getValue());
|
|
||||||
}
|
|
||||||
attributes.put(attributeName, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attributes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,84 +52,115 @@ public class SplitPDFController {
|
|||||||
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
|
||||||
String pages = request.getPageNumbers();
|
|
||||||
// open the pdf document
|
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = null;
|
||||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
Path zipFile = null;
|
||||||
int totalPages = document.getNumberOfPages();
|
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
|
||||||
if (!pageNumbers.contains(totalPages - 1)) {
|
|
||||||
// Create a mutable ArrayList so we can add to it
|
|
||||||
pageNumbers = new ArrayList<>(pageNumbers);
|
|
||||||
pageNumbers.add(totalPages - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Splitting PDF into pages: {}",
|
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
||||||
|
|
||||||
// split the document
|
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
int previousPageNumber = 0;
|
|
||||||
for (int splitPoint : pageNumbers) {
|
try {
|
||||||
try (PDDocument splitDocument =
|
|
||||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
MultipartFile file = request.getFileInput();
|
||||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
String pages = request.getPageNumbers();
|
||||||
PDPage page = document.getPage(i);
|
// open the pdf document
|
||||||
splitDocument.addPage(page);
|
|
||||||
logger.info("Adding page {} to split document", i);
|
document = Loader.loadPDF(file.getBytes());
|
||||||
|
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||||
|
int totalPages = document.getNumberOfPages();
|
||||||
|
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||||
|
if (!pageNumbers.contains(totalPages - 1)) {
|
||||||
|
// Create a mutable ArrayList so we can add to it
|
||||||
|
pageNumbers = new ArrayList<>(pageNumbers);
|
||||||
|
pageNumbers.add(totalPages - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Splitting PDF into pages: {}",
|
||||||
|
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
|
||||||
|
// split the document
|
||||||
|
splitDocumentsBoas = new ArrayList<>();
|
||||||
|
int previousPageNumber = 0;
|
||||||
|
for (int splitPoint : pageNumbers) {
|
||||||
|
try (PDDocument splitDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
||||||
|
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||||
|
PDPage page = document.getPage(i);
|
||||||
|
splitDocument.addPage(page);
|
||||||
|
logger.info("Adding page {} to split document", i);
|
||||||
|
}
|
||||||
|
previousPageNumber = splitPoint + 1;
|
||||||
|
|
||||||
|
// Transfer metadata to split pdf
|
||||||
|
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
splitDocument.save(baos);
|
||||||
|
|
||||||
|
splitDocumentsBoas.add(baos);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed splitting documents and saving them", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
previousPageNumber = splitPoint + 1;
|
}
|
||||||
|
|
||||||
// Transfer metadata to split pdf
|
// closing the original document
|
||||||
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
document.close();
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
zipFile = Files.createTempFile("split_documents", ".zip");
|
||||||
splitDocument.save(baos);
|
|
||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||||
|
// loop through the split documents and write them to the zip file
|
||||||
|
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||||
|
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||||
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
|
byte[] pdf = baos.toByteArray();
|
||||||
|
|
||||||
|
// Add PDF file to the zip
|
||||||
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
|
zipOut.putNextEntry(pdfEntry);
|
||||||
|
zipOut.write(pdf);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
logger.info("Wrote split document {} to zip file", fileName);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
logger.error("Failed writing to zip", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// closing the original document
|
logger.info(
|
||||||
document.close();
|
"Successfully created zip file with split documents: {}", zipFile.toString());
|
||||||
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
// return the Resource in the response
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
String filename =
|
} finally {
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
try {
|
||||||
.replaceFirst("[.][^.]+$", "");
|
// Close the main document
|
||||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
if (document != null) {
|
||||||
// loop through the split documents and write them to the zip file
|
document.close();
|
||||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
}
|
||||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
|
||||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
|
||||||
byte[] pdf = baos.toByteArray();
|
|
||||||
|
|
||||||
// Add PDF file to the zip
|
// Close all ByteArrayOutputStreams
|
||||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
for (ByteArrayOutputStream baos : splitDocumentsBoas) {
|
||||||
zipOut.putNextEntry(pdfEntry);
|
if (baos != null) {
|
||||||
zipOut.write(pdf);
|
baos.close();
|
||||||
zipOut.closeEntry();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("Wrote split document {} to zip file", fileName);
|
// Delete temporary zip file
|
||||||
|
if (zipFile != null) {
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error while cleaning up resources", e);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed writing to zip", e);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
|
|
||||||
// return the Resource in the response
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,70 +59,86 @@ public class SplitPdfByChaptersController {
|
|||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
boolean includeMetadata = request.getIncludeMetadata();
|
PDDocument sourceDocument = null;
|
||||||
Integer bookmarkLevel =
|
Path zipFile = null;
|
||||||
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
|
||||||
if (bookmarkLevel < 0) {
|
|
||||||
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
|
||||||
}
|
|
||||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
|
||||||
|
|
||||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
|
||||||
|
|
||||||
if (outline == null) {
|
|
||||||
logger.warn("No outline found for {}", file.getOriginalFilename());
|
|
||||||
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
|
||||||
}
|
|
||||||
List<Bookmark> bookmarks = new ArrayList<>();
|
|
||||||
try {
|
try {
|
||||||
bookmarks =
|
boolean includeMetadata = request.getIncludeMetadata();
|
||||||
extractOutlineItems(
|
Integer bookmarkLevel =
|
||||||
sourceDocument,
|
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
||||||
outline.getFirstChild(),
|
if (bookmarkLevel < 0) {
|
||||||
bookmarks,
|
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
||||||
outline.getFirstChild().getNextSibling(),
|
}
|
||||||
0,
|
sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
bookmarkLevel);
|
|
||||||
// to handle last page edge case
|
|
||||||
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
|
||||||
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||||
logger.error("Unable to extract outline items", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
if (outline == null) {
|
||||||
.body("Unable to extract outline items".getBytes());
|
logger.warn("No outline found for {}", file.getOriginalFilename());
|
||||||
|
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
||||||
|
}
|
||||||
|
List<Bookmark> bookmarks = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
bookmarks =
|
||||||
|
extractOutlineItems(
|
||||||
|
sourceDocument,
|
||||||
|
outline.getFirstChild(),
|
||||||
|
bookmarks,
|
||||||
|
outline.getFirstChild().getNextSibling(),
|
||||||
|
0,
|
||||||
|
bookmarkLevel);
|
||||||
|
// to handle last page edge case
|
||||||
|
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
||||||
|
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Unable to extract outline items", e);
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body("Unable to extract outline items".getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allowDuplicates = request.getAllowDuplicates();
|
||||||
|
if (!allowDuplicates) {
|
||||||
|
/*
|
||||||
|
duplicates are generated when multiple bookmarks correspond to the same page,
|
||||||
|
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
||||||
|
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
||||||
|
*/
|
||||||
|
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
||||||
|
}
|
||||||
|
for (Bookmark bookmark : bookmarks) {
|
||||||
|
logger.info(
|
||||||
|
"{}::::{} to {}",
|
||||||
|
bookmark.getTitle(),
|
||||||
|
bookmark.getStartPage(),
|
||||||
|
bookmark.getEndPage());
|
||||||
|
}
|
||||||
|
List<ByteArrayOutputStream> splitDocumentsBoas =
|
||||||
|
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
||||||
|
|
||||||
|
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
||||||
|
|
||||||
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
sourceDocument.close();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (sourceDocument != null) {
|
||||||
|
sourceDocument.close();
|
||||||
|
}
|
||||||
|
if (zipFile != null) {
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error while cleaning up resources", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean allowDuplicates = request.getAllowDuplicates();
|
|
||||||
if (!allowDuplicates) {
|
|
||||||
/*
|
|
||||||
duplicates are generated when multiple bookmarks correspond to the same page,
|
|
||||||
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
|
||||||
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
|
||||||
*/
|
|
||||||
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
|
||||||
}
|
|
||||||
for (Bookmark bookmark : bookmarks) {
|
|
||||||
logger.info(
|
|
||||||
"{}::::{} to {}",
|
|
||||||
bookmark.getTitle(),
|
|
||||||
bookmark.getStartPage(),
|
|
||||||
bookmark.getEndPage());
|
|
||||||
}
|
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas =
|
|
||||||
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
|
||||||
|
|
||||||
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
|
||||||
|
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
|
|
||||||
String filename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
sourceDocument.close();
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
||||||
|
|||||||
@@ -105,15 +105,13 @@ public class SplitPdfBySectionsController {
|
|||||||
|
|
||||||
if (sectionNum == horiz * verti) pageNum++;
|
if (sectionNum == horiz * verti) pageNum++;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("exception", e);
|
|
||||||
} finally {
|
|
||||||
data = Files.readAllBytes(zipFile);
|
data = Files.readAllBytes(zipFile);
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
|
} finally {
|
||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PDDocument> splitPdfPages(
|
public List<PDDocument> splitPdfPages(
|
||||||
|
|||||||
@@ -244,8 +244,8 @@ public class UserController {
|
|||||||
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
return new RedirectView("/addUsers?messageType=invalidRole", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authType.equalsIgnoreCase(AuthenticationType.OAUTH2.toString())) {
|
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
|
||||||
userService.saveUser(username, AuthenticationType.OAUTH2, role);
|
userService.saveUser(username, AuthenticationType.SSO, role);
|
||||||
} else {
|
} else {
|
||||||
if (password.isBlank()) {
|
if (password.isBlank()) {
|
||||||
return new RedirectView("/addUsers?messageType=invalidPassword", true);
|
return new RedirectView("/addUsers?messageType=invalidPassword", true);
|
||||||
|
|||||||
@@ -65,112 +65,137 @@ public class ConvertImgPDFController {
|
|||||||
String colorType = request.getColorType();
|
String colorType = request.getColorType();
|
||||||
String dpi = request.getDpi();
|
String dpi = request.getDpi();
|
||||||
|
|
||||||
byte[] pdfBytes = file.getBytes();
|
Path tempFile = null;
|
||||||
ImageType colorTypeResult = ImageType.RGB;
|
Path tempOutputDir = null;
|
||||||
if ("greyscale".equals(colorType)) {
|
Path tempPdfPath = null;
|
||||||
colorTypeResult = ImageType.GRAY;
|
|
||||||
} else if ("blackwhite".equals(colorType)) {
|
|
||||||
colorTypeResult = ImageType.BINARY;
|
|
||||||
}
|
|
||||||
// returns bytes for image
|
|
||||||
boolean singleImage = "single".equals(singleOrMultiple);
|
|
||||||
byte[] result = null;
|
byte[] result = null;
|
||||||
String filename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
|
|
||||||
result =
|
try {
|
||||||
PdfUtils.convertFromPdf(
|
byte[] pdfBytes = file.getBytes();
|
||||||
pdfBytes,
|
ImageType colorTypeResult = ImageType.RGB;
|
||||||
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
|
if ("greyscale".equals(colorType)) {
|
||||||
colorTypeResult,
|
colorTypeResult = ImageType.GRAY;
|
||||||
singleImage,
|
} else if ("blackwhite".equals(colorType)) {
|
||||||
Integer.valueOf(dpi),
|
colorTypeResult = ImageType.BINARY;
|
||||||
filename);
|
|
||||||
if (result == null || result.length == 0) {
|
|
||||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
|
||||||
}
|
|
||||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
|
||||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
|
||||||
} else if ("webp".equalsIgnoreCase(imageFormat)
|
|
||||||
&& CheckProgramInstall.isPythonAvailable()) {
|
|
||||||
// Write the output stream to a temp file
|
|
||||||
Path tempFile = Files.createTempFile("temp_png", ".png");
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
|
|
||||||
fos.write(result);
|
|
||||||
fos.flush();
|
|
||||||
}
|
}
|
||||||
|
// returns bytes for image
|
||||||
|
boolean singleImage = "single".equals(singleOrMultiple);
|
||||||
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
|
||||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
result =
|
||||||
|
PdfUtils.convertFromPdf(
|
||||||
List<String> command = new ArrayList<>();
|
pdfBytes,
|
||||||
command.add(pythonVersion);
|
"webp".equalsIgnoreCase(imageFormat)
|
||||||
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
? "png"
|
||||||
|
: imageFormat.toUpperCase(),
|
||||||
// Create a temporary directory for the output WebP files
|
colorTypeResult,
|
||||||
Path tempOutputDir = Files.createTempDirectory("webp_output");
|
singleImage,
|
||||||
if (singleImage) {
|
Integer.valueOf(dpi),
|
||||||
// Run the Python script to convert PNG to WebP
|
filename);
|
||||||
command.add(tempFile.toString());
|
if (result == null || result.length == 0) {
|
||||||
command.add(tempOutputDir.toString());
|
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||||
command.add("--single");
|
|
||||||
} else {
|
|
||||||
// Save the uploaded PDF to a temporary file
|
|
||||||
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
|
|
||||||
file.transferTo(tempPdfPath.toFile());
|
|
||||||
// Run the Python script to convert PDF to WebP
|
|
||||||
command.add(tempPdfPath.toString());
|
|
||||||
command.add(tempOutputDir.toString());
|
|
||||||
}
|
}
|
||||||
command.add("--dpi");
|
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
||||||
command.add(dpi);
|
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||||
ProcessExecutorResult resultProcess =
|
} else if ("webp".equalsIgnoreCase(imageFormat)
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
&& CheckProgramInstall.isPythonAvailable()) {
|
||||||
.runCommandWithOutputHandling(command);
|
// Write the output stream to a temp file
|
||||||
|
tempFile = Files.createTempFile("temp_png", ".png");
|
||||||
// Find all WebP files in the output directory
|
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
|
||||||
List<Path> webpFiles =
|
fos.write(result);
|
||||||
Files.walk(tempOutputDir)
|
fos.flush();
|
||||||
.filter(path -> path.toString().endsWith(".webp"))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (webpFiles.isEmpty()) {
|
|
||||||
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
|
||||||
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bodyBytes = new byte[0];
|
|
||||||
|
|
||||||
if (webpFiles.size() == 1) {
|
|
||||||
// Return the single WebP file directly
|
|
||||||
Path webpFilePath = webpFiles.get(0);
|
|
||||||
bodyBytes = Files.readAllBytes(webpFilePath);
|
|
||||||
} else {
|
|
||||||
// Create a ZIP file containing all WebP images
|
|
||||||
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
|
|
||||||
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
|
|
||||||
for (Path webpFile : webpFiles) {
|
|
||||||
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
|
|
||||||
Files.copy(webpFile, zos);
|
|
||||||
zos.closeEntry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bodyBytes = zipOutputStream.toByteArray();
|
|
||||||
}
|
|
||||||
// Clean up the temporary files
|
|
||||||
Files.deleteIfExists(tempFile);
|
|
||||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
|
||||||
result = bodyBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (singleImage) {
|
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||||
String docName = filename + "." + imageFormat;
|
|
||||||
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
List<String> command = new ArrayList<>();
|
||||||
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
command.add(pythonVersion);
|
||||||
} else {
|
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
||||||
String zipFilename = filename + "_convertedToImages.zip";
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
// Create a temporary directory for the output WebP files
|
||||||
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
tempOutputDir = Files.createTempDirectory("webp_output");
|
||||||
|
if (singleImage) {
|
||||||
|
// Run the Python script to convert PNG to WebP
|
||||||
|
command.add(tempFile.toString());
|
||||||
|
command.add(tempOutputDir.toString());
|
||||||
|
command.add("--single");
|
||||||
|
} else {
|
||||||
|
// Save the uploaded PDF to a temporary file
|
||||||
|
tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
|
||||||
|
file.transferTo(tempPdfPath.toFile());
|
||||||
|
// Run the Python script to convert PDF to WebP
|
||||||
|
command.add(tempPdfPath.toString());
|
||||||
|
command.add(tempOutputDir.toString());
|
||||||
|
}
|
||||||
|
command.add("--dpi");
|
||||||
|
command.add(dpi);
|
||||||
|
ProcessExecutorResult resultProcess =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
// Find all WebP files in the output directory
|
||||||
|
List<Path> webpFiles =
|
||||||
|
Files.walk(tempOutputDir)
|
||||||
|
.filter(path -> path.toString().endsWith(".webp"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (webpFiles.isEmpty()) {
|
||||||
|
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
||||||
|
throw new IOException(
|
||||||
|
"No WebP files were created. " + resultProcess.getMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bodyBytes = new byte[0];
|
||||||
|
|
||||||
|
if (webpFiles.size() == 1) {
|
||||||
|
// Return the single WebP file directly
|
||||||
|
Path webpFilePath = webpFiles.get(0);
|
||||||
|
bodyBytes = Files.readAllBytes(webpFilePath);
|
||||||
|
} else {
|
||||||
|
// Create a ZIP file containing all WebP images
|
||||||
|
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
|
||||||
|
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
|
||||||
|
for (Path webpFile : webpFiles) {
|
||||||
|
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
|
||||||
|
Files.copy(webpFile, zos);
|
||||||
|
zos.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bodyBytes = zipOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
// Clean up the temporary files
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
|
result = bodyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (singleImage) {
|
||||||
|
String docName = filename + "." + imageFormat;
|
||||||
|
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
||||||
|
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
||||||
|
} else {
|
||||||
|
String zipFilename = filename + "_convertedToImages.zip";
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
// Clean up temporary files
|
||||||
|
if (tempFile != null) {
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
}
|
||||||
|
if (tempPdfPath != null) {
|
||||||
|
Files.deleteIfExists(tempPdfPath);
|
||||||
|
}
|
||||||
|
if (tempOutputDir != null) {
|
||||||
|
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error cleaning up temporary files", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.File;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -37,59 +38,90 @@ public class ConvertPDFToPDFA {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
description =
|
description =
|
||||||
"This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO")
|
"This endpoint converts a PDF file to a PDF/A file using LibreOffice. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
|
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String outputFormat = request.getOutputFormat();
|
String outputFormat = request.getOutputFormat();
|
||||||
|
|
||||||
// Convert MultipartFile to byte[]
|
// Validate input file type
|
||||||
byte[] pdfBytes = inputFile.getBytes();
|
if (!"application/pdf".equals(inputFile.getContentType())) {
|
||||||
|
logger.error("Invalid input file type: {}", inputFile.getContentType());
|
||||||
// Save the uploaded file to a temporary location
|
throw new IllegalArgumentException("Input file must be a PDF");
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
|
||||||
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
|
||||||
outputStream.write(pdfBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the output file path
|
// Get the original filename without extension
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
String originalFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||||
|
if (originalFileName == null || originalFileName.trim().isEmpty()) {
|
||||||
// Prepare the ghostscript command
|
originalFileName = "output.pdf";
|
||||||
List<String> command = new ArrayList<>();
|
|
||||||
command.add("gs");
|
|
||||||
command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1"));
|
|
||||||
command.add("-dNOPAUSE");
|
|
||||||
command.add("-dBATCH");
|
|
||||||
command.add("-sColorConversionStrategy=sRGB");
|
|
||||||
command.add("-sDEVICE=pdfwrite");
|
|
||||||
command.add("-dPDFACompatibilityPolicy=2");
|
|
||||||
command.add("-o");
|
|
||||||
command.add(tempOutputFile.toString());
|
|
||||||
command.add(tempInputFile.toString());
|
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
|
|
||||||
if (returnCode.getRc() != 0) {
|
|
||||||
logger.info(
|
|
||||||
outputFormat + " conversion failed with return code: " + returnCode.getRc());
|
|
||||||
}
|
}
|
||||||
|
String baseFileName =
|
||||||
|
originalFileName.contains(".")
|
||||||
|
? originalFileName.substring(0, originalFileName.lastIndexOf('.'))
|
||||||
|
: originalFileName;
|
||||||
|
|
||||||
|
Path tempInputFile = null;
|
||||||
|
Path tempOutputDir = null;
|
||||||
|
byte[] fileBytes;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile);
|
// Save uploaded file to temp location
|
||||||
// Return the optimized PDF as a response
|
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
String outputFilename =
|
inputFile.transferTo(tempInputFile);
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
// Create temp output directory
|
||||||
+ "_PDFA.pdf";
|
tempOutputDir = Files.createTempDirectory("output_");
|
||||||
|
|
||||||
|
// Determine PDF/A filter based on requested format
|
||||||
|
String pdfFilter =
|
||||||
|
"pdfa".equals(outputFormat)
|
||||||
|
? "writer_pdf_Export:{'SelectPdfVersion':{'Value':'2'}}:writer_pdf_Export"
|
||||||
|
: "writer_pdf_Export:{'SelectPdfVersion':{'Value':'1'}}:writer_pdf_Export";
|
||||||
|
|
||||||
|
// Prepare LibreOffice command
|
||||||
|
List<String> command =
|
||||||
|
new ArrayList<>(
|
||||||
|
Arrays.asList(
|
||||||
|
"soffice",
|
||||||
|
"--headless",
|
||||||
|
"--nologo",
|
||||||
|
"--convert-to",
|
||||||
|
"pdf:" + pdfFilter,
|
||||||
|
"--outdir",
|
||||||
|
tempOutputDir.toString(),
|
||||||
|
tempInputFile.toString()));
|
||||||
|
|
||||||
|
ProcessExecutorResult returnCode =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
if (returnCode.getRc() != 0) {
|
||||||
|
logger.error("PDF/A conversion failed with return code: {}", returnCode.getRc());
|
||||||
|
throw new RuntimeException("PDF/A conversion failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the output file
|
||||||
|
File[] outputFiles = tempOutputDir.toFile().listFiles();
|
||||||
|
if (outputFiles == null || outputFiles.length != 1) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Expected exactly one output file but found "
|
||||||
|
+ (outputFiles == null ? "none" : outputFiles.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
fileBytes = FileUtils.readFileToByteArray(outputFiles[0]);
|
||||||
|
String outputFilename = baseFileName + "_PDFA.pdf";
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF);
|
fileBytes, outputFilename, MediaType.APPLICATION_PDF);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up temporary files
|
||||||
Files.deleteIfExists(tempInputFile);
|
if (tempInputFile != null) {
|
||||||
Files.deleteIfExists(tempOutputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
|
}
|
||||||
|
if (tempOutputDir != null) {
|
||||||
|
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.controller.api.CropController;
|
|
||||||
import stirling.software.SPDF.model.api.extract.PDFFilePage;
|
import stirling.software.SPDF.model.api.extract.PDFFilePage;
|
||||||
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
|
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
|
||||||
import technology.tabula.ObjectExtractor;
|
import technology.tabula.ObjectExtractor;
|
||||||
@@ -37,11 +37,15 @@ public class ExtractCSVController {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Extracts a CSV document from a PDF", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
@Operation(
|
||||||
|
summary = "Extracts a CSV document from a PDF",
|
||||||
|
description =
|
||||||
|
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
||||||
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
|
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
|
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
|
||||||
CSVFormat format = CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
|
CSVFormat format =
|
||||||
|
CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
|
||||||
Writer csvWriter = new FlexibleCSVWriter(format);
|
Writer csvWriter = new FlexibleCSVWriter(format);
|
||||||
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
|
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
|
||||||
try (ObjectExtractor extractor = new ObjectExtractor(document)) {
|
try (ObjectExtractor extractor = new ObjectExtractor(document)) {
|
||||||
@@ -56,8 +60,8 @@ public class ExtractCSVController {
|
|||||||
ContentDisposition.builder("attachment")
|
ContentDisposition.builder("attachment")
|
||||||
.filename(
|
.filename(
|
||||||
form.getFileInput()
|
form.getFileInput()
|
||||||
.getOriginalFilename()
|
.getOriginalFilename()
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_extracted.csv")
|
+ "_extracted.csv")
|
||||||
.build());
|
.build());
|
||||||
headers.setContentType(MediaType.parseMediaType("text/csv"));
|
headers.setContentType(MediaType.parseMediaType("text/csv"));
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@@ -53,6 +52,54 @@ public class CompressController {
|
|||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void compressImagesInPDF(Path pdfFile, double initialScaleFactor) throws Exception {
|
||||||
|
byte[] fileBytes = Files.readAllBytes(pdfFile);
|
||||||
|
try (PDDocument doc = Loader.loadPDF(fileBytes)) {
|
||||||
|
double scaleFactor = initialScaleFactor;
|
||||||
|
|
||||||
|
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 instanceof PDImageXObject) {
|
||||||
|
PDImageXObject image = (PDImageXObject) xobj;
|
||||||
|
BufferedImage bufferedImage = image.getImage();
|
||||||
|
|
||||||
|
int newWidth = (int) (bufferedImage.getWidth() * scaleFactor);
|
||||||
|
int newHeight = (int) (bufferedImage.getHeight() * scaleFactor);
|
||||||
|
|
||||||
|
if (newWidth == 0 || newHeight == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image scaledImage =
|
||||||
|
bufferedImage.getScaledInstance(
|
||||||
|
newWidth, newHeight, Image.SCALE_SMOOTH);
|
||||||
|
|
||||||
|
BufferedImage scaledBufferedImage =
|
||||||
|
new BufferedImage(
|
||||||
|
newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
|
||||||
|
scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
|
||||||
|
|
||||||
|
ByteArrayOutputStream compressedImageStream =
|
||||||
|
new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream);
|
||||||
|
byte[] imageBytes = compressedImageStream.toByteArray();
|
||||||
|
compressedImageStream.close();
|
||||||
|
|
||||||
|
PDImageXObject compressedImage =
|
||||||
|
PDImageXObject.createFromByteArray(
|
||||||
|
doc, imageBytes, image.getCOSObject().toString());
|
||||||
|
res.put(name, compressedImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doc.save(pdfFile.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Optimize PDF file",
|
summary = "Optimize PDF file",
|
||||||
@@ -75,209 +122,92 @@ public class CompressController {
|
|||||||
autoMode = true;
|
autoMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the uploaded file to a temporary location
|
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
|
|
||||||
long inputFileSize = Files.size(tempInputFile);
|
long inputFileSize = Files.size(tempInputFile);
|
||||||
|
|
||||||
// Prepare the output file path
|
|
||||||
|
|
||||||
Path tempOutputFile = null;
|
Path tempOutputFile = null;
|
||||||
byte[] pdfBytes;
|
byte[] pdfBytes;
|
||||||
try {
|
try {
|
||||||
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
// Determine initial optimization level based on expected size reduction, only if in
|
|
||||||
// autoMode
|
|
||||||
if (autoMode) {
|
if (autoMode) {
|
||||||
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
|
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
|
||||||
if (sizeReductionRatio > 0.7) {
|
optimizeLevel = determineOptimizeLevel(sizeReductionRatio);
|
||||||
optimizeLevel = 1;
|
|
||||||
} else if (sizeReductionRatio > 0.5) {
|
|
||||||
optimizeLevel = 2;
|
|
||||||
} else if (sizeReductionRatio > 0.35) {
|
|
||||||
optimizeLevel = 3;
|
|
||||||
} else {
|
|
||||||
optimizeLevel = 3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean sizeMet = false;
|
boolean sizeMet = false;
|
||||||
while (!sizeMet && optimizeLevel <= 4) {
|
while (!sizeMet && optimizeLevel <= 9) {
|
||||||
// Prepare the Ghostscript command
|
|
||||||
List<String> command = new ArrayList<>();
|
|
||||||
command.add("gs");
|
|
||||||
command.add("-sDEVICE=pdfwrite");
|
|
||||||
command.add("-dCompatibilityLevel=1.5");
|
|
||||||
|
|
||||||
switch (optimizeLevel) {
|
// Apply additional image compression for levels 6-9
|
||||||
case 1:
|
if (optimizeLevel >= 6) {
|
||||||
command.add("-dPDFSETTINGS=/prepress");
|
// Calculate scale factor based on optimization level
|
||||||
break;
|
double scaleFactor =
|
||||||
case 2:
|
switch (optimizeLevel) {
|
||||||
command.add("-dPDFSETTINGS=/printer");
|
case 6 -> 0.9; // 90% of original size
|
||||||
break;
|
case 7 -> 0.8; // 80% of original size
|
||||||
case 3:
|
case 8 -> 0.65; // 70% of original size
|
||||||
command.add("-dPDFSETTINGS=/ebook");
|
case 9 -> 0.5; // 60% of original size
|
||||||
break;
|
default -> 1.0;
|
||||||
case 4:
|
};
|
||||||
command.add("-dPDFSETTINGS=/screen");
|
compressImagesInPDF(tempInputFile, scaleFactor);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
command.add("-dPDFSETTINGS=/default");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
command.add("-dNOPAUSE");
|
// Run QPDF optimization
|
||||||
command.add("-dQUIET");
|
List<String> command = new ArrayList<>();
|
||||||
command.add("-dBATCH");
|
command.add("qpdf");
|
||||||
command.add("-sOutputFile=" + tempOutputFile.toString());
|
if (request.getNormalize()) {
|
||||||
|
command.add("--normalize-content=y");
|
||||||
|
}
|
||||||
|
if (request.getLinearize()) {
|
||||||
|
command.add("--linearize");
|
||||||
|
}
|
||||||
|
command.add("--optimize-images");
|
||||||
|
command.add("--recompress-flate");
|
||||||
|
command.add("--compression-level=" + optimizeLevel);
|
||||||
|
command.add("--compress-streams=y");
|
||||||
|
command.add("--object-streams=generate");
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode = null;
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
try {
|
||||||
.runCommandWithOutputHandling(command);
|
returnCode =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (returnCode != null && returnCode.getRc() != 3) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if file size is within expected size or not auto mode so instantly finish
|
// Check if file size is within expected size or not auto mode
|
||||||
long outputFileSize = Files.size(tempOutputFile);
|
long outputFileSize = Files.size(tempOutputFile);
|
||||||
if (outputFileSize <= expectedOutputSize || !autoMode) {
|
if (outputFileSize <= expectedOutputSize || !autoMode) {
|
||||||
sizeMet = true;
|
sizeMet = true;
|
||||||
} else {
|
} else {
|
||||||
// Increase optimization level for next iteration
|
optimizeLevel =
|
||||||
optimizeLevel++;
|
incrementOptimizeLevel(
|
||||||
if (autoMode && optimizeLevel > 4) {
|
optimizeLevel, outputFileSize, expectedOutputSize);
|
||||||
logger.info("Skipping level 5 due to bad results in auto mode");
|
if (autoMode && optimizeLevel > 9) {
|
||||||
|
logger.info("Maximum compression level reached in auto mode");
|
||||||
sizeMet = true;
|
sizeMet = true;
|
||||||
} else {
|
|
||||||
logger.info(
|
|
||||||
"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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Current file size: "
|
|
||||||
+ FileUtils.byteCountToDisplaySize(currentSize));
|
|
||||||
logger.info("Current scale factor: " + scaleFactor);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
Path finalFile = tempOutputFile;
|
Path finalFile = tempOutputFile;
|
||||||
|
|
||||||
// Check if optimized file is larger than the original
|
// Check if optimized file is larger than the original
|
||||||
if (pdfBytes.length > inputFileSize) {
|
if (pdfBytes.length > inputFileSize) {
|
||||||
// Log the occurrence
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Optimized file is larger than the original. Returning the original file instead.");
|
"Optimized file is larger than the original. Returning the original file instead.");
|
||||||
|
|
||||||
// Read the original file again
|
|
||||||
finalFile = tempInputFile;
|
finalFile = tempInputFile;
|
||||||
}
|
}
|
||||||
// Return the optimized PDF as a response
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
@@ -286,10 +216,31 @@ public class CompressController {
|
|||||||
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
|
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
|
||||||
// deleted by multipart file handler deu to transferTo?
|
|
||||||
// Files.deleteIfExists(tempInputFile);
|
|
||||||
Files.deleteIfExists(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int determineOptimizeLevel(double sizeReductionRatio) {
|
||||||
|
if (sizeReductionRatio > 0.9) return 1;
|
||||||
|
if (sizeReductionRatio > 0.8) return 2;
|
||||||
|
if (sizeReductionRatio > 0.7) return 3;
|
||||||
|
if (sizeReductionRatio > 0.6) return 4;
|
||||||
|
if (sizeReductionRatio > 0.5) return 5;
|
||||||
|
if (sizeReductionRatio > 0.4) return 6;
|
||||||
|
if (sizeReductionRatio > 0.3) return 7;
|
||||||
|
if (sizeReductionRatio > 0.2) return 8;
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
|
||||||
|
double currentRatio = currentSize / (double) targetSize;
|
||||||
|
logger.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
|
||||||
|
|
||||||
|
if (currentRatio > 2.0) {
|
||||||
|
return Math.min(9, currentLevel + 3);
|
||||||
|
} else if (currentRatio > 1.5) {
|
||||||
|
return Math.min(9, currentLevel + 2);
|
||||||
|
}
|
||||||
|
return Math.min(9, currentLevel + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class FakeScanControllerWIP {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.")
|
"This endpoint repairs a given PDF file by running qpdf command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.")
|
||||||
public ResponseEntity<byte[]> fakeScan(@ModelAttribute PDFFile request) throws IOException {
|
public ResponseEntity<byte[]> fakeScan(@ModelAttribute PDFFile request) throws IOException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -26,6 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@@ -44,6 +47,11 @@ public class MetadataController {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder binder) {
|
||||||
|
binder.registerCustomEditor(Map.class, "allRequestParams", new StringToMapPropertyEditor());
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Update metadata of a PDF file",
|
summary = "Update metadata of a PDF file",
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -23,24 +33,31 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.github.pixee.security.BoundedLineReader;
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
@Slf4j
|
||||||
public class OCRController {
|
public class OCRController {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the list of available Tesseract languages from the tessdata directory */
|
||||||
public List<String> getAvailableTesseractLanguages() {
|
public List<String> getAvailableTesseractLanguages() {
|
||||||
String tessdataDir = applicationProperties.getSystem().getTessdataDir();
|
String tessdataDir = applicationProperties.getSystem().getTessdataDir();
|
||||||
File[] files = new File(tessdataDir).listFiles();
|
File[] files = new File(tessdataDir).listFiles();
|
||||||
@@ -54,196 +71,167 @@ public class OCRController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
|
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
@Operation(
|
|
||||||
summary = "Process a PDF file with OCR",
|
|
||||||
description =
|
|
||||||
"This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
|
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
public ResponseEntity<byte[]> processPdfWithOCR(
|
||||||
@ModelAttribute ProcessPdfWithOcrRequest request)
|
@ModelAttribute ProcessPdfWithOcrRequest request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
List<String> selectedLanguages = request.getLanguages();
|
List<String> languages = request.getLanguages();
|
||||||
Boolean sidecar = request.isSidecar();
|
|
||||||
Boolean deskew = request.isDeskew();
|
|
||||||
Boolean clean = request.isClean();
|
|
||||||
Boolean cleanFinal = request.isCleanFinal();
|
|
||||||
String ocrType = request.getOcrType();
|
String ocrType = request.getOcrType();
|
||||||
String ocrRenderType = request.getOcrRenderType();
|
|
||||||
Boolean removeImagesAfter = request.isRemoveImagesAfter();
|
|
||||||
// --output-type pdfa
|
|
||||||
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
|
|
||||||
throw new IOException("Please select at least one language.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!"hocr".equals(ocrRenderType) && !"sandwich".equals(ocrRenderType)) {
|
Path tempDir = Files.createTempDirectory("ocr_process");
|
||||||
throw new IOException("ocrRenderType wrong");
|
Path tempInputFile = tempDir.resolve("input.pdf");
|
||||||
}
|
Path tempOutputDir = tempDir.resolve("output");
|
||||||
|
Path tempImagesDir = tempDir.resolve("images");
|
||||||
// Get available Tesseract languages
|
Path finalOutputFile = tempDir.resolve("final_output.pdf");
|
||||||
List<String> availableLanguages = getAvailableTesseractLanguages();
|
|
||||||
|
|
||||||
// Validate selected languages
|
|
||||||
selectedLanguages =
|
|
||||||
selectedLanguages.stream().filter(availableLanguages::contains).toList();
|
|
||||||
|
|
||||||
if (selectedLanguages.isEmpty()) {
|
|
||||||
throw new IOException("None of the selected languages are valid.");
|
|
||||||
}
|
|
||||||
// Save the uploaded file to a temporary location
|
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
|
||||||
Path sidecarTextPath = null;
|
|
||||||
|
|
||||||
|
Files.createDirectories(tempOutputDir);
|
||||||
|
Files.createDirectories(tempImagesDir);
|
||||||
|
Process process = null;
|
||||||
try {
|
try {
|
||||||
|
// Save input file
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
|
PDFMergerUtility merger = new PDFMergerUtility();
|
||||||
|
merger.setDestinationFileName(finalOutputFile.toString());
|
||||||
|
|
||||||
// Run OCR Command
|
try (PDDocument document = pdfDocumentFactory.load(tempInputFile.toFile())) {
|
||||||
String languageOption = String.join("+", selectedLanguages);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
int pageCount = document.getNumberOfPages();
|
||||||
|
|
||||||
List<String> command =
|
for (int pageNum = 0; pageNum < pageCount; pageNum++) {
|
||||||
new ArrayList<>(
|
PDPage page = document.getPage(pageNum);
|
||||||
Arrays.asList(
|
boolean hasText = false;
|
||||||
"ocrmypdf",
|
|
||||||
"--verbose",
|
|
||||||
"2",
|
|
||||||
"--output-type",
|
|
||||||
"pdf",
|
|
||||||
"--pdf-renderer",
|
|
||||||
ocrRenderType));
|
|
||||||
|
|
||||||
if (sidecar != null && sidecar) {
|
// Check for existing text
|
||||||
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
try (PDDocument tempDoc = new PDDocument()) {
|
||||||
command.add("--sidecar");
|
tempDoc.addPage(page);
|
||||||
command.add(sidecarTextPath.toString());
|
PDFTextStripper stripper = new PDFTextStripper();
|
||||||
}
|
hasText = !stripper.getText(tempDoc).trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
if (deskew != null && deskew) {
|
boolean shouldOcr =
|
||||||
command.add("--deskew");
|
switch (ocrType) {
|
||||||
}
|
case "skip-text" -> !hasText;
|
||||||
if (clean != null && clean) {
|
case "force-ocr" -> true;
|
||||||
command.add("--clean");
|
default -> true;
|
||||||
}
|
};
|
||||||
if (cleanFinal != null && cleanFinal) {
|
|
||||||
command.add("--clean-final");
|
|
||||||
}
|
|
||||||
if (ocrType != null && !"".equals(ocrType)) {
|
|
||||||
if ("skip-text".equals(ocrType)) {
|
|
||||||
command.add("--skip-text");
|
|
||||||
} else if ("force-ocr".equals(ocrType)) {
|
|
||||||
command.add("--force-ocr");
|
|
||||||
} else if ("Normal".equals(ocrType)) {
|
|
||||||
|
|
||||||
|
Path pageOutputPath =
|
||||||
|
tempOutputDir.resolve(String.format("page_%d.pdf", pageNum));
|
||||||
|
|
||||||
|
if (shouldOcr) {
|
||||||
|
// Convert page to image
|
||||||
|
BufferedImage image = pdfRenderer.renderImageWithDPI(pageNum, 300);
|
||||||
|
Path imagePath =
|
||||||
|
tempImagesDir.resolve(String.format("page_%d.png", pageNum));
|
||||||
|
ImageIO.write(image, "png", imagePath.toFile());
|
||||||
|
|
||||||
|
// Build OCR command
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add("tesseract");
|
||||||
|
command.add(imagePath.toString());
|
||||||
|
command.add(
|
||||||
|
tempOutputDir
|
||||||
|
.resolve(String.format("page_%d", pageNum))
|
||||||
|
.toString());
|
||||||
|
command.add("-l");
|
||||||
|
command.add(String.join("+", languages));
|
||||||
|
command.add("pdf"); // Always output PDF
|
||||||
|
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
|
process = pb.start();
|
||||||
|
|
||||||
|
// Capture any error output
|
||||||
|
try (BufferedReader reader =
|
||||||
|
new BufferedReader(
|
||||||
|
new InputStreamReader(process.getErrorStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {
|
||||||
|
log.debug("Tesseract: {}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Tesseract failed with exit code: " + exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add OCR'd PDF to merger
|
||||||
|
merger.addSource(pageOutputPath.toFile());
|
||||||
|
} else {
|
||||||
|
// Save original page without OCR
|
||||||
|
try (PDDocument pageDoc = new PDDocument()) {
|
||||||
|
pageDoc.addPage(page);
|
||||||
|
pageDoc.save(pageOutputPath.toFile());
|
||||||
|
merger.addSource(pageOutputPath.toFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command.addAll(
|
// Merge all pages into final PDF
|
||||||
Arrays.asList(
|
merger.mergeDocuments(null);
|
||||||
"--language",
|
|
||||||
languageOption,
|
|
||||||
tempInputFile.toString(),
|
|
||||||
tempOutputFile.toString()));
|
|
||||||
|
|
||||||
// Run CLI command
|
// Read the final PDF file
|
||||||
ProcessExecutorResult result =
|
byte[] pdfContent = Files.readAllBytes(finalOutputFile);
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
if (result.getRc() != 0
|
|
||||||
&& result.getMessages().contains("multiprocessing/synchronize.py")
|
|
||||||
&& result.getMessages()
|
|
||||||
.contains("OSError: [Errno 38] Function not implemented")) {
|
|
||||||
command.add("--jobs");
|
|
||||||
command.add("1");
|
|
||||||
result =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove images from the OCR processed PDF if the flag is set to true
|
|
||||||
if (removeImagesAfter != null && removeImagesAfter) {
|
|
||||||
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
|
|
||||||
|
|
||||||
List<String> gsCommand =
|
|
||||||
Arrays.asList(
|
|
||||||
"gs",
|
|
||||||
"-sDEVICE=pdfwrite",
|
|
||||||
"-dFILTERIMAGE",
|
|
||||||
"-o",
|
|
||||||
tempPdfWithoutImages.toString(),
|
|
||||||
tempOutputFile.toString());
|
|
||||||
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
|
||||||
.runCommandWithOutputHandling(gsCommand);
|
|
||||||
tempOutputFile = tempPdfWithoutImages;
|
|
||||||
}
|
|
||||||
// Read the OCR processed PDF file
|
|
||||||
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
|
|
||||||
|
|
||||||
// Return the OCR processed PDF as a response
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_OCR.pdf";
|
+ "_OCR.pdf";
|
||||||
|
|
||||||
if (sidecar != null && sidecar) {
|
return ResponseEntity.ok()
|
||||||
// Create a zip file containing both the PDF and the text file
|
.header(
|
||||||
String outputZipFilename =
|
"Content-Disposition",
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
"attachment; filename=\"" + outputFilename + "\"")
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.contentType(MediaType.APPLICATION_PDF)
|
||||||
+ "_OCR.zip";
|
.body(pdfContent);
|
||||||
Path tempZipFile = Files.createTempFile("output_", ".zip");
|
|
||||||
|
|
||||||
try (ZipOutputStream zipOut =
|
|
||||||
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
|
||||||
// Add PDF file to the zip
|
|
||||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
|
||||||
zipOut.putNextEntry(pdfEntry);
|
|
||||||
try (ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes)) {
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ((length = pdfInputStream.read(buffer)) != -1) {
|
|
||||||
zipOut.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zipOut.closeEntry();
|
|
||||||
|
|
||||||
// Add text file to the zip
|
|
||||||
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
|
|
||||||
zipOut.putNextEntry(txtEntry);
|
|
||||||
Files.copy(sidecarTextPath, zipOut);
|
|
||||||
zipOut.closeEntry();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
|
||||||
|
|
||||||
// Clean up the temporary zip file
|
|
||||||
Files.deleteIfExists(tempZipFile);
|
|
||||||
Files.deleteIfExists(tempOutputFile);
|
|
||||||
Files.deleteIfExists(sidecarTextPath);
|
|
||||||
|
|
||||||
// Return the zip file containing both the PDF and the text file
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
} else {
|
|
||||||
// Return the OCR processed PDF as a response
|
|
||||||
Files.deleteIfExists(tempOutputFile);
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
if (process != null) {
|
||||||
Files.deleteIfExists(tempOutputFile);
|
process.destroy();
|
||||||
// Comment out as transferTo makes multipart handle cleanup
|
|
||||||
// Files.deleteIfExists(tempInputFile);
|
|
||||||
if (sidecarTextPath != null) {
|
|
||||||
Files.deleteIfExists(sidecarTextPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up temporary files
|
||||||
|
deleteDirectory(tempDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFileToZip(File file, String filename, ZipOutputStream zipOut)
|
||||||
|
throws IOException {
|
||||||
|
if (!file.exists()) {
|
||||||
|
log.warn("File {} does not exist, skipping", file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileInputStream fis = new FileInputStream(file)) {
|
||||||
|
ZipEntry zipEntry = new ZipEntry(filename);
|
||||||
|
zipOut.putNextEntry(zipEntry);
|
||||||
|
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = fis.read(buffer)) >= 0) {
|
||||||
|
zipOut.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteDirectory(Path directory) {
|
||||||
|
try {
|
||||||
|
Files.walk(directory)
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.forEach(
|
||||||
|
path -> {
|
||||||
|
try {
|
||||||
|
Files.delete(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error deleting {}: {}", path, e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error walking directory {}: {}", directory, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,30 +44,29 @@ public class RepairController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO")
|
"This endpoint repairs a given PDF file by running qpdf command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request)
|
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
|
||||||
byte[] pdfBytes = null;
|
byte[] pdfBytes = null;
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
try {
|
try {
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("gs");
|
command.add("qpdf");
|
||||||
command.add("-o");
|
command.add("--replace-input"); // Automatically fixes problems it can
|
||||||
command.add(tempOutputFile.toString());
|
command.add("--qdf"); // Linearizes and normalizes PDF structure
|
||||||
command.add("-sDEVICE=pdfwrite");
|
command.add("--object-streams=disable"); // Can help with some corruptions
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
|
pdfBytes = pdfDocumentFactory.loadToBytes(tempInputFile.toFile());
|
||||||
|
|
||||||
// Return the optimized PDF as a response
|
// Return the optimized PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
@@ -78,7 +77,6 @@ public class RepairController {
|
|||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.deleteIfExists(tempInputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
Files.deleteIfExists(tempOutputFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,10 +229,22 @@ public class StampController {
|
|||||||
calculatePositionY(
|
calculatePositionY(
|
||||||
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
|
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
|
||||||
}
|
}
|
||||||
|
// Split the stampText into multiple lines
|
||||||
|
String[] lines = stampText.split("\\\\n");
|
||||||
|
|
||||||
|
// Calculate dynamic line height based on font ascent and descent
|
||||||
|
float ascent = font.getFontDescriptor().getAscent();
|
||||||
|
float descent = font.getFontDescriptor().getDescent();
|
||||||
|
float lineHeight = ((ascent - descent) / 1000) * fontSize;
|
||||||
|
|
||||||
contentStream.beginText();
|
contentStream.beginText();
|
||||||
contentStream.setTextMatrix(Matrix.getRotateInstance(Math.toRadians(rotation), x, y));
|
for (int i = 0; i < lines.length; i++) {
|
||||||
contentStream.showText(stampText);
|
String line = lines[i];
|
||||||
|
// Set the text matrix for each line with rotation
|
||||||
|
contentStream.setTextMatrix(
|
||||||
|
Matrix.getRotateInstance(Math.toRadians(rotation), x, y - (i * lineHeight)));
|
||||||
|
contentStream.showText(line);
|
||||||
|
}
|
||||||
contentStream.endText();
|
contentStream.endText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public class PipelineProcessor {
|
|||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
String apiKey = getApiKeyForUser();
|
String apiKey = getApiKeyForUser();
|
||||||
headers.add("X-API-Key", apiKey);
|
headers.add("X-API-KEY", apiKey);
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
|
||||||
// Create HttpEntity with the body and headers
|
// Create HttpEntity with the body and headers
|
||||||
|
|||||||
@@ -98,10 +98,10 @@ public class CertSignController {
|
|||||||
|
|
||||||
public CreateSignature(KeyStore keystore, char[] pin)
|
public CreateSignature(KeyStore keystore, char[] pin)
|
||||||
throws KeyStoreException,
|
throws KeyStoreException,
|
||||||
UnrecoverableKeyException,
|
UnrecoverableKeyException,
|
||||||
NoSuchAlgorithmException,
|
NoSuchAlgorithmException,
|
||||||
IOException,
|
IOException,
|
||||||
CertificateException {
|
CertificateException {
|
||||||
super(keystore, pin);
|
super(keystore, pin);
|
||||||
ClassPathResource resource = new ClassPathResource("static/images/signature.png");
|
ClassPathResource resource = new ClassPathResource("static/images/signature.png");
|
||||||
try (InputStream is = resource.getInputStream()) {
|
try (InputStream is = resource.getInputStream()) {
|
||||||
@@ -160,7 +160,8 @@ public class CertSignController {
|
|||||||
extState.setNonStrokingAlphaConstant(0.5f);
|
extState.setNonStrokingAlphaConstant(0.5f);
|
||||||
cs.setGraphicsStateParameters(extState);
|
cs.setGraphicsStateParameters(extState);
|
||||||
cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));
|
cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));
|
||||||
PDImageXObject img = PDImageXObject.createFromFileByExtension(logoFile, doc);
|
PDImageXObject img =
|
||||||
|
PDImageXObject.createFromFileByExtension(logoFile, doc);
|
||||||
cs.drawImage(img, 100, 0);
|
cs.drawImage(img, 100, 0);
|
||||||
cs.restoreGraphicsState();
|
cs.restoreGraphicsState();
|
||||||
}
|
}
|
||||||
@@ -208,7 +209,10 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
@Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
|
@Operation(
|
||||||
|
summary = "Sign PDF with a Digital Certificate",
|
||||||
|
description =
|
||||||
|
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile pdf = request.getFileInput();
|
MultipartFile pdf = request.getFileInput();
|
||||||
@@ -238,7 +242,7 @@ public class CertSignController {
|
|||||||
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
|
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
|
||||||
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
|
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
|
||||||
ks.setKeyEntry(
|
ks.setKeyEntry(
|
||||||
"alias", privateKey, password.toCharArray(), new Certificate[] { cert });
|
"alias", privateKey, password.toCharArray(), new Certificate[] {cert});
|
||||||
break;
|
break;
|
||||||
case "PKCS12":
|
case "PKCS12":
|
||||||
ks = KeyStore.getInstance("PKCS12");
|
ks = KeyStore.getInstance("PKCS12");
|
||||||
@@ -310,19 +314,22 @@ public class CertSignController {
|
|||||||
|
|
||||||
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
||||||
throws IOException, OperatorCreationException, PKCSException {
|
throws IOException, OperatorCreationException, PKCSException {
|
||||||
try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
|
try (PEMParser pemParser =
|
||||||
|
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
|
||||||
Object pemObject = pemParser.readObject();
|
Object pemObject = pemParser.readObject();
|
||||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
||||||
PrivateKeyInfo pkInfo;
|
PrivateKeyInfo pkInfo;
|
||||||
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
||||||
InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder()
|
InputDecryptorProvider decProv =
|
||||||
.build(password.toCharArray());
|
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
|
||||||
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
|
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
|
||||||
} else if (pemObject instanceof PEMEncryptedKeyPair) {
|
} else if (pemObject instanceof PEMEncryptedKeyPair) {
|
||||||
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
PEMDecryptorProvider decProv =
|
||||||
pkInfo = ((PEMEncryptedKeyPair) pemObject)
|
new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
||||||
.decryptKeyPair(decProv)
|
pkInfo =
|
||||||
.getPrivateKeyInfo();
|
((PEMEncryptedKeyPair) pemObject)
|
||||||
|
.decryptKeyPair(decProv)
|
||||||
|
.getPrivateKeyInfo();
|
||||||
} else {
|
} else {
|
||||||
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,27 +322,14 @@ public class GetInfoOnPDF {
|
|||||||
PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();
|
PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();
|
||||||
encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter());
|
encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter());
|
||||||
encryption.put("KeyLength", pdfEncryption.getLength());
|
encryption.put("KeyLength", pdfEncryption.getLength());
|
||||||
AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission();
|
|
||||||
if (ap != null) {
|
|
||||||
ObjectNode permissionsNode = objectMapper.createObjectNode();
|
|
||||||
|
|
||||||
permissionsNode.put("CanAssembleDocument", ap.canAssembleDocument());
|
|
||||||
permissionsNode.put("CanExtractContent", ap.canExtractContent());
|
|
||||||
permissionsNode.put(
|
|
||||||
"CanExtractForAccessibility", ap.canExtractForAccessibility());
|
|
||||||
permissionsNode.put("CanFillInForm", ap.canFillInForm());
|
|
||||||
permissionsNode.put("CanModify", ap.canModify());
|
|
||||||
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
|
|
||||||
permissionsNode.put("CanPrint", ap.canPrint());
|
|
||||||
|
|
||||||
encryption.set(
|
|
||||||
"Permissions", permissionsNode); // set the node under "Permissions"
|
|
||||||
}
|
|
||||||
// Add other encryption-related properties as needed
|
// Add other encryption-related properties as needed
|
||||||
} else {
|
} else {
|
||||||
encryption.put("IsEncrypted", false);
|
encryption.put("IsEncrypted", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjectNode permissionsNode = objectMapper.createObjectNode();
|
||||||
|
setNodePermissions(pdfBoxDoc, permissionsNode);
|
||||||
|
|
||||||
ObjectNode pageInfoParent = objectMapper.createObjectNode();
|
ObjectNode pageInfoParent = objectMapper.createObjectNode();
|
||||||
for (int pageNum = 0; pageNum < pdfBoxDoc.getNumberOfPages(); pageNum++) {
|
for (int pageNum = 0; pageNum < pdfBoxDoc.getNumberOfPages(); pageNum++) {
|
||||||
ObjectNode pageInfo = objectMapper.createObjectNode();
|
ObjectNode pageInfo = objectMapper.createObjectNode();
|
||||||
@@ -584,6 +571,7 @@ public class GetInfoOnPDF {
|
|||||||
jsonOutput.set("DocumentInfo", docInfoNode);
|
jsonOutput.set("DocumentInfo", docInfoNode);
|
||||||
jsonOutput.set("Compliancy", compliancy);
|
jsonOutput.set("Compliancy", compliancy);
|
||||||
jsonOutput.set("Encryption", encryption);
|
jsonOutput.set("Encryption", encryption);
|
||||||
|
jsonOutput.set("Permissions", permissionsNode); // set the node under "Permissions"
|
||||||
jsonOutput.set("Other", other);
|
jsonOutput.set("Other", other);
|
||||||
jsonOutput.set("PerPageInfo", pageInfoParent);
|
jsonOutput.set("PerPageInfo", pageInfoParent);
|
||||||
|
|
||||||
@@ -602,6 +590,24 @@ public class GetInfoOnPDF {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setNodePermissions(PDDocument pdfBoxDoc, ObjectNode permissionsNode) {
|
||||||
|
AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission();
|
||||||
|
|
||||||
|
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||||
|
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
||||||
|
permissionsNode.put(
|
||||||
|
"Extracting for accessibility",
|
||||||
|
getPermissionState(ap.canExtractForAccessibility()));
|
||||||
|
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||||
|
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||||
|
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||||
|
permissionsNode.put("Printing", getPermissionState(ap.canPrint()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPermissionState(boolean state) {
|
||||||
|
return state ? "Allowed" : "Not Allowed";
|
||||||
|
}
|
||||||
|
|
||||||
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
||||||
if (outline == null) return;
|
if (outline == null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -39,10 +39,7 @@ public class PasswordController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(
|
@Operation(summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
|
||||||
summary = "Remove password from a PDF file",
|
|
||||||
description =
|
|
||||||
"This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
|
|
||||||
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@@ -52,15 +49,12 @@ public class PasswordController {
|
|||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_password_removed.pdf");
|
+ "_password_removed.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
@Operation(
|
@Operation(summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
|
||||||
summary = "Add password to a PDF file",
|
|
||||||
description =
|
|
||||||
"This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
|
|
||||||
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@@ -98,12 +92,12 @@ public class PasswordController {
|
|||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_permissions.pdf");
|
+ "_permissions.pdf");
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_passworded.pdf");
|
+ "_passworded.pdf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cms.CMSProcessable;
|
||||||
|
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||||
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
|
import org.bouncycastle.cms.SignerInformation;
|
||||||
|
import org.bouncycastle.cms.SignerInformationStore;
|
||||||
|
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||||
|
import org.bouncycastle.util.Store;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.security.SignatureValidationRequest;
|
||||||
|
import stirling.software.SPDF.model.api.security.SignatureValidationResult;
|
||||||
|
import stirling.software.SPDF.service.CertificateValidationService;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
|
public class ValidateSignatureController {
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
private final CertificateValidationService certValidationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ValidateSignatureController(
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory,
|
||||||
|
CertificateValidationService certValidationService) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
this.certValidationService = certValidationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Validate PDF Digital Signature",
|
||||||
|
description =
|
||||||
|
"Validates the digital signatures in a PDF file against default or custom certificates. Input:PDF Output:JSON Type:SISO")
|
||||||
|
@PostMapping(value = "/validate-signature")
|
||||||
|
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
|
||||||
|
@ModelAttribute SignatureValidationRequest request) throws IOException {
|
||||||
|
List<SignatureValidationResult> results = new ArrayList<>();
|
||||||
|
MultipartFile file = request.getFileInput();
|
||||||
|
|
||||||
|
// Load custom certificate if provided
|
||||||
|
X509Certificate customCert = null;
|
||||||
|
if (request.getCertFile() != null && !request.getCertFile().isEmpty()) {
|
||||||
|
try (ByteArrayInputStream certStream =
|
||||||
|
new ByteArrayInputStream(request.getCertFile().getBytes())) {
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
customCert = (X509Certificate) cf.generateCertificate(certStream);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
throw new RuntimeException("Invalid certificate file: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PDDocument document = pdfDocumentFactory.load(file.getInputStream())) {
|
||||||
|
List<PDSignature> signatures = document.getSignatureDictionaries();
|
||||||
|
|
||||||
|
for (PDSignature sig : signatures) {
|
||||||
|
SignatureValidationResult result = new SignatureValidationResult();
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] signedContent = sig.getSignedContent(file.getInputStream());
|
||||||
|
byte[] signatureBytes = sig.getContents(file.getInputStream());
|
||||||
|
|
||||||
|
CMSProcessable content = new CMSProcessableByteArray(signedContent);
|
||||||
|
CMSSignedData signedData = new CMSSignedData(content, signatureBytes);
|
||||||
|
|
||||||
|
Store<X509CertificateHolder> certStore = signedData.getCertificates();
|
||||||
|
SignerInformationStore signerStore = signedData.getSignerInfos();
|
||||||
|
|
||||||
|
for (SignerInformation signer : signerStore.getSigners()) {
|
||||||
|
X509CertificateHolder certHolder =
|
||||||
|
(X509CertificateHolder)
|
||||||
|
certStore.getMatches(signer.getSID()).iterator().next();
|
||||||
|
X509Certificate cert =
|
||||||
|
new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||||
|
|
||||||
|
boolean isValid =
|
||||||
|
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
||||||
|
result.setValid(isValid);
|
||||||
|
|
||||||
|
// Additional validations
|
||||||
|
result.setChainValid(
|
||||||
|
customCert != null
|
||||||
|
? certValidationService
|
||||||
|
.validateCertificateChainWithCustomCert(
|
||||||
|
cert, customCert)
|
||||||
|
: certValidationService.validateCertificateChain(cert));
|
||||||
|
|
||||||
|
result.setTrustValid(
|
||||||
|
customCert != null
|
||||||
|
? certValidationService.validateTrustWithCustomCert(
|
||||||
|
cert, customCert)
|
||||||
|
: certValidationService.validateTrustStore(cert));
|
||||||
|
|
||||||
|
result.setNotRevoked(!certValidationService.isRevoked(cert));
|
||||||
|
result.setNotExpired(!cert.getNotAfter().before(new Date()));
|
||||||
|
|
||||||
|
// Set basic signature info
|
||||||
|
result.setSignerName(sig.getName());
|
||||||
|
result.setSignatureDate(sig.getSignDate().getTime().toString());
|
||||||
|
result.setReason(sig.getReason());
|
||||||
|
result.setLocation(sig.getLocation());
|
||||||
|
|
||||||
|
// Set new certificate details
|
||||||
|
result.setIssuerDN(cert.getIssuerX500Principal().getName());
|
||||||
|
result.setSubjectDN(cert.getSubjectX500Principal().getName());
|
||||||
|
result.setSerialNumber(cert.getSerialNumber().toString(16)); // Hex format
|
||||||
|
result.setValidFrom(cert.getNotBefore().toString());
|
||||||
|
result.setValidUntil(cert.getNotAfter().toString());
|
||||||
|
result.setSignatureAlgorithm(cert.getSigAlgName());
|
||||||
|
|
||||||
|
// Get key size (if possible)
|
||||||
|
try {
|
||||||
|
result.setKeySize(
|
||||||
|
((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// If not RSA or error, set to 0
|
||||||
|
result.setKeySize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setVersion(String.valueOf(cert.getVersion()));
|
||||||
|
|
||||||
|
// Set key usage
|
||||||
|
List<String> keyUsages = new ArrayList<>();
|
||||||
|
boolean[] keyUsageFlags = cert.getKeyUsage();
|
||||||
|
if (keyUsageFlags != null) {
|
||||||
|
String[] keyUsageLabels = {
|
||||||
|
"Digital Signature", "Non-Repudiation", "Key Encipherment",
|
||||||
|
"Data Encipherment", "Key Agreement", "Certificate Signing",
|
||||||
|
"CRL Signing", "Encipher Only", "Decipher Only"
|
||||||
|
};
|
||||||
|
for (int i = 0; i < keyUsageFlags.length; i++) {
|
||||||
|
if (keyUsageFlags[i]) {
|
||||||
|
keyUsages.add(keyUsageLabels[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.setKeyUsages(keyUsages);
|
||||||
|
|
||||||
|
// Check if self-signed
|
||||||
|
result.setSelfSigned(
|
||||||
|
cert.getSubjectX500Principal()
|
||||||
|
.equals(cert.getIssuerX500Principal()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.setValid(false);
|
||||||
|
result.setErrorMessage("Signature validation failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
results.add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ public class WatermarkController {
|
|||||||
float opacity = request.getOpacity();
|
float opacity = request.getOpacity();
|
||||||
int widthSpacer = request.getWidthSpacer();
|
int widthSpacer = request.getWidthSpacer();
|
||||||
int heightSpacer = request.getHeightSpacer();
|
int heightSpacer = request.getHeightSpacer();
|
||||||
|
String customColor = request.getCustomColor();
|
||||||
boolean convertPdfToImage = request.isConvertPDFToImage();
|
boolean convertPdfToImage = request.isConvertPDFToImage();
|
||||||
|
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
@@ -97,7 +98,8 @@ public class WatermarkController {
|
|||||||
widthSpacer,
|
widthSpacer,
|
||||||
heightSpacer,
|
heightSpacer,
|
||||||
fontSize,
|
fontSize,
|
||||||
alphabet);
|
alphabet,
|
||||||
|
customColor);
|
||||||
} else if ("image".equalsIgnoreCase(watermarkType)) {
|
} else if ("image".equalsIgnoreCase(watermarkType)) {
|
||||||
addImageWatermark(
|
addImageWatermark(
|
||||||
contentStream,
|
contentStream,
|
||||||
@@ -136,7 +138,8 @@ public class WatermarkController {
|
|||||||
int widthSpacer,
|
int widthSpacer,
|
||||||
int heightSpacer,
|
int heightSpacer,
|
||||||
float fontSize,
|
float fontSize,
|
||||||
String alphabet)
|
String alphabet,
|
||||||
|
String colorString)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String resourceDir = "";
|
String resourceDir = "";
|
||||||
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||||
@@ -173,7 +176,18 @@ public class WatermarkController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentStream.setFont(font, fontSize);
|
contentStream.setFont(font, fontSize);
|
||||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
|
||||||
|
Color redactColor;
|
||||||
|
try {
|
||||||
|
if (!colorString.startsWith("#")) {
|
||||||
|
colorString = "#" + colorString;
|
||||||
|
}
|
||||||
|
redactColor = Color.decode(colorString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
|
||||||
|
redactColor = Color.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
String[] textLines = watermarkText.split("\\\\n");
|
String[] textLines = watermarkText.split("\\\\n");
|
||||||
float maxLineWidth = 0;
|
float maxLineWidth = 0;
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ public class HomeWebController {
|
|||||||
return "licenses";
|
return "licenses";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/releases")
|
||||||
|
public String getReleaseNotes(Model model) {
|
||||||
|
return "releases";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String home(Model model) {
|
public String home(Model model) {
|
||||||
model.addAttribute("currentPage", "home");
|
model.addAttribute("currentPage", "home");
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ public class SecurityWebController {
|
|||||||
return "security/cert-sign";
|
return "security/cert-sign";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/validate-signature")
|
||||||
|
@Hidden
|
||||||
|
public String certSignVerifyForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "validate-signature");
|
||||||
|
return "security/validate-signature";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/remove-cert-sign")
|
@GetMapping("/remove-cert-sign")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String certUnSignForm(Model model) {
|
public String certUnSignForm(Model model) {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public class ApplicationProperties {
|
|||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
|
private String customGlobalAPIKey;
|
||||||
|
|
||||||
public Boolean isAltLogin() {
|
public Boolean isAltLogin() {
|
||||||
return saml2.getEnabled() || oauth2.getEnabled();
|
return saml2.getEnabled() || oauth2.getEnabled();
|
||||||
@@ -285,6 +286,7 @@ public class ApplicationProperties {
|
|||||||
public static class AutomaticallyGenerated {
|
public static class AutomaticallyGenerated {
|
||||||
@ToString.Exclude private String key;
|
@ToString.Exclude private String key;
|
||||||
private String UUID;
|
private String UUID;
|
||||||
|
private String appVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -320,12 +322,20 @@ public class ApplicationProperties {
|
|||||||
public static class SessionLimit {
|
public static class SessionLimit {
|
||||||
private int libreOfficeSessionLimit;
|
private int libreOfficeSessionLimit;
|
||||||
private int pdfToHtmlSessionLimit;
|
private int pdfToHtmlSessionLimit;
|
||||||
private int ocrMyPdfSessionLimit;
|
|
||||||
private int pythonOpenCvSessionLimit;
|
private int pythonOpenCvSessionLimit;
|
||||||
private int ghostScriptSessionLimit;
|
|
||||||
private int weasyPrintSessionLimit;
|
private int weasyPrintSessionLimit;
|
||||||
private int installAppSessionLimit;
|
private int installAppSessionLimit;
|
||||||
private int calibreSessionLimit;
|
private int calibreSessionLimit;
|
||||||
|
private int qpdfSessionLimit;
|
||||||
|
private int tesseractSessionLimit;
|
||||||
|
|
||||||
|
public int getQpdfSessionLimit() {
|
||||||
|
return qpdfSessionLimit > 0 ? qpdfSessionLimit : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTesseractSessionLimit() {
|
||||||
|
return tesseractSessionLimit > 0 ? tesseractSessionLimit : 1;
|
||||||
|
}
|
||||||
|
|
||||||
public int getLibreOfficeSessionLimit() {
|
public int getLibreOfficeSessionLimit() {
|
||||||
return libreOfficeSessionLimit > 0 ? libreOfficeSessionLimit : 1;
|
return libreOfficeSessionLimit > 0 ? libreOfficeSessionLimit : 1;
|
||||||
@@ -335,18 +345,10 @@ public class ApplicationProperties {
|
|||||||
return pdfToHtmlSessionLimit > 0 ? pdfToHtmlSessionLimit : 1;
|
return pdfToHtmlSessionLimit > 0 ? pdfToHtmlSessionLimit : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOcrMyPdfSessionLimit() {
|
|
||||||
return ocrMyPdfSessionLimit > 0 ? ocrMyPdfSessionLimit : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPythonOpenCvSessionLimit() {
|
public int getPythonOpenCvSessionLimit() {
|
||||||
return pythonOpenCvSessionLimit > 0 ? pythonOpenCvSessionLimit : 8;
|
return pythonOpenCvSessionLimit > 0 ? pythonOpenCvSessionLimit : 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGhostScriptSessionLimit() {
|
|
||||||
return ghostScriptSessionLimit > 0 ? ghostScriptSessionLimit : 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWeasyPrintSessionLimit() {
|
public int getWeasyPrintSessionLimit() {
|
||||||
return weasyPrintSessionLimit > 0 ? weasyPrintSessionLimit : 16;
|
return weasyPrintSessionLimit > 0 ? weasyPrintSessionLimit : 16;
|
||||||
}
|
}
|
||||||
@@ -364,12 +366,20 @@ public class ApplicationProperties {
|
|||||||
public static class TimeoutMinutes {
|
public static class TimeoutMinutes {
|
||||||
private long libreOfficeTimeoutMinutes;
|
private long libreOfficeTimeoutMinutes;
|
||||||
private long pdfToHtmlTimeoutMinutes;
|
private long pdfToHtmlTimeoutMinutes;
|
||||||
private long ocrMyPdfTimeoutMinutes;
|
|
||||||
private long pythonOpenCvTimeoutMinutes;
|
private long pythonOpenCvTimeoutMinutes;
|
||||||
private long ghostScriptTimeoutMinutes;
|
|
||||||
private long weasyPrintTimeoutMinutes;
|
private long weasyPrintTimeoutMinutes;
|
||||||
private long installAppTimeoutMinutes;
|
private long installAppTimeoutMinutes;
|
||||||
private long calibreTimeoutMinutes;
|
private long calibreTimeoutMinutes;
|
||||||
|
private long tesseractTimeoutMinutes;
|
||||||
|
private long qpdfTimeoutMinutes;
|
||||||
|
|
||||||
|
public long getTesseractTimeoutMinutes() {
|
||||||
|
return tesseractTimeoutMinutes > 0 ? tesseractTimeoutMinutes : 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getQpdfTimeoutMinutes() {
|
||||||
|
return qpdfTimeoutMinutes > 0 ? qpdfTimeoutMinutes : 30;
|
||||||
|
}
|
||||||
|
|
||||||
public long getLibreOfficeTimeoutMinutes() {
|
public long getLibreOfficeTimeoutMinutes() {
|
||||||
return libreOfficeTimeoutMinutes > 0 ? libreOfficeTimeoutMinutes : 30;
|
return libreOfficeTimeoutMinutes > 0 ? libreOfficeTimeoutMinutes : 30;
|
||||||
@@ -379,18 +389,10 @@ public class ApplicationProperties {
|
|||||||
return pdfToHtmlTimeoutMinutes > 0 ? pdfToHtmlTimeoutMinutes : 20;
|
return pdfToHtmlTimeoutMinutes > 0 ? pdfToHtmlTimeoutMinutes : 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getOcrMyPdfTimeoutMinutes() {
|
|
||||||
return ocrMyPdfTimeoutMinutes > 0 ? ocrMyPdfTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getPythonOpenCvTimeoutMinutes() {
|
public long getPythonOpenCvTimeoutMinutes() {
|
||||||
return pythonOpenCvTimeoutMinutes > 0 ? pythonOpenCvTimeoutMinutes : 30;
|
return pythonOpenCvTimeoutMinutes > 0 ? pythonOpenCvTimeoutMinutes : 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getGhostScriptTimeoutMinutes() {
|
|
||||||
return ghostScriptTimeoutMinutes > 0 ? ghostScriptTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getWeasyPrintTimeoutMinutes() {
|
public long getWeasyPrintTimeoutMinutes() {
|
||||||
return weasyPrintTimeoutMinutes > 0 ? weasyPrintTimeoutMinutes : 30;
|
return weasyPrintTimeoutMinutes > 0 ? weasyPrintTimeoutMinutes : 30;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package stirling.software.SPDF.model;
|
|||||||
|
|
||||||
public enum AuthenticationType {
|
public enum AuthenticationType {
|
||||||
WEB,
|
WEB,
|
||||||
OAUTH2
|
SSO
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,15 @@ public class OptimizePdfRequest extends PDFFile {
|
|||||||
|
|
||||||
@Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.")
|
@Schema(description = "The expected output size, e.g. '100MB', '25KB', etc.")
|
||||||
private String expectedOutputSize;
|
private String expectedOutputSize;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Whether to linearize the PDF for faster web viewing. Default is false.",
|
||||||
|
defaultValue = "false")
|
||||||
|
private Boolean linearize = false;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Whether to normalize the PDF content for better compatibility. Default is true.",
|
||||||
|
defaultValue = "true")
|
||||||
|
private Boolean normalize = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,6 @@ public class ProcessPdfWithOcrRequest extends PDFFile {
|
|||||||
@Schema(description = "List of languages to use in OCR processing")
|
@Schema(description = "List of languages to use in OCR processing")
|
||||||
private List<String> languages;
|
private List<String> languages;
|
||||||
|
|
||||||
@Schema(description = "Include OCR text in a sidecar text file if set to true")
|
|
||||||
private boolean sidecar;
|
|
||||||
|
|
||||||
@Schema(description = "Deskew the input file if set to true")
|
|
||||||
private boolean deskew;
|
|
||||||
|
|
||||||
@Schema(description = "Clean the input file if set to true")
|
|
||||||
private boolean clean;
|
|
||||||
|
|
||||||
@Schema(description = "Clean the final output if set to true")
|
|
||||||
private boolean cleanFinal;
|
|
||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'",
|
description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'",
|
||||||
allowableValues = {"skip-text", "force-ocr", "Normal"})
|
allowableValues = {"skip-text", "force-ocr", "Normal"})
|
||||||
@@ -37,7 +25,4 @@ public class ProcessPdfWithOcrRequest extends PDFFile {
|
|||||||
allowableValues = {"hocr", "sandwich"},
|
allowableValues = {"hocr", "sandwich"},
|
||||||
defaultValue = "hocr")
|
defaultValue = "hocr")
|
||||||
private String ocrRenderType = "hocr";
|
private String ocrRenderType = "hocr";
|
||||||
|
|
||||||
@Schema(description = "Remove images from the output PDF if set to true")
|
|
||||||
private boolean removeImagesAfter;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ public class AddWatermarkRequest extends PDFFile {
|
|||||||
@Schema(description = "The height spacer between watermark elements", example = "50")
|
@Schema(description = "The height spacer between watermark elements", example = "50")
|
||||||
private int heightSpacer;
|
private int heightSpacer;
|
||||||
|
|
||||||
|
@Schema(description = "The color for watermark", defaultValue = "#d3d3d3")
|
||||||
|
private String customColor = "#d3d3d3";
|
||||||
|
|
||||||
@Schema(description = "Convert the redacted PDF to an image", defaultValue = "false")
|
@Schema(description = "Convert the redacted PDF to an image", defaultValue = "false")
|
||||||
private boolean convertPDFToImage;
|
private boolean convertPDFToImage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package stirling.software.SPDF.model.api.security;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class SignatureValidationRequest extends PDFFile {
|
||||||
|
|
||||||
|
@Schema(description = "(Optional) file to compare PDF cert signatures against x.509 format")
|
||||||
|
private MultipartFile certFile;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package stirling.software.SPDF.model.api.security;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SignatureValidationResult {
|
||||||
|
private boolean valid;
|
||||||
|
private String signerName;
|
||||||
|
private String signatureDate;
|
||||||
|
private String reason;
|
||||||
|
private String location;
|
||||||
|
private String errorMessage;
|
||||||
|
private boolean chainValid;
|
||||||
|
private boolean trustValid;
|
||||||
|
private boolean notExpired;
|
||||||
|
private boolean notRevoked;
|
||||||
|
|
||||||
|
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||||
|
private String subjectDN; // Certificate subject's Distinguished Name
|
||||||
|
private String serialNumber; // Certificate serial number
|
||||||
|
private String validFrom; // Certificate validity start date
|
||||||
|
private String validUntil; // Certificate validity end date
|
||||||
|
private String signatureAlgorithm; // Algorithm used for signing
|
||||||
|
private int keySize; // Key size in bits
|
||||||
|
private String version; // Certificate version
|
||||||
|
private List<String> keyUsages; // List of key usage purposes
|
||||||
|
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.repository;
|
package stirling.software.SPDF.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -19,4 +20,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
|||||||
Optional<User> findByUsername(String username);
|
Optional<User> findByUsername(String username);
|
||||||
|
|
||||||
Optional<User> findByApiKey(String apiKey);
|
Optional<User> findByApiKey(String apiKey);
|
||||||
|
|
||||||
|
List<User> findByAuthenticationTypeIgnoreCase(String authenticationType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.cert.CertPath;
|
||||||
|
import java.security.cert.CertPathValidator;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
|
import java.security.cert.PKIXParameters;
|
||||||
|
import java.security.cert.TrustAnchor;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import io.github.pixee.security.BoundedLineReader;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CertificateValidationService {
|
||||||
|
private KeyStore trustStore;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
private void initializeTrustStore() throws Exception {
|
||||||
|
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
trustStore.load(null, null);
|
||||||
|
loadMozillaCertificates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMozillaCertificates() throws Exception {
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/certdata.txt")) {
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||||
|
String line;
|
||||||
|
StringBuilder certData = new StringBuilder();
|
||||||
|
boolean inCert = false;
|
||||||
|
int certCount = 0;
|
||||||
|
|
||||||
|
while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {
|
||||||
|
if (line.startsWith("CKA_VALUE MULTILINE_OCTAL")) {
|
||||||
|
inCert = true;
|
||||||
|
certData = new StringBuilder();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inCert) {
|
||||||
|
if ("END".equals(line)) {
|
||||||
|
inCert = false;
|
||||||
|
byte[] certBytes = parseOctalData(certData.toString());
|
||||||
|
if (certBytes != null) {
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
X509Certificate cert =
|
||||||
|
(X509Certificate)
|
||||||
|
cf.generateCertificate(
|
||||||
|
new ByteArrayInputStream(certBytes));
|
||||||
|
trustStore.setCertificateEntry("mozilla-cert-" + certCount++, cert);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
certData.append(line).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] parseOctalData(String data) {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
String[] tokens = data.split("\\\\");
|
||||||
|
for (String token : tokens) {
|
||||||
|
token = token.trim();
|
||||||
|
if (!token.isEmpty()) {
|
||||||
|
baos.write(Integer.parseInt(token, 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baos.toByteArray();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateCertificateChain(X509Certificate cert) {
|
||||||
|
try {
|
||||||
|
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||||
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
|
List<X509Certificate> certList = Arrays.asList(cert);
|
||||||
|
CertPath certPath = cf.generateCertPath(certList);
|
||||||
|
|
||||||
|
Set<TrustAnchor> anchors = new HashSet<>();
|
||||||
|
Enumeration<String> aliases = trustStore.aliases();
|
||||||
|
while (aliases.hasMoreElements()) {
|
||||||
|
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
||||||
|
if (trustCert instanceof X509Certificate) {
|
||||||
|
anchors.add(new TrustAnchor((X509Certificate) trustCert, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PKIXParameters params = new PKIXParameters(anchors);
|
||||||
|
params.setRevocationEnabled(false);
|
||||||
|
validator.validate(certPath, params);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateTrustStore(X509Certificate cert) {
|
||||||
|
try {
|
||||||
|
Enumeration<String> aliases = trustStore.aliases();
|
||||||
|
while (aliases.hasMoreElements()) {
|
||||||
|
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
||||||
|
if (trustCert instanceof X509Certificate && cert.equals(trustCert)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRevoked(X509Certificate cert) {
|
||||||
|
try {
|
||||||
|
cert.checkValidity();
|
||||||
|
return false;
|
||||||
|
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateCertificateChainWithCustomCert(
|
||||||
|
X509Certificate cert, X509Certificate customCert) {
|
||||||
|
try {
|
||||||
|
cert.verify(customCert.getPublicKey());
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateTrustWithCustomCert(X509Certificate cert, X509Certificate customCert) {
|
||||||
|
try {
|
||||||
|
// Compare the issuer of the signature certificate with the custom certificate
|
||||||
|
return cert.getIssuerX500Principal().equals(customCert.getSubjectX500Principal());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ public class MetricsAggregatorService {
|
|||||||
this.postHogService = postHogService;
|
this.postHogService = postHogService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedRate = 1800000) // Run every 30 minutes
|
@Scheduled(fixedRate = 7200000) // Run every 2 hours
|
||||||
public void aggregateAndSendMetrics() {
|
public void aggregateAndSendMetrics() {
|
||||||
Map<String, Object> metrics = new HashMap<>();
|
Map<String, Object> metrics = new HashMap<>();
|
||||||
Search.in(meterRegistry)
|
Search.in(meterRegistry)
|
||||||
@@ -34,17 +34,22 @@ public class MetricsAggregatorService {
|
|||||||
counter -> {
|
counter -> {
|
||||||
String method = counter.getId().getTag("method");
|
String method = counter.getId().getTag("method");
|
||||||
String uri = counter.getId().getTag("uri");
|
String uri = counter.getId().getTag("uri");
|
||||||
|
|
||||||
// Skip if either method or uri is null
|
// Skip if either method or uri is null
|
||||||
if (method == null || uri == null) {
|
if (method == null || uri == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!method.equals("GET") && !method.equals("POST")) {
|
||||||
String key = String.format(
|
return;
|
||||||
"http_requests_%s_%s",
|
}
|
||||||
method,
|
// Skip URIs that are 2 characters or shorter
|
||||||
uri.replace("/", "_")
|
if (uri.length() <= 2) {
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key =
|
||||||
|
String.format(
|
||||||
|
"http_requests_%s_%s", method, uri.replace("/", "_"));
|
||||||
|
|
||||||
double currentCount = counter.count();
|
double currentCount = counter.count();
|
||||||
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
||||||
|
|||||||
@@ -17,15 +17,18 @@ public class PdfMetadataService {
|
|||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final String stirlingPDFLabel;
|
private final String stirlingPDFLabel;
|
||||||
private final UserServiceInterface userService;
|
private final UserServiceInterface userService;
|
||||||
|
private final boolean runningEE;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PdfMetadataService(
|
public PdfMetadataService(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@Qualifier("StirlingPDFLabel") String stirlingPDFLabel,
|
@Qualifier("StirlingPDFLabel") String stirlingPDFLabel,
|
||||||
|
@Qualifier("runningEE") boolean runningEE,
|
||||||
@Autowired(required = false) UserServiceInterface userService) {
|
@Autowired(required = false) UserServiceInterface userService) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.stirlingPDFLabel = stirlingPDFLabel;
|
this.stirlingPDFLabel = stirlingPDFLabel;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
|
this.runningEE = runningEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
|
public PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
|
||||||
@@ -61,10 +64,8 @@ public class PdfMetadataService {
|
|||||||
|
|
||||||
String creator = stirlingPDFLabel;
|
String creator = stirlingPDFLabel;
|
||||||
|
|
||||||
if (applicationProperties
|
if (applicationProperties.getEnterpriseEdition().getCustomMetadata().isAutoUpdateMetadata()
|
||||||
.getEnterpriseEdition()
|
&& runningEE) {
|
||||||
.getCustomMetadata()
|
|
||||||
.isAutoUpdateMetadata()) {
|
|
||||||
|
|
||||||
creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
|
creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
|
||||||
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
|
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
|
||||||
@@ -83,10 +84,8 @@ public class PdfMetadataService {
|
|||||||
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
|
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
|
||||||
|
|
||||||
String author = pdfMetadata.getAuthor();
|
String author = pdfMetadata.getAuthor();
|
||||||
if (applicationProperties
|
if (applicationProperties.getEnterpriseEdition().getCustomMetadata().isAutoUpdateMetadata()
|
||||||
.getEnterpriseEdition()
|
&& runningEE) {
|
||||||
.getCustomMetadata()
|
|
||||||
.isAutoUpdateMetadata()) {
|
|
||||||
author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
|
author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
|
||||||
|
|
||||||
if (userService != null) {
|
if (userService != null) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class PostHogService {
|
|||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final UserServiceInterface userService;
|
private final UserServiceInterface userService;
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PostHogService(
|
public PostHogService(
|
||||||
PostHog postHog,
|
PostHog postHog,
|
||||||
@@ -71,16 +71,16 @@ public class PostHogService {
|
|||||||
Map<String, Object> metrics = new HashMap<>();
|
Map<String, Object> metrics = new HashMap<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//Application version
|
// Application version
|
||||||
metrics.put("app_version", appVersion);
|
metrics.put("app_version", appVersion);
|
||||||
String deploymentType = "JAR"; // default
|
String deploymentType = "JAR"; // default
|
||||||
if ("true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"))) {
|
if ("true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"))) {
|
||||||
deploymentType = "EXE";
|
deploymentType = "EXE";
|
||||||
} else if (isRunningInDocker()) {
|
} else if (isRunningInDocker()) {
|
||||||
deploymentType = "DOCKER";
|
deploymentType = "DOCKER";
|
||||||
}
|
}
|
||||||
metrics.put("deployment_type", deploymentType);
|
metrics.put("deployment_type", deploymentType);
|
||||||
|
|
||||||
// System info
|
// System info
|
||||||
metrics.put("os_name", System.getProperty("os.name"));
|
metrics.put("os_name", System.getProperty("os.name"));
|
||||||
metrics.put("os_version", System.getProperty("os.version"));
|
metrics.put("os_version", System.getProperty("os.version"));
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class FileToPdf {
|
|||||||
new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) {
|
new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) {
|
||||||
ZipEntry entry = zipIn.getNextEntry();
|
ZipEntry entry = zipIn.getNextEntry();
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
Path filePath = tempUnzippedDir.resolve(entry.getName());
|
Path filePath = tempUnzippedDir.resolve(sanitizeZipFilename(entry.getName()));
|
||||||
if (!entry.isDirectory()) {
|
if (!entry.isDirectory()) {
|
||||||
Files.createDirectories(filePath.getParent());
|
Files.createDirectories(filePath.getParent());
|
||||||
if (entry.getName().toLowerCase().endsWith(".html")
|
if (entry.getName().toLowerCase().endsWith(".html")
|
||||||
@@ -175,7 +175,7 @@ public class FileToPdf {
|
|||||||
ZipSecurity.createHardenedInputStream(new ByteArrayInputStream(fileBytes))) {
|
ZipSecurity.createHardenedInputStream(new ByteArrayInputStream(fileBytes))) {
|
||||||
ZipEntry entry = zipIn.getNextEntry();
|
ZipEntry entry = zipIn.getNextEntry();
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
Path filePath = tempDirectory.resolve(entry.getName());
|
Path filePath = tempDirectory.resolve(sanitizeZipFilename(entry.getName()));
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
Files.createDirectories(filePath); // Explicitly create the directory structure
|
Files.createDirectories(filePath); // Explicitly create the directory structure
|
||||||
} else {
|
} else {
|
||||||
@@ -241,4 +241,14 @@ public class FileToPdf {
|
|||||||
Files.deleteIfExists(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String sanitizeZipFilename(String entryName) {
|
||||||
|
if (entryName == null || entryName.trim().isEmpty()) {
|
||||||
|
return entryName;
|
||||||
|
}
|
||||||
|
while (entryName.contains("../") || entryName.contains("..\\")) {
|
||||||
|
entryName = entryName.replace("../", "").replace("..\\", "");
|
||||||
|
}
|
||||||
|
return entryName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,15 +88,45 @@ public class GeneralUtils {
|
|||||||
|
|
||||||
public static boolean isURLReachable(String urlStr) {
|
public static boolean isURLReachable(String urlStr) {
|
||||||
try {
|
try {
|
||||||
|
// Parse the URL
|
||||||
URL url = URI.create(urlStr).toURL();
|
URL url = URI.create(urlStr).toURL();
|
||||||
|
|
||||||
|
// Allow only http and https protocols
|
||||||
|
String protocol = url.getProtocol();
|
||||||
|
if (!protocol.equals("http") && !protocol.equals("https")) {
|
||||||
|
return false; // Disallow other protocols
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the host is a local address
|
||||||
|
String host = url.getHost();
|
||||||
|
if (isLocalAddress(host)) {
|
||||||
|
return false; // Exclude local addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the URL is reachable
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setRequestMethod("HEAD");
|
connection.setRequestMethod("HEAD");
|
||||||
|
// connection.setConnectTimeout(5000); // Set connection timeout
|
||||||
|
// connection.setReadTimeout(5000); // Set read timeout
|
||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
return (200 <= responseCode && responseCode <= 399);
|
return (200 <= responseCode && responseCode <= 399);
|
||||||
} catch (MalformedURLException e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false; // Return false in case of any exception
|
||||||
} catch (IOException e) {
|
}
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
private static boolean isLocalAddress(String host) {
|
||||||
|
try {
|
||||||
|
// Resolve DNS to IP address
|
||||||
|
InetAddress address = InetAddress.getByName(host);
|
||||||
|
|
||||||
|
// Check for local addresses
|
||||||
|
return address.isAnyLocalAddress() || // Matches 0.0.0.0 or similar
|
||||||
|
address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1
|
||||||
|
address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to 172.31.x.x
|
||||||
|
address.getHostAddress().startsWith("fe80:"); // Matches link-local IPv6 addresses
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false; // Return false for invalid or unresolved addresses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,6 +319,10 @@ public class GeneralUtils {
|
|||||||
saveKeyToConfig(id, key, true);
|
saveKeyToConfig(id, key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void saveKeyToConfig(String id, boolean key) throws IOException {
|
||||||
|
saveKeyToConfig(id, key, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||||
@@ -307,6 +341,24 @@ public class GeneralUtils {
|
|||||||
settingsYml.save();
|
settingsYml.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
|
||||||
|
throws IOException {
|
||||||
|
Path path = Paths.get("configs", "settings.yml");
|
||||||
|
|
||||||
|
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||||
|
DumperOptions yamlOptionssettingsYml =
|
||||||
|
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||||
|
yamlOptionssettingsYml.setSplitLines(false);
|
||||||
|
|
||||||
|
settingsYml.loadWithComments();
|
||||||
|
|
||||||
|
YamlFileWrapper writer = settingsYml.path(id).set(key);
|
||||||
|
if (autoGenerated) {
|
||||||
|
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||||
|
}
|
||||||
|
settingsYml.save();
|
||||||
|
}
|
||||||
|
|
||||||
public static String generateMachineFingerprint() {
|
public static String generateMachineFingerprint() {
|
||||||
try {
|
try {
|
||||||
// Get the MAC address
|
// Get the MAC address
|
||||||
@@ -349,4 +401,33 @@ public class GeneralUtils {
|
|||||||
return "GenericID";
|
return "GenericID";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
|
||||||
|
if (currentVersion == null || compareVersion == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split versions into components
|
||||||
|
String[] current = currentVersion.split("\\.");
|
||||||
|
String[] compare = compareVersion.split("\\.");
|
||||||
|
|
||||||
|
// Get the length of the shorter version array
|
||||||
|
int length = Math.min(current.length, compare.length);
|
||||||
|
|
||||||
|
// Compare each component
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int currentPart = Integer.parseInt(current[i]);
|
||||||
|
int comparePart = Integer.parseInt(compare[i]);
|
||||||
|
|
||||||
|
if (currentPart > comparePart) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (currentPart < comparePart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all components so far are equal, the longer version is considered higher
|
||||||
|
return current.length > compare.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ public class ProcessExecutor {
|
|||||||
public enum Processes {
|
public enum Processes {
|
||||||
LIBRE_OFFICE,
|
LIBRE_OFFICE,
|
||||||
PDFTOHTML,
|
PDFTOHTML,
|
||||||
OCR_MY_PDF,
|
|
||||||
PYTHON_OPENCV,
|
PYTHON_OPENCV,
|
||||||
GHOSTSCRIPT,
|
|
||||||
WEASYPRINT,
|
WEASYPRINT,
|
||||||
INSTALL_APP,
|
INSTALL_APP,
|
||||||
CALIBRE
|
CALIBRE,
|
||||||
|
TESSERACT,
|
||||||
|
QPDF
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
||||||
@@ -59,21 +59,11 @@ public class ProcessExecutor {
|
|||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
.getSessionLimit()
|
.getSessionLimit()
|
||||||
.getPdfToHtmlSessionLimit();
|
.getPdfToHtmlSessionLimit();
|
||||||
case OCR_MY_PDF ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getOcrMyPdfSessionLimit();
|
|
||||||
case PYTHON_OPENCV ->
|
case PYTHON_OPENCV ->
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
.getSessionLimit()
|
.getSessionLimit()
|
||||||
.getPythonOpenCvSessionLimit();
|
.getPythonOpenCvSessionLimit();
|
||||||
case GHOSTSCRIPT ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getGhostScriptSessionLimit();
|
|
||||||
case WEASYPRINT ->
|
case WEASYPRINT ->
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
@@ -84,6 +74,16 @@ public class ProcessExecutor {
|
|||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
.getSessionLimit()
|
.getSessionLimit()
|
||||||
.getInstallAppSessionLimit();
|
.getInstallAppSessionLimit();
|
||||||
|
case TESSERACT ->
|
||||||
|
applicationProperties
|
||||||
|
.getProcessExecutor()
|
||||||
|
.getSessionLimit()
|
||||||
|
.getTesseractSessionLimit();
|
||||||
|
case QPDF ->
|
||||||
|
applicationProperties
|
||||||
|
.getProcessExecutor()
|
||||||
|
.getSessionLimit()
|
||||||
|
.getQpdfSessionLimit();
|
||||||
case CALIBRE ->
|
case CALIBRE ->
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
@@ -103,21 +103,11 @@ public class ProcessExecutor {
|
|||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
.getTimeoutMinutes()
|
.getTimeoutMinutes()
|
||||||
.getPdfToHtmlTimeoutMinutes();
|
.getPdfToHtmlTimeoutMinutes();
|
||||||
case OCR_MY_PDF ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getOcrMyPdfTimeoutMinutes();
|
|
||||||
case PYTHON_OPENCV ->
|
case PYTHON_OPENCV ->
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
.getTimeoutMinutes()
|
.getTimeoutMinutes()
|
||||||
.getPythonOpenCvTimeoutMinutes();
|
.getPythonOpenCvTimeoutMinutes();
|
||||||
case GHOSTSCRIPT ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getGhostScriptTimeoutMinutes();
|
|
||||||
case WEASYPRINT ->
|
case WEASYPRINT ->
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
@@ -128,6 +118,16 @@ public class ProcessExecutor {
|
|||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
.getTimeoutMinutes()
|
.getTimeoutMinutes()
|
||||||
.getInstallAppTimeoutMinutes();
|
.getInstallAppTimeoutMinutes();
|
||||||
|
case TESSERACT ->
|
||||||
|
applicationProperties
|
||||||
|
.getProcessExecutor()
|
||||||
|
.getTimeoutMinutes()
|
||||||
|
.getTesseractTimeoutMinutes();
|
||||||
|
case QPDF ->
|
||||||
|
applicationProperties
|
||||||
|
.getProcessExecutor()
|
||||||
|
.getTimeoutMinutes()
|
||||||
|
.getQpdfTimeoutMinutes();
|
||||||
case CALIBRE ->
|
case CALIBRE ->
|
||||||
applicationProperties
|
applicationProperties
|
||||||
.getProcessExecutor()
|
.getProcessExecutor()
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package stirling.software.SPDF.utils.propertyeditor;
|
||||||
|
|
||||||
|
import java.beans.PropertyEditorSupport;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
public class StringToMapPropertyEditor extends PropertyEditorSupport {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsText(String text) throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
TypeReference<HashMap<String, String>> typeRef =
|
||||||
|
new TypeReference<HashMap<String, String>>() {};
|
||||||
|
Map<String, String> map = objectMapper.readValue(text, typeRef);
|
||||||
|
setValue(map);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Failed to convert java.lang.String to java.util.Map");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ multipart.enabled=true
|
|||||||
logging.level.org.springframework=WARN
|
logging.level.org.springframework=WARN
|
||||||
logging.level.org.hibernate=WARN
|
logging.level.org.hibernate=WARN
|
||||||
logging.level.org.eclipse.jetty=WARN
|
logging.level.org.eclipse.jetty=WARN
|
||||||
|
#logging.level.org.springframework.security.saml2=TRACE
|
||||||
|
#logging.level.org.springframework.security=DEBUG
|
||||||
|
#logging.level.org.opensaml=DEBUG
|
||||||
|
#logging.level.stirling.software.SPDF.config.security: DEBUG
|
||||||
logging.level.com.zaxxer.hikari=WARN
|
logging.level.com.zaxxer.hikari=WARN
|
||||||
|
|
||||||
spring.jpa.open-in-view=false
|
spring.jpa.open-in-view=false
|
||||||
@@ -27,6 +31,8 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/}
|
|||||||
|
|
||||||
spring.devtools.restart.enabled=true
|
spring.devtools.restart.enabled=true
|
||||||
spring.devtools.livereload.enabled=true
|
spring.devtools.livereload.enabled=true
|
||||||
|
spring.devtools.restart.exclude=stirling.software.SPDF.config.security/**
|
||||||
|
|
||||||
spring.thymeleaf.encoding=UTF-8
|
spring.thymeleaf.encoding=UTF-8
|
||||||
|
|
||||||
spring.web.resources.mime-mappings.webmanifest=application/manifest+json
|
spring.web.resources.mime-mappings.webmanifest=application/manifest+json
|
||||||
|
|||||||
25972
src/main/resources/certdata.txt
Normal file
25972
src/main/resources/certdata.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=تجزئة المستندات PDF حسب الفص
|
|||||||
home.splitPdfByChapters.desc=قسم مستند PDF إلى ملفات متعددة بناءً على هيكل فصوله.
|
home.splitPdfByChapters.desc=قسم مستند PDF إلى ملفات متعددة بناءً على هيكل فصوله.
|
||||||
splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم
|
splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=إستبدال-عكس اللون
|
replace-color.title=إستبدال-عكس اللون
|
||||||
replace-color.header=استبدال-عكس لون PDF
|
replace-color.header=استبدال-عكس لون PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=وضع التعرف الضوئي على الحروف
|
|||||||
ocr.selectText.11=إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل)
|
ocr.selectText.11=إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل)
|
||||||
ocr.selectText.12=نوع العرض (متقدم)
|
ocr.selectText.12=نوع العرض (متقدم)
|
||||||
ocr.help=يرجى قراءة هذه الوثائق حول كيفية استخدام هذا للغات أخرى و/أو الاستخدام ليس في Docker
|
ocr.help=يرجى قراءة هذه الوثائق حول كيفية استخدام هذا للغات أخرى و/أو الاستخدام ليس في Docker
|
||||||
ocr.credit=تستخدم هذه الخدمة OCRmyPDF و Tesseract للتعرف الضوئي على الحروف.
|
ocr.credit=تستخدم هذه الخدمة qpdf و Tesseract للتعرف الضوئي على الحروف.
|
||||||
ocr.submit=معالجة PDF باستخدام OCR
|
ocr.submit=معالجة PDF باستخدام OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=تحويل إلى PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=ضغط
|
compress.title=ضغط
|
||||||
compress.header=ضغط ملف PDF
|
compress.header=ضغط ملف PDF
|
||||||
compress.credit=تستخدم هذه الخدمة OCRmyPDF لضغط / تحسين PDF.
|
compress.credit=تستخدم هذه الخدمة qpdf لضغط / تحسين PDF.
|
||||||
compress.selectText.1=الوضع اليدوي - من 1 إلى 4
|
compress.selectText.1=الوضع اليدوي - من 1 إلى 4
|
||||||
compress.selectText.2=مستوى التحسين:
|
compress.selectText.2=مستوى التحسين:
|
||||||
compress.selectText.3=4 (رهيب للصور النصية)
|
compress.selectText.3=4 (رهيب للصور النصية)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=تحريك إلى اليسار
|
|||||||
multiTool.moveRight=تحريك إلى اليمين
|
multiTool.moveRight=تحريك إلى اليمين
|
||||||
multiTool.delete=حذف
|
multiTool.delete=حذف
|
||||||
multiTool.dragDropMessage=الصفحات المحددة
|
multiTool.dragDropMessage=الصفحات المحددة
|
||||||
|
multiTool.undo=تراجع
|
||||||
|
multiTool.redo=إعادة إجراء
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
|
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=تشفير
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=إضافة علامة مائية
|
watermark.title=إضافة علامة مائية
|
||||||
watermark.header=إضافة علامة مائية
|
watermark.header=إضافة علامة مائية
|
||||||
|
watermark.customColor=لون نص مخصص
|
||||||
watermark.selectText.1=حدد PDF لإضافة العلامة المائية إليه:
|
watermark.selectText.1=حدد PDF لإضافة العلامة المائية إليه:
|
||||||
watermark.selectText.2=نص العلامة المائية:
|
watermark.selectText.2=نص العلامة المائية:
|
||||||
watermark.selectText.3=حجم الخط:
|
watermark.selectText.3=حجم الخط:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=تغيير
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF إلى PDF/A
|
pdfToPDFA.title=PDF إلى PDF/A
|
||||||
pdfToPDFA.header=PDF إلى PDF/A
|
pdfToPDFA.header=PDF إلى PDF/A
|
||||||
pdfToPDFA.credit=تستخدم هذه الخدمة ghostscript لتحويل PDF/A.
|
pdfToPDFA.credit=تستخدم هذه الخدمة qpdf لتحويل PDF/A.
|
||||||
pdfToPDFA.submit=تحويل
|
pdfToPDFA.submit=تحويل
|
||||||
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
|
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
|
||||||
pdfToPDFA.outputFormat=تنسيق الإخراج
|
pdfToPDFA.outputFormat=تنسيق الإخراج
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=مستوى الإشارة المرجعية: اختر مس
|
|||||||
splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ.
|
splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ.
|
||||||
splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة.
|
splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة.
|
||||||
splitByChapters.submit=تقطيع ملف PDF
|
splitByChapters.submit=تقطيع ملف PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=انقر هنا
|
||||||
|
fileChooser.or=أو
|
||||||
|
fileChooser.dragAndDrop=قم بسحب الملفات وإفلاتها
|
||||||
|
fileChooser.hoveredDragAndDrop=قم بسحب المفات وإفلاتها هنا
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ sizes.small=Kiçik
|
|||||||
sizes.medium=Orta
|
sizes.medium=Orta
|
||||||
sizes.large=Böyük
|
sizes.large=Böyük
|
||||||
sizes.x-large=Ekstra Böyük
|
sizes.x-large=Ekstra Böyük
|
||||||
error.pdfPassword=PDF sənədi şifrələnmişdir və şifrə təmin edilməmişdir və ya yanlışdır.
|
error.pdfPassword=PDF sənədi şifrlənmişdir və şifr təmin edilməmişdir və ya yanlışdır.
|
||||||
delete=Sil
|
delete=Sil
|
||||||
username=İstifadəçi Adı
|
username=İstifadəçi Adı
|
||||||
password=Şifrə
|
password=Şifr
|
||||||
welcome=Xoş gəldiniz
|
welcome=Xoş gəldiniz
|
||||||
property=Xüsusiyyət
|
property=Xüsusiyyət
|
||||||
black=Qara
|
black=Qara
|
||||||
@@ -53,11 +53,11 @@ no=Xeyr
|
|||||||
changedCredsMessage=Etibarnamələr dəyişdirildi!
|
changedCredsMessage=Etibarnamələr dəyişdirildi!
|
||||||
notAuthenticatedMessage=İstifadəçinin kimliyi təsdiqlənməyib.
|
notAuthenticatedMessage=İstifadəçinin kimliyi təsdiqlənməyib.
|
||||||
userNotFoundMessage=İstifadəçi tapılmadı.
|
userNotFoundMessage=İstifadəçi tapılmadı.
|
||||||
incorrectPasswordMessage=Cari şifrə yanlışdır.
|
incorrectPasswordMessage=Cari şifr yanlışdır.
|
||||||
usernameExistsMessage=İstifadəçi adı mövcuddur.
|
usernameExistsMessage=İstifadəçi adı mövcuddur.
|
||||||
invalidUsernameMessage=Yanlış istifadəçi adı, istifadəçi adı sadəcə hərflərdən, rəqəmlərdən və @._+- xüsusi simvollarından ibarət ola bilər və ya düzgün email ünvanı olmalıdır.
|
invalidUsernameMessage=Yanlış istifadəçi adı, istifadəçi adı sadəcə hərflərdən, rəqəmlərdən və @._+- xüsusi simvollarından ibarət ola bilər və ya düzgün email ünvanı olmalıdır.
|
||||||
invalidPasswordMessage=Şifrə boş olmamalıdır, başlanğıc və sonunda boşluqdan istifadə edilməməlidir.
|
invalidPasswordMessage=Şifr boş olmamalıdır, başlanğıc və sonunda boşluqdan istifadə edilməməlidir.
|
||||||
confirmPasswordErrorMessage=Yeni Şifrə və Yeni Şifrəni Doğrula uyğun olmalıdır.
|
confirmPasswordErrorMessage=Yeni Şifr və Yeni Şifri Doğrula uyğun olmalıdır.
|
||||||
deleteCurrentUserMessage=Hazırda daxil olmuş istifadəçini silmək mümkün deyil.
|
deleteCurrentUserMessage=Hazırda daxil olmuş istifadəçini silmək mümkün deyil.
|
||||||
deleteUsernameExistsMessage=İstifadəçi adı mövcud deyildir və silinə bilməz.
|
deleteUsernameExistsMessage=İstifadəçi adı mövcud deyildir və silinə bilməz.
|
||||||
downgradeCurrentUserMessage=Cari istifadəçinin rolunu aşağı salmaq mümkün deyil
|
downgradeCurrentUserMessage=Cari istifadəçinin rolunu aşağı salmaq mümkün deyil
|
||||||
@@ -70,8 +70,8 @@ oops=Oops!
|
|||||||
help=Yardım
|
help=Yardım
|
||||||
goHomepage=Ana səhifəyə get
|
goHomepage=Ana səhifəyə get
|
||||||
joinDiscord=Discord serverimizə qatıl
|
joinDiscord=Discord serverimizə qatıl
|
||||||
seeDockerHub=Docker Hub'a bax
|
seeDockerHub=Docker Hub-a bax
|
||||||
visitGithub=Github Repository'ə Baş Çək
|
visitGithub=Github Repository-ə Baş Çək
|
||||||
donate=İanə Ver
|
donate=İanə Ver
|
||||||
color=Rəng
|
color=Rəng
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
@@ -126,9 +126,9 @@ enterpriseEdition.ssoAdvert=Daha çox istifadəçi-idarəetmə xüsusiyyətləri
|
|||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Stirling PDF'i daha yaxşı etmək istəyirsinizmi?
|
analytics.title=Stirling PDF-i daha yaxşı etmək istəyirsinizmi?
|
||||||
analytics.paragraph1=Stirling PDF bizə məhsulu inkişaf etdirməyə kömək etmək üçün analitikaya üstünlük verib. Biz heç bir şəxsi məlumatı və ya fayl məzmununu izləmirik.
|
analytics.paragraph1=Stirling PDF bizə məhsulu inkişaf etdirməyə kömək etmək üçün analitikaya üstünlük verib. Biz heç bir şəxsi məlumatı və ya fayl məzmununu izləmirik.
|
||||||
analytics.paragraph2=Zəhmət olmasa, Stringling-PDF'ə inkişaf etməkdə və istifadəçilərimizi daha yaxşı anlamaqda yardım etmək üçün analitikanı aktivləşdirməyi nəzərə alın.
|
analytics.paragraph2=Zəhmət olmasa, Stringling-PDF-ə inkişaf etməkdə və istifadəçilərimizi daha yaxşı anlamaqda yardım etmək üçün analitikanı aktivləşdirməyi nəzərə alın.
|
||||||
analytics.enable=Analitikanı aktivləşdir
|
analytics.enable=Analitikanı aktivləşdir
|
||||||
analytics.disable=Analitikanı deaktivləşdir
|
analytics.disable=Analitikanı deaktivləşdir
|
||||||
analytics.settings=Analitikanın parametrlərini config/settings.yml faylından dəyişə bilərsiniz.
|
analytics.settings=Analitikanın parametrlərini config/settings.yml faylından dəyişə bilərsiniz.
|
||||||
@@ -141,11 +141,11 @@ navbar.darkmode=Qaranlıq Tema
|
|||||||
navbar.language=Dillər
|
navbar.language=Dillər
|
||||||
navbar.settings=Parametrlər
|
navbar.settings=Parametrlər
|
||||||
navbar.allTools=Alətlər
|
navbar.allTools=Alətlər
|
||||||
navbar.multiTool=Çox Alət
|
navbar.multiTool=Multi-Alət
|
||||||
navbar.search=Axtar
|
navbar.search=Axtar
|
||||||
navbar.sections.organize=Təşkil et
|
navbar.sections.organize=Təşkil et
|
||||||
navbar.sections.convertTo=PDF'ə Çevir
|
navbar.sections.convertTo=PDF-ə Çevir
|
||||||
navbar.sections.convertFrom=PDF'dən Çevir
|
navbar.sections.convertFrom=PDF-dən Çevir
|
||||||
navbar.sections.security=İmza & Təhlükəsizlik
|
navbar.sections.security=İmza & Təhlükəsizlik
|
||||||
navbar.sections.advance=Qabaqcıl
|
navbar.sections.advance=Qabaqcıl
|
||||||
navbar.sections.edit=Bax & Redaktə et
|
navbar.sections.edit=Bax & Redaktə et
|
||||||
@@ -171,11 +171,11 @@ settings.cacheInputs.help=Gələcək əməliyyatlar üçün əvvəllər istifad
|
|||||||
|
|
||||||
changeCreds.title=Məlumatları dəyişdirin
|
changeCreds.title=Məlumatları dəyişdirin
|
||||||
changeCreds.header=Hesab Məlumatlarınızı Yeniləyin
|
changeCreds.header=Hesab Məlumatlarınızı Yeniləyin
|
||||||
changeCreds.changePassword=Siz standart giriş məlumatlarından istifadə edirsiniz. Zəhmət olmasa, yeni şifrə daxil edin
|
changeCreds.changePassword=Siz standart giriş məlumatlarından istifadə edirsiniz. Zəhmət olmasa, yeni şifr daxil edin
|
||||||
changeCreds.newUsername=Yeni İstifadəçi Adı
|
changeCreds.newUsername=Yeni İstifadəçi Adı
|
||||||
changeCreds.oldPassword=Cari Şifrə
|
changeCreds.oldPassword=Cari Şifr
|
||||||
changeCreds.newPassword=Yeni Şifrə
|
changeCreds.newPassword=Yeni Şifr
|
||||||
changeCreds.confirmNewPassword=Yeni Şifrəni Təsdiqləyin
|
changeCreds.confirmNewPassword=Yeni Şifri Təsdiqləyin
|
||||||
changeCreds.submit=Dəyişiklikləri Təsdiqlə
|
changeCreds.submit=Dəyişiklikləri Təsdiqlə
|
||||||
|
|
||||||
|
|
||||||
@@ -186,11 +186,11 @@ account.adminSettings=Admin Paramterləri - İstifadəçilər Əlavə Et və Onl
|
|||||||
account.userControlSettings=İstifadəçi İdarəetmə Parametrləri
|
account.userControlSettings=İstifadəçi İdarəetmə Parametrləri
|
||||||
account.changeUsername=İstifadəçi Adını Dəyiş
|
account.changeUsername=İstifadəçi Adını Dəyiş
|
||||||
account.newUsername=Yeni İstifadəçi Adı
|
account.newUsername=Yeni İstifadəçi Adı
|
||||||
account.password=Təsdiqləmə Şifrəsi
|
account.password=Təsdiqləmə Şifri
|
||||||
account.oldPassword=Keçmiş Şifrə
|
account.oldPassword=Keçmiş Şifr
|
||||||
account.newPassword=Yeni Şifrə
|
account.newPassword=Yeni Şifr
|
||||||
account.changePassword=Şifrəni Dəyiş
|
account.changePassword=Şifri Dəyiş
|
||||||
account.confirmNewPassword=Yeni Şifrəni Təsdiqlə
|
account.confirmNewPassword=Yeni Şifri Təsdiqlə
|
||||||
account.signOut=Çıxış
|
account.signOut=Çıxış
|
||||||
account.yourApiKey=Sizin API Açarınız
|
account.yourApiKey=Sizin API Açarınız
|
||||||
account.syncTitle=Brauzer parametrlərini hesabla sinxronlaşdırın
|
account.syncTitle=Brauzer parametrlərini hesabla sinxronlaşdırın
|
||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=PDF-i Fəsillərə Əsasən Böl
|
|||||||
home.splitPdfByChapters.desc=Fəsil strukturuna əsasən PDF-i bir neçə fayla böl.
|
home.splitPdfByChapters.desc=Fəsil strukturuna əsasən PDF-i bir neçə fayla böl.
|
||||||
splitPdfByChapters.tags=böl,fəsillər,əlfəcinlər,nizamla
|
splitPdfByChapters.tags=böl,fəsillər,əlfəcinlər,nizamla
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Qabaqcıl Rəng Seçimləri
|
replace-color.title=Qabaqcıl Rəng Seçimləri
|
||||||
replace-color.header=PDF-də Rəngləri Dəyiş-Tərsinə Çevir
|
replace-color.header=PDF-də Rəngləri Dəyiş-Tərsinə Çevir
|
||||||
@@ -543,7 +547,7 @@ login.title=Daxil olun
|
|||||||
login.header=Daxil olun
|
login.header=Daxil olun
|
||||||
login.signin=Daxil olun
|
login.signin=Daxil olun
|
||||||
login.rememberme=Məni xatırla
|
login.rememberme=Məni xatırla
|
||||||
login.invalid=Etibarsız istifadəçi adı və ya şifrə.
|
login.invalid=Etibarsız istifadəçi adı və ya şifr.
|
||||||
login.locked=Sizin hesabınız kilidlənmişdir.
|
login.locked=Sizin hesabınız kilidlənmişdir.
|
||||||
login.signinTitle=Zəhmət olmasa, daxil olun
|
login.signinTitle=Zəhmət olmasa, daxil olun
|
||||||
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
|
login.ssoSignIn=Single Sign-on vasitəsilə daxil olun
|
||||||
@@ -581,8 +585,8 @@ showJS.submit=Göstər
|
|||||||
|
|
||||||
|
|
||||||
#pdfToSinglePage
|
#pdfToSinglePage
|
||||||
pdfToSinglePage.title=PDF'dən Tək Səhifəyə
|
pdfToSinglePage.title=PDF-dən Tək Səhifəyə
|
||||||
pdfToSinglePage.header=PDF'dən Tək Səhifəyə
|
pdfToSinglePage.header=PDF-dən Tək Səhifəyə
|
||||||
pdfToSinglePage.submit=Tək Səhifəyə Çevir
|
pdfToSinglePage.submit=Tək Səhifəyə Çevir
|
||||||
|
|
||||||
|
|
||||||
@@ -601,8 +605,8 @@ getPdfInfo.downloadJson=JSON yüklə
|
|||||||
|
|
||||||
|
|
||||||
#markdown-to-pdf
|
#markdown-to-pdf
|
||||||
MarkdownToPDF.title=Markdown'dan PDF'ə
|
MarkdownToPDF.title=Markdown-dan PDF-ə
|
||||||
MarkdownToPDF.header=Markdown'dan PDF'ə
|
MarkdownToPDF.header=Markdown-dan PDF-ə
|
||||||
MarkdownToPDF.submit=Çevir
|
MarkdownToPDF.submit=Çevir
|
||||||
MarkdownToPDF.help=İş davam edir
|
MarkdownToPDF.help=İş davam edir
|
||||||
MarkdownToPDF.credit=WeasyPrint İstifadə Edir
|
MarkdownToPDF.credit=WeasyPrint İstifadə Edir
|
||||||
@@ -617,8 +621,8 @@ URLToPDF.credit=WeasyPrint İstifadə Edir
|
|||||||
|
|
||||||
|
|
||||||
#html-to-pdf
|
#html-to-pdf
|
||||||
HTMLToPDF.title=HTML'dən PDF'ə
|
HTMLToPDF.title=HTML-dən PDF-ə
|
||||||
HTMLToPDF.header=HTML'dən PDF'ə
|
HTMLToPDF.header=HTML-dən PDF-ə
|
||||||
HTMLToPDF.help=HTML fayllarını və tərkibində mütləq html/css/images və s. olan ZIP fayllarını qəbul edir
|
HTMLToPDF.help=HTML fayllarını və tərkibində mütləq html/css/images və s. olan ZIP fayllarını qəbul edir
|
||||||
HTMLToPDF.submit=Çevir
|
HTMLToPDF.submit=Çevir
|
||||||
HTMLToPDF.credit=WeasyPrint İstifadə Edir
|
HTMLToPDF.credit=WeasyPrint İstifadə Edir
|
||||||
@@ -656,14 +660,14 @@ AddStampRequest.submit=Təsdiqlə
|
|||||||
|
|
||||||
|
|
||||||
#sanitizePDF
|
#sanitizePDF
|
||||||
sanitizePDF.title=PDF'i Təmizlə
|
sanitizePDF.title=PDF-i Təmizlə
|
||||||
sanitizePDF.header=PDF Faylını Təmizlə
|
sanitizePDF.header=PDF Faylını Təmizlə
|
||||||
sanitizePDF.selectText.1=JavaScript Fəaliyyətlərini Sil
|
sanitizePDF.selectText.1=JavaScript Fəaliyyətlərini Sil
|
||||||
sanitizePDF.selectText.2=Daxil Edilmiş Faylları Sil
|
sanitizePDF.selectText.2=Daxil Edilmiş Faylları Sil
|
||||||
sanitizePDF.selectText.3=Metadatanı Sil
|
sanitizePDF.selectText.3=Metadatanı Sil
|
||||||
sanitizePDF.selectText.4=Linkləri Sil
|
sanitizePDF.selectText.4=Linkləri Sil
|
||||||
sanitizePDF.selectText.5=Şriftləri Sil
|
sanitizePDF.selectText.5=Şriftləri Sil
|
||||||
sanitizePDF.submit=PDF'i Təmizlə
|
sanitizePDF.submit=PDF-i Təmizlə
|
||||||
|
|
||||||
|
|
||||||
#addPageNumbers
|
#addPageNumbers
|
||||||
@@ -683,7 +687,7 @@ addPageNumbers.submit=Səhifə Nömrələri əlavə edin
|
|||||||
|
|
||||||
#auto-rename
|
#auto-rename
|
||||||
auto-rename.title=Avtomatik Yenidən Adlandır
|
auto-rename.title=Avtomatik Yenidən Adlandır
|
||||||
auto-rename.header=Pdf'in Adını Avtomatik Yenidən Adlandır
|
auto-rename.header=Pdf-in Adını Avtomatik Yenidən Adlandır
|
||||||
auto-rename.submit=Avtomatik Yenidən Adlandır
|
auto-rename.submit=Avtomatik Yenidən Adlandır
|
||||||
|
|
||||||
|
|
||||||
@@ -698,7 +702,7 @@ adjustContrast.download=Yüklə
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Kəs
|
crop.title=Kəs
|
||||||
crop.header=Pdf'ləri Kəs
|
crop.header=Pdf-ləri Kəs
|
||||||
crop.submit=Təsdiq Et
|
crop.submit=Təsdiq Et
|
||||||
|
|
||||||
|
|
||||||
@@ -722,8 +726,8 @@ pipeline.title=Pipeline
|
|||||||
|
|
||||||
|
|
||||||
#pageLayout
|
#pageLayout
|
||||||
pageLayout.title=Çoxsəhifəli Sxem
|
pageLayout.title=Çoxsəhifəli Tərtibat
|
||||||
pageLayout.header=Çoxsəhifəli Sxem
|
pageLayout.header=Çoxsəhifəli Tərtibat
|
||||||
pageLayout.pagesPerSheet=Vərəqdəki Səhifə Sayı:
|
pageLayout.pagesPerSheet=Vərəqdəki Səhifə Sayı:
|
||||||
pageLayout.addBorder=Çərçivə Əlavə Et
|
pageLayout.addBorder=Çərçivə Əlavə Et
|
||||||
pageLayout.submit=Təsdiq et
|
pageLayout.submit=Təsdiq et
|
||||||
@@ -739,22 +743,22 @@ scalePages.submit=Təsdiq edin
|
|||||||
|
|
||||||
|
|
||||||
#certSign
|
#certSign
|
||||||
certSign.title=Certificate Signing
|
certSign.title=Sertifikatla İmzala
|
||||||
certSign.header=Sign a PDF with your certificate (Work in progress)
|
certSign.header=PDF-i Sertifikatınızla İmzalayın (İşlənilir)
|
||||||
certSign.selectPDF=Select a PDF File for Signing:
|
certSign.selectPDF=İmzalamaq üçün PDF Faylı seçin:
|
||||||
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
certSign.jksNote=Note: Əgər sertifikatınızın tipi aşağıda göstərilməyibsə, zəhmət olmasa "Keytool command line tool" istifadə edərək onu "Java Keystroke" (.jks) faylına çevirin. Sonra, aşağıdan .jks faylını seçin.
|
||||||
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
|
certSign.selectKey=Şəxsi Açar faylınızı seçin (PKCS#8 format, .pem və ya .der ola bilər):
|
||||||
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
|
certSign.selectCert=Sertifikat faylınızı seçin (X.509 format, .pem və ya .der ola bilər):
|
||||||
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
|
certSign.selectP12=PKCS#12 Keystore Faylınızı seçin (.p12 və ya .pfx) (İstəyə bağlı, əgər təmin olunarsa, şəxsi açar və sertifikatınızı ehtiva etməlidir):
|
||||||
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
certSign.selectJKS=Java Keystore Faylınızı seçin (.jks və ya .keystore):
|
||||||
certSign.certType=Certificate Type
|
certSign.certType=Sertifikat Tipi
|
||||||
certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
certSign.password=Keystore və ya Şəxsi Açar daxil edin (Əgər varsa):
|
||||||
certSign.showSig=Show Signature
|
certSign.showSig=İmzanı Göstər
|
||||||
certSign.reason=Reason
|
certSign.reason=Səbəb
|
||||||
certSign.location=Location
|
certSign.location=Məkan
|
||||||
certSign.name=Name
|
certSign.name=Ad
|
||||||
certSign.showLogo=Show Logo
|
certSign.showLogo=Loqonu Göstər
|
||||||
certSign.submit=Sign PDF
|
certSign.submit=PDF-i İmzala
|
||||||
|
|
||||||
|
|
||||||
#removeCertSign
|
#removeCertSign
|
||||||
@@ -765,13 +769,13 @@ removeCertSign.submit=İmzanı silin
|
|||||||
|
|
||||||
|
|
||||||
#removeBlanks
|
#removeBlanks
|
||||||
removeBlanks.title=Remove Blanks
|
removeBlanks.title=Boş Səhifələri Sil
|
||||||
removeBlanks.header=Remove Blank Pages
|
removeBlanks.header=Boş SƏhifələri Silir
|
||||||
removeBlanks.threshold=Pixel Whiteness Threshold:
|
removeBlanks.threshold=Minimal Piksel Bəyazlığı:
|
||||||
removeBlanks.thresholdDesc=Threshold for determining how white a white pixel must be to be classed as 'White'. 0 = Black, 255 pure white.
|
removeBlanks.thresholdDesc=Pikselin "Ağ" hesab olunması üçün minimal nə qədər bəyaz olmalı olduğunu təyin edin. 0 = Qara, 255 Ağappaq.
|
||||||
removeBlanks.whitePercent=White Percent (%):
|
removeBlanks.whitePercent=Bəyaz Faizi (%):
|
||||||
removeBlanks.whitePercentDesc=Percent of page that must be 'white' pixels to be removed
|
removeBlanks.whitePercentDesc=Silinmək üçün səhifənin neçə faizi "ağ" piksellərdən təşkil olunmalıdır
|
||||||
removeBlanks.submit=Remove Blanks
|
removeBlanks.submit=Boş Səhifələri Sil
|
||||||
|
|
||||||
|
|
||||||
#removeAnnotations
|
#removeAnnotations
|
||||||
@@ -781,16 +785,16 @@ removeAnnotations.submit=Sil
|
|||||||
|
|
||||||
|
|
||||||
#compare
|
#compare
|
||||||
compare.title=Compare
|
compare.title=Müqayisə Et
|
||||||
compare.header=Compare PDFs
|
compare.header=PDF-ləri Müqayisə Et
|
||||||
compare.highlightColor.1=Highlight Color 1:
|
compare.highlightColor.1=Önə Çıxarma Rəngi 1:
|
||||||
compare.highlightColor.2=Highlight Color 2:
|
compare.highlightColor.2=Önə Çıxarma Rəngi 2:
|
||||||
compare.document.1=Document 1
|
compare.document.1=Sənəd 1
|
||||||
compare.document.2=Document 2
|
compare.document.2=Sənəd 2
|
||||||
compare.submit=Compare
|
compare.submit=Müqayisə Et
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
compare.complex.message=Fayllardan biri və ya ikisi də böyük fayldır. Müqayisə effektivliyi azala bilər.
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
compare.large.file.message=Fayllardan biri və ya ikisi də işləmək üçün çox böyükdür.
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
compare.no.text.message=Fayllardan birində və ya ikisində də mətn məzmunu yoxdur. Zəhmət olmasa, müqayisə üçün mətn məzmunlu PDF seçin.
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Kitabları və Komiksləri PDF-ə
|
BookToPDF.title=Kitabları və Komiksləri PDF-ə
|
||||||
@@ -818,12 +822,12 @@ sign.save=İmzanı yadda Saxla
|
|||||||
sign.personalSigs=Şəxsi İmzalar
|
sign.personalSigs=Şəxsi İmzalar
|
||||||
sign.sharedSigs=Paylaşılan İmzalar
|
sign.sharedSigs=Paylaşılan İmzalar
|
||||||
sign.noSavedSigs=Saxlanmış imza tapılmadı
|
sign.noSavedSigs=Saxlanmış imza tapılmadı
|
||||||
sign.addToAll=Add to all pages
|
sign.addToAll=Bütün səhiflərə əlavə et
|
||||||
sign.delete=Delete
|
sign.delete=Sil
|
||||||
sign.first=First page
|
sign.first=İlk səhifə
|
||||||
sign.last=Last page
|
sign.last=Son səhifə
|
||||||
sign.next=Next page
|
sign.next=Növbəti səhifə
|
||||||
sign.previous=Previous page
|
sign.previous=Əvvəlki səhifə
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
repair.title=Bərpa Et
|
repair.title=Bərpa Et
|
||||||
@@ -839,37 +843,37 @@ flatten.submit=Düzləşdirin
|
|||||||
|
|
||||||
|
|
||||||
#ScannerImageSplit
|
#ScannerImageSplit
|
||||||
ScannerImageSplit.selectText.1=Angle Threshold:
|
ScannerImageSplit.selectText.1=Bucaq Aşağı Limiti:
|
||||||
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).
|
ScannerImageSplit.selectText.2=Şəklin fırladılması üçün lazım olan minimal mütləq bucağı təyin edir (defolt: 10).
|
||||||
ScannerImageSplit.selectText.3=Tolerance:
|
ScannerImageSplit.selectText.3=Rəng Toleransı:
|
||||||
ScannerImageSplit.selectText.4=Determines the range of color variation around the estimated background color (default: 30).
|
ScannerImageSplit.selectText.4=Təxmin olunan arxaplan rənginin ətrafındakı rəng fərqliliyi intervalını təyin edir (defolt: 30).
|
||||||
ScannerImageSplit.selectText.5=Minimum Area:
|
ScannerImageSplit.selectText.5=Minimal Sahə:
|
||||||
ScannerImageSplit.selectText.6=Sets the minimum area threshold for a photo (default: 10000).
|
ScannerImageSplit.selectText.6=Foto üçün minimal sahənin aşağı limitini təyin edir (defolt: 10000).
|
||||||
ScannerImageSplit.selectText.7=Minimum Contour Area:
|
ScannerImageSplit.selectText.7=Minimal Kontur Sahəsi:
|
||||||
ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo
|
ScannerImageSplit.selectText.8=Fotonun kontur sahəsi üçün minimal aşağı limiti təyin edir
|
||||||
ScannerImageSplit.selectText.9=Border Size:
|
ScannerImageSplit.selectText.9=Sərhəd Ölçüsü:
|
||||||
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
|
ScannerImageSplit.selectText.10=Faylda ağ sərhədlərin olmasının qarşısını almaq üçün əlavə ediləcək sərhədin ölçüsünü təyin edir (defolt: 1).
|
||||||
ScannerImageSplit.info=Python is not installed. It is required to run.
|
ScannerImageSplit.info=Python yüklənməyib. İşə salmaq üçün Python lazımdır.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
ocr.title=OCR / Scan Cleanup
|
ocr.title=OST (OCR) / Skan Təmizləmə
|
||||||
ocr.header=Cleanup Scans / OCR (Optical Character Recognition)
|
ocr.header=Skanları Təmizlə / OST (Optik Simvol Tanınması)
|
||||||
ocr.selectText.1=Select languages that are to be detected within the PDF (Ones listed are the ones currently detected):
|
ocr.selectText.1=PDF-də aşkar olunacaq dilləri seçin (Göstərilmiş dillər hazırda aşkar olunmuşlardır):
|
||||||
ocr.selectText.2=Produce text file containing OCR text alongside the OCR'ed PDF
|
ocr.selectText.2=OST-lənmiş PDF ilə yanaşı daxilində OST edilmiş mətn olan PDF yaradın
|
||||||
ocr.selectText.3=Correct pages were scanned at a skewed angle by rotating them back into place
|
ocr.selectText.3=Əyri skan olunmuş səhifələri yerinə fırladaraq düzəldin
|
||||||
ocr.selectText.4=Clean page so its less likely that OCR will find text in background noise. (No output change)
|
ocr.selectText.4=OST-in arxaplandakı artıq mətni aşkar etməsinin qarşısını almaq üçün səhifəni təmizləyin. (Çıxış dəyişmir)
|
||||||
ocr.selectText.5=Clean page so its less likely that OCR will find text in background noise, maintains cleanup in output.
|
ocr.selectText.5=OST-in arxaplandakı artıq mətni aşkar etməsinin qarşısını almaq üçün səhifəni təmizləyin, təmizləməni çıxışa verilən faylda saxlayır.
|
||||||
ocr.selectText.6=Ignores pages that have interactive text on them, only OCRs pages that are images
|
ocr.selectText.6=Üzərində interaktiv yazı olan səhifələri nəzərə almır, yalnız şəkil olan səhifələri OST edir.
|
||||||
ocr.selectText.7=Force OCR, will OCR Every page removing all original text elements
|
ocr.selectText.7=OST-ə məcbur et, bütün orijinal mətn elementlərini silərək hər səhifəni OST edir
|
||||||
ocr.selectText.8=Normal (Will error if PDF contains text)
|
ocr.selectText.8=Normal (PDF-də mətn varsa, xəta verəcək)
|
||||||
ocr.selectText.9=Additional Settings
|
ocr.selectText.9=Əlavə Parametrlər
|
||||||
ocr.selectText.10=OCR Mode
|
ocr.selectText.10=OST (OCR) Rejimi
|
||||||
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
|
ocr.selectText.11=OST-dən sonra şəkilləri sil (BÜTÜN şəkilləri silir, ancaq çevirmə prosesinin bir hissəsi olduqda işə yarayır)
|
||||||
ocr.selectText.12=Render Type (Advanced)
|
ocr.selectText.12=Render Tipi (Qabaqcıl)
|
||||||
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
ocr.help=Bunu digər dillər üçün necə istifadə etmək və/və ya docker-də istifadə etməmək üçün bu dokumentasiyanı oxuyun
|
||||||
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
ocr.credit=Bu servis OST (OCR) üçün "OCRmyPDF" və "Tesseract" istifadə edir.
|
||||||
ocr.submit=Process PDF with OCR
|
ocr.submit=PDF-i OST ilə işlə
|
||||||
|
|
||||||
|
|
||||||
#extractImages
|
#extractImages
|
||||||
@@ -890,15 +894,15 @@ fileToPDF.submit=PDF-ə Çevir
|
|||||||
|
|
||||||
|
|
||||||
#compress
|
#compress
|
||||||
compress.title=Compress
|
compress.title=Sıxışdır
|
||||||
compress.header=Compress PDF
|
compress.header=PDF-i Sıxışdır
|
||||||
compress.credit=This service uses Ghostscript for PDF Compress/Optimisation.
|
compress.credit=Bu servis PDF sıxışdırılması/Optimizasiyası üçün Ghostscript istifadə edir.
|
||||||
compress.selectText.1=Manual Mode - From 1 to 4
|
compress.selectText.1=Manual Mod - 1-dən 4-ə
|
||||||
compress.selectText.2=Optimization level:
|
compress.selectText.2=Optimizasiya səviyyəsi:
|
||||||
compress.selectText.3=4 (Terrible for text images)
|
compress.selectText.3=4 (Mətn şəkilləri üçün yaxşı deyil)
|
||||||
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
|
compress.selectText.4=Avto mod - PDF-in dəqiq ölçüsünü əldə etmək üçün keyfiyyəti avtomatik tənzimləyir
|
||||||
compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB)
|
compress.selectText.5=Gözlənilən PDF Ölçüsü (məsələn, 25MB, 10.8MB, 25KB)
|
||||||
compress.submit=Compress
|
compress.submit=Sıxışdır
|
||||||
|
|
||||||
|
|
||||||
#Add image
|
#Add image
|
||||||
@@ -919,35 +923,35 @@ merge.submit=Birləşdirin
|
|||||||
|
|
||||||
|
|
||||||
#pdfOrganiser
|
#pdfOrganiser
|
||||||
pdfOrganiser.title=Page Organiser
|
pdfOrganiser.title=Səhifə Tənzimləyicisi
|
||||||
pdfOrganiser.header=PDF Page Organiser
|
pdfOrganiser.header=PDF Səhifə Tənzimləyicisi
|
||||||
pdfOrganiser.submit=Rearrange Pages
|
pdfOrganiser.submit=Səhifələri Yenidən Təşkil Edin
|
||||||
pdfOrganiser.mode=Mode
|
pdfOrganiser.mode=Rejim
|
||||||
pdfOrganiser.mode.1=Custom Page Order
|
pdfOrganiser.mode.1=Fərdi Səhifə Düzülüşü
|
||||||
pdfOrganiser.mode.2=Reverse Order
|
pdfOrganiser.mode.2=Tərs Düzülüş
|
||||||
pdfOrganiser.mode.3=Duplex Sort
|
pdfOrganiser.mode.3=İkitərəfli Çeşidləmə
|
||||||
pdfOrganiser.mode.4=Booklet Sort
|
pdfOrganiser.mode.4=Kitabça Çeşidləmə
|
||||||
pdfOrganiser.mode.5=Side Stitch Booklet Sort
|
pdfOrganiser.mode.5=Yan Tikiş Kitabçasının Çeşidlənməsi
|
||||||
pdfOrganiser.mode.6=Odd-Even Split
|
pdfOrganiser.mode.6=Tək-Cüt Bölünmə
|
||||||
pdfOrganiser.mode.7=Remove First
|
pdfOrganiser.mode.7=Birincini Sil
|
||||||
pdfOrganiser.mode.8=Remove Last
|
pdfOrganiser.mode.8=Sonuncunu Sil
|
||||||
pdfOrganiser.mode.9=Remove First and Last
|
pdfOrganiser.mode.9=Birinci və Sonuncunu Sil
|
||||||
pdfOrganiser.mode.10=Odd-Even Merge
|
pdfOrganiser.mode.10=Tək-Cüt Birləşdirmə
|
||||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
pdfOrganiser.placeholder=(məs., 1,3,2 və ya 4-8,2,10-12 və ya 2n-1)
|
||||||
|
|
||||||
|
|
||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title=PDF Multi Tool
|
multiTool.title=PDF Multi-Alət
|
||||||
multiTool.header=PDF Multi Tool
|
multiTool.header=PDF Multi-Alət
|
||||||
multiTool.uploadPrompts=File Name
|
multiTool.uploadPrompts=Fayl Adı
|
||||||
multiTool.selectAll=Select All
|
multiTool.selectAll=Hamısını Seç
|
||||||
multiTool.deselectAll=Deselect All
|
multiTool.deselectAll=Hamısını Seçməni Ləğv Et
|
||||||
multiTool.selectPages=Page Select
|
multiTool.selectPages=Səhifə Seçimi
|
||||||
multiTool.selectedPages=Selected Pages
|
multiTool.selectedPages=Seçilmiş Səhifələr
|
||||||
multiTool.page=Page
|
multiTool.page=Səhifə
|
||||||
multiTool.deleteSelected=Delete Selected
|
multiTool.deleteSelected=Seçilmişi Sil
|
||||||
multiTool.downloadAll=Export
|
multiTool.downloadAll=İxrac Et
|
||||||
multiTool.downloadSelected=Export Selected
|
multiTool.downloadSelected=Seçilmişi İxrac Et
|
||||||
|
|
||||||
multiTool.insertPageBreak=Insert Page Break
|
multiTool.insertPageBreak=Insert Page Break
|
||||||
multiTool.addFile=Add File
|
multiTool.addFile=Add File
|
||||||
@@ -957,10 +961,22 @@ multiTool.split=Split
|
|||||||
multiTool.moveLeft=Move Left
|
multiTool.moveLeft=Move Left
|
||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=PDF-ə baxın
|
viewPdf.title=PDF-ə baxın
|
||||||
@@ -982,32 +998,32 @@ rotate.submit=Fırladın
|
|||||||
|
|
||||||
|
|
||||||
#split-pdfs
|
#split-pdfs
|
||||||
split.title=Split PDF
|
split.title=PDF-i Bölün
|
||||||
split.header=Split PDF
|
split.header=PDF-i Bölün
|
||||||
split.desc.1=The numbers you select are the page number you wish to do a split on
|
split.desc.1=Seçdiyiniz Nömrələr Bölmək İstədiyiniz Səhifə Nömrəsidir
|
||||||
split.desc.2=As such selecting 1,3,7-9 would split a 10 page document into 6 separate PDFS with:
|
split.desc.2=Beləliklə, 1,3,7-9 Seçimi 10 Səhifəlik Sənədi 6 Ayrı PDF-ə Böləcək:
|
||||||
split.desc.3=Document #1: Page 1
|
split.desc.3=Sənəd #1: Səhifə 1
|
||||||
split.desc.4=Document #2: Page 2 and 3
|
split.desc.4=Sənəd #2: Səhifə 2 və 3
|
||||||
split.desc.5=Document #3: Page 4, 5, 6 and 7
|
split.desc.5=Sənəd #3: Səhifə 4, 5, 6 və 7
|
||||||
split.desc.6=Document #4: Page 8
|
split.desc.6=Sənəd #4: Səhifə 8
|
||||||
split.desc.7=Document #5: Page 9
|
split.desc.7=Sənəd #5: Səhifə 9
|
||||||
split.desc.8=Document #6: Page 10
|
split.desc.8=Sənəd #6: Səhifə 10
|
||||||
split.splitPages=Enter pages to split on:
|
split.splitPages=Bölünəcək Səhifələri Daxil Edin:
|
||||||
split.submit=Split
|
split.submit=Bölün
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
imageToPDF.title=Image to PDF
|
imageToPDF.title=Şəkli PDF-ə
|
||||||
imageToPDF.header=Image to PDF
|
imageToPDF.header=Şəkli PDF-ə
|
||||||
imageToPDF.submit=Convert
|
imageToPDF.submit=Çevir
|
||||||
imageToPDF.selectLabel=Image Fit Options
|
imageToPDF.selectLabel=Şəkil Uyğunluğu Seçimləri
|
||||||
imageToPDF.fillPage=Fill Page
|
imageToPDF.fillPage=Səhifəni Doldur
|
||||||
imageToPDF.fitDocumentToImage=Fit Page to Image
|
imageToPDF.fitDocumentToImage=Şəklə Uyğun Səhifə
|
||||||
imageToPDF.maintainAspectRatio=Maintain Aspect Ratios
|
imageToPDF.maintainAspectRatio=Aspekt Nisbətlərini Qoruyun
|
||||||
imageToPDF.selectText.2=Auto rotate PDF
|
imageToPDF.selectText.2=PDF-i Avtomatik Fırlat
|
||||||
imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images)
|
imageToPDF.selectText.3=Çoxsaylı Fayl Məntiqi (Yalnız Birdən Çox Şəkil İlə İşləyərkən Aktivdir)
|
||||||
imageToPDF.selectText.4=Merge into single PDF
|
imageToPDF.selectText.4=Tək Bir PDF-ə Birləşdir
|
||||||
imageToPDF.selectText.5=Convert to separate PDFs
|
imageToPDF.selectText.5=Ayrı PDF-lərə Çevirin
|
||||||
|
|
||||||
|
|
||||||
#pdfToImage
|
#pdfToImage
|
||||||
@@ -1026,97 +1042,98 @@ pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir
|
|||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
addPassword.title=Add Password
|
addPassword.title=Şifr Əlavə Et
|
||||||
addPassword.header=Add password (Encrypt)
|
addPassword.header=Şifr Əlavə Et (Şifrləmə)
|
||||||
addPassword.selectText.1=Select PDF to encrypt
|
addPassword.selectText.1=Şifrlənəcək PDF-i seç
|
||||||
addPassword.selectText.2=User Password
|
addPassword.selectText.2=İstifadəçi Şifri
|
||||||
addPassword.selectText.3=Encryption Key Length
|
addPassword.selectText.3=Şifrləmə Açarı Uzunluğu
|
||||||
addPassword.selectText.4=Higher values are stronger, but lower values have better compatibility.
|
addPassword.selectText.4=Böyük dəyərlər daha güclüdür, lakin kiçik dəyərlərin uyğunluğu yüksəkdir.
|
||||||
addPassword.selectText.5=Permissions to set (Recommended to be used along with Owner password)
|
addPassword.selectText.5=Təyin olunacaq icazə (Sahib (Owner) Şifri ilə birgə istifadə olunması tövsiyə olunur.)
|
||||||
addPassword.selectText.6=Prevent assembly of document
|
addPassword.selectText.6=Sənədin strukturunun dəyişilməsinin qarşısını al
|
||||||
addPassword.selectText.7=Prevent content extraction
|
addPassword.selectText.7=Məzmun xaric edilməsinin qarşısını al
|
||||||
addPassword.selectText.8=Prevent extraction for accessibility
|
addPassword.selectText.8=Əlçatanlıq üçün xaricetmənin qarşısını al
|
||||||
addPassword.selectText.9=Prevent filling in form
|
addPassword.selectText.9=Anketin doldurulmasının qarşısını al
|
||||||
addPassword.selectText.10=Prevent modification
|
addPassword.selectText.10=Modifikasiyanın qarşısını al
|
||||||
addPassword.selectText.11=Prevent annotation modification
|
addPassword.selectText.11=Sitat modifikasiyasının qarşısını al
|
||||||
addPassword.selectText.12=Prevent printing
|
addPassword.selectText.12=Çap etmənin qarşısını al
|
||||||
addPassword.selectText.13=Prevent printing different formats
|
addPassword.selectText.13=Müxtəlif formatların çap edilməsinin qarşısını al
|
||||||
addPassword.selectText.14=Owner Password
|
addPassword.selectText.14=Sahib Şifri
|
||||||
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
|
addPassword.selectText.15=Sənəd açıldıqdan sonra onunla nə edilə biləcəyini limitləndir (Bütün oxuyucular dəstəkləmir)
|
||||||
addPassword.selectText.16=Restricts the opening of the document itself
|
addPassword.selectText.16=Sənədin özünün açılmağını limitləndirir
|
||||||
addPassword.submit=Encrypt
|
addPassword.submit=Şifrlə
|
||||||
|
|
||||||
|
|
||||||
#watermark
|
#watermark
|
||||||
watermark.title=Add Watermark
|
watermark.title=Watermark Əlavə Et
|
||||||
watermark.header=Add Watermark
|
watermark.header=Watermark Əlavə Et
|
||||||
watermark.selectText.1=Select PDF to add watermark to:
|
watermark.customColor=Fərdi Mətn Rəngi
|
||||||
watermark.selectText.2=Watermark Text:
|
watermark.selectText.1=Watermark əlavə olunacaq PDF-i seç
|
||||||
watermark.selectText.3=Font Size:
|
watermark.selectText.2=Watermark Mətni:
|
||||||
watermark.selectText.4=Rotation (0-360):
|
watermark.selectText.3=Şrift Ölçüsü:
|
||||||
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
watermark.selectText.4=Fırlatma (0-360):
|
||||||
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
watermark.selectText.5=enBoşluq (Üfuqi olaraq watermark-lar arasındakı məsafə):
|
||||||
watermark.selectText.7=Opacity (0% - 100%):
|
watermark.selectText.6=uzunluqBoşluq (Şaquli olaraq watermark-lar arasındakı məsafə):
|
||||||
watermark.selectText.8=Watermark Type:
|
watermark.selectText.7=Şəffaflıq (0% - 100%):
|
||||||
watermark.selectText.9=Watermark Image:
|
watermark.selectText.8=Watermark Tipi:
|
||||||
watermark.selectText.10=Convert PDF to PDF-Image
|
watermark.selectText.9=Watermark Şəkili:
|
||||||
watermark.submit=Add Watermark
|
watermark.selectText.10=PDF-i PDF-Şəkil-ə çevir
|
||||||
watermark.type.1=Text
|
watermark.submit=Watermark Əlavə Et
|
||||||
watermark.type.2=Image
|
watermark.type.1=Mətn
|
||||||
|
watermark.type.2=Şəkil
|
||||||
|
|
||||||
|
|
||||||
#Change permissions
|
#Change permissions
|
||||||
permissions.title=Change Permissions
|
permissions.title=İcazələri Dəyişdir
|
||||||
permissions.header=Change Permissions
|
permissions.header=İcazələri Dəyişdir
|
||||||
permissions.warning=Warning to have these permissions be unchangeable it is recommended to set them with a password via the add-password page
|
permissions.warning=Bu İcazələrin Dəyişməz Olması İlə Bağlı Xəbərdarlıq Edərək, Onları Parol Əlavə Et Səhifəsi Vasitəsilə Parolla Təyin Etmək Tövsiyə Olunur.
|
||||||
permissions.selectText.1=Select PDF to change permissions
|
permissions.selectText.1=İcazələri Dəyişdirmək Üçün PDF-i Seç
|
||||||
permissions.selectText.2=Permissions to set
|
permissions.selectText.2=Tənzimlənmiş İcazələr
|
||||||
permissions.selectText.3=Prevent assembly of document
|
permissions.selectText.3=Sənədin Yığılmasının Qarşısını Al
|
||||||
permissions.selectText.4=Prevent content extraction
|
permissions.selectText.4=Məzmunun Çıxarılmasının Qarşısını Al
|
||||||
permissions.selectText.5=Prevent extraction for accessibility
|
permissions.selectText.5=Əlçatanlıq Üçün Çıxarılmasının Qarşısını Alın
|
||||||
permissions.selectText.6=Prevent filling in form
|
permissions.selectText.6=Formanın Doldurulmasının Qarşısını Alır
|
||||||
permissions.selectText.7=Prevent modification
|
permissions.selectText.7=Modifikasiyanın Qarşısını Al
|
||||||
permissions.selectText.8=Prevent annotation modification
|
permissions.selectText.8=Annotasiyanın Dəyişdirilməsinin Qarşısını Almaq
|
||||||
permissions.selectText.9=Prevent printing
|
permissions.selectText.9=Çapın Qarşısını Al
|
||||||
permissions.selectText.10=Prevent printing different formats
|
permissions.selectText.10=Fərqli Formatlarda Çapın Qarşısını Al
|
||||||
permissions.submit=Change
|
permissions.submit=Dəyiş
|
||||||
|
|
||||||
|
|
||||||
#remove password
|
#remove password
|
||||||
removePassword.title=Remove password
|
removePassword.title=Şifri Sil
|
||||||
removePassword.header=Remove password (Decrypt)
|
removePassword.header=Şifri Sil (Deşifr)
|
||||||
removePassword.selectText.1=Select PDF to Decrypt
|
removePassword.selectText.1=Deşifr Üçün PDF-i Seç
|
||||||
removePassword.selectText.2=Password
|
removePassword.selectText.2=Şifr
|
||||||
removePassword.submit=Remove
|
removePassword.submit=Sil
|
||||||
|
|
||||||
|
|
||||||
#changeMetadata
|
#changeMetadata
|
||||||
changeMetadata.title=Change Metadata
|
changeMetadata.title=Metadata-nı Dəyiş
|
||||||
changeMetadata.header=Change Metadata
|
changeMetadata.header=Metadata-nı Dəyiş
|
||||||
changeMetadata.selectText.1=Please edit the variables you wish to change
|
changeMetadata.selectText.1=Dəyişmək istədiyiniz dəyişənləri redaktə edin
|
||||||
changeMetadata.selectText.2=Delete all metadata
|
changeMetadata.selectText.2=Bütün Metadata-nı Sil
|
||||||
changeMetadata.selectText.3=Show Custom Metadata:
|
changeMetadata.selectText.3=Fərdi Metadatanı göstərin:
|
||||||
changeMetadata.author=Author:
|
changeMetadata.author=Müəllif:
|
||||||
changeMetadata.creationDate=Creation Date (yyyy/MM/dd HH:mm:ss):
|
changeMetadata.creationDate=Yaradılma Tarixi (yyyy/MM/dd HH:mm:ss):
|
||||||
changeMetadata.creator=Creator:
|
changeMetadata.creator=Yaradıcı:
|
||||||
changeMetadata.keywords=Keywords:
|
changeMetadata.keywords=Açar Sözlər:
|
||||||
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
|
changeMetadata.modDate=Dəyişiklik Tarixi (yyyy/MM/dd HH:mm:ss):
|
||||||
changeMetadata.producer=Producer:
|
changeMetadata.producer=İstehsalçı:
|
||||||
changeMetadata.subject=Subject:
|
changeMetadata.subject=Mövzu:
|
||||||
changeMetadata.trapped=Trapped:
|
changeMetadata.trapped=Tələ:
|
||||||
changeMetadata.selectText.4=Other Metadata:
|
changeMetadata.selectText.4=Digər Metadata:
|
||||||
changeMetadata.selectText.5=Add Custom Metadata Entry
|
changeMetadata.selectText.5=Xüsusi Metadata girişi əlavə edin
|
||||||
changeMetadata.submit=Change
|
changeMetadata.submit=Dəyiş
|
||||||
|
|
||||||
|
|
||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF To PDF/A
|
pdfToPDFA.title=PDF-i PDF/A-ya
|
||||||
pdfToPDFA.header=PDF To PDF/A
|
pdfToPDFA.header=PDF-i PDF/A-ya
|
||||||
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
pdfToPDFA.credit=Bu Servis PDF/A Çevirmək Üçün ghostscript İşlədir
|
||||||
pdfToPDFA.submit=Convert
|
pdfToPDFA.submit=Çevir
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Hazırda Birdən Çox Giriş Üçün İşləmir
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Çıxış Formatı
|
||||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
pdfToPDFA.pdfWithDigitalSignature=PDF Rəqəmsal İmza Ehtiva Edir.Bu, növbəti addımda silinəcək.
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Səviyyəsi: Bölmə üçün istifadə ediləcə
|
|||||||
splitByChapters.desc.3=Metadatanı daxil edin: Əgər yoxlanılıbsa, orijinal PDF-in metadatası hər bir bölünmüş PDF-ə daxil ediləcək.
|
splitByChapters.desc.3=Metadatanı daxil edin: Əgər yoxlanılıbsa, orijinal PDF-in metadatası hər bir bölünmüş PDF-ə daxil ediləcək.
|
||||||
splitByChapters.desc.4=Allow Duplicates: Dublikatlara icazə verin: Əgər işarələnərsə, eyni səhifədə birdən çox bookmarka ayrı-ayrı PDF sənədləri yaratmağa icazə verin.
|
splitByChapters.desc.4=Allow Duplicates: Dublikatlara icazə verin: Əgər işarələnərsə, eyni səhifədə birdən çox bookmarka ayrı-ayrı PDF sənədləri yaratmağa icazə verin.
|
||||||
splitByChapters.submit=PDF-i Ayır
|
splitByChapters.submit=PDF-i Ayır
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Buraxılışlar
|
||||||
|
releases.title=Buraxılış Qeydləri
|
||||||
|
releases.header=Buraxılış Qeydləri
|
||||||
|
releases.current.version=Hazırki Buraxılış
|
||||||
|
releases.note=Buraxılış Qeydləri yalnız ingiliscə mövcuddur
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Разделете PDF по глави
|
|||||||
home.splitPdfByChapters.desc=Разделете PDF на множество файлове въз основа на неговата структура на глави.
|
home.splitPdfByChapters.desc=Разделете PDF на множество файлове въз основа на неговата структура на глави.
|
||||||
splitPdfByChapters.tags=разделяне, глави, отметки, организиране
|
splitPdfByChapters.tags=разделяне, глави, отметки, организиране
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Замени-инвертиране-на-цвят
|
replace-color.title=Замени-инвертиране-на-цвят
|
||||||
replace-color.header=Замяна-инвертиране на цвят PDF
|
replace-color.header=Замяна-инвертиране на цвят PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR режим
|
|||||||
ocr.selectText.11=Премахване на изображения след OCR (Премахва ВСИЧКИ изображения, полезно само ако е част от стъпката на преобразуване)
|
ocr.selectText.11=Премахване на изображения след OCR (Премахва ВСИЧКИ изображения, полезно само ако е част от стъпката на преобразуване)
|
||||||
ocr.selectText.12=Тип изобразяване (Разширен)
|
ocr.selectText.12=Тип изобразяване (Разширен)
|
||||||
ocr.help=Моля, прочетете тази документация за това как да използвате това за други езици и/или да не използвате в docker
|
ocr.help=Моля, прочетете тази документация за това как да използвате това за други езици и/или да не използвате в docker
|
||||||
ocr.credit=Тази услуга използва OCRmyPDF и Tesseract за OCR.
|
ocr.credit=Тази услуга използва qpdf и Tesseract за OCR.
|
||||||
ocr.submit=Обработка на PDF чрез OCR
|
ocr.submit=Обработка на PDF чрез OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Преобразуване към PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Компресиране
|
compress.title=Компресиране
|
||||||
compress.header=Компресиране на PDF
|
compress.header=Компресиране на PDF
|
||||||
compress.credit=Тази услуга използва Ghostscript за PDF компресиране/оптимизиране.
|
compress.credit=Тази услуга използва qpdf за PDF компресиране/оптимизиране.
|
||||||
compress.selectText.1=Ръчен режим - от 1 до 4
|
compress.selectText.1=Ръчен режим - от 1 до 4
|
||||||
compress.selectText.2=Ниво на оптимизация:
|
compress.selectText.2=Ниво на оптимизация:
|
||||||
compress.selectText.3=4 (Ужасно за текстови изображения)
|
compress.selectText.3=4 (Ужасно за текстови изображения)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Шифроване
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Добавяне на воден знак
|
watermark.title=Добавяне на воден знак
|
||||||
watermark.header=Добавяне на воден знак
|
watermark.header=Добавяне на воден знак
|
||||||
|
watermark.customColor=Персонализиран цвят на текста
|
||||||
watermark.selectText.1=Изберете PDF, към който да добавите воден знак:
|
watermark.selectText.1=Изберете PDF, към който да добавите воден знак:
|
||||||
watermark.selectText.2=Текст на воден знак:
|
watermark.selectText.2=Текст на воден знак:
|
||||||
watermark.selectText.3=Размер на шрифта:
|
watermark.selectText.3=Размер на шрифта:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Промени
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF към PDF/A
|
pdfToPDFA.title=PDF към PDF/A
|
||||||
pdfToPDFA.header=PDF към PDF/A
|
pdfToPDFA.header=PDF към PDF/A
|
||||||
pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A преобразуване.
|
pdfToPDFA.credit=Тази услуга използва qpdf за PDF/A преобразуване.
|
||||||
pdfToPDFA.submit=Преобразуване
|
pdfToPDFA.submit=Преобразуване
|
||||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||||
pdfToPDFA.outputFormat=Изходен формат
|
pdfToPDFA.outputFormat=Изходен формат
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Ниво на отметка: Изберете нивот
|
|||||||
splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF.
|
splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF.
|
||||||
splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове.
|
splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове.
|
||||||
splitByChapters.submit=Разделяне на PDF
|
splitByChapters.submit=Разделяне на PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Divideix PDF per Capítols
|
|||||||
home.splitPdfByChapters.desc=Divideix un PDF en múltiples fitxers segons la seva estructura de capítols.
|
home.splitPdfByChapters.desc=Divideix un PDF en múltiples fitxers segons la seva estructura de capítols.
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Reemplaça-Inverteix-Color
|
replace-color.title=Reemplaça-Inverteix-Color
|
||||||
replace-color.header=Reemplaça-Inverteix Color en PDF
|
replace-color.header=Reemplaça-Inverteix Color en PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Mode OCR
|
|||||||
ocr.selectText.11=Elimina Imatges després de l'OCR (Elimina TOTES les imatges, útil si forma part d'un procés de conversió)
|
ocr.selectText.11=Elimina Imatges després de l'OCR (Elimina TOTES les imatges, útil si forma part d'un procés de conversió)
|
||||||
ocr.selectText.12=Tipus de Renderització (Avançat)
|
ocr.selectText.12=Tipus de Renderització (Avançat)
|
||||||
ocr.help=Llegeix aquesta documentació sobre com utilitzar-la per a altres idiomes i/o no utilitzar-la a Docker
|
ocr.help=Llegeix aquesta documentació sobre com utilitzar-la per a altres idiomes i/o no utilitzar-la a Docker
|
||||||
ocr.credit=Aquest servei fa servir OCRmyPDF i Tesseract per a OCR.
|
ocr.credit=Aquest servei fa servir qpdf i Tesseract per a OCR.
|
||||||
ocr.submit=Processa PDF amb OCR
|
ocr.submit=Processa PDF amb OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Converteix a PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Aquest servei utilitza Ghostscript per a la compressió/optimització de PDF.
|
compress.credit=Aquest servei utilitza qpdf per a la compressió/optimització de PDF.
|
||||||
compress.selectText.1=Mode manual: de l'1 al 4
|
compress.selectText.1=Mode manual: de l'1 al 4
|
||||||
compress.selectText.2=Nivell d'optimització:
|
compress.selectText.2=Nivell d'optimització:
|
||||||
compress.selectText.3=4 (terrible per a imatges de text)
|
compress.selectText.3=4 (terrible per a imatges de text)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Encripta
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Afegir Marca d'Aigua
|
watermark.title=Afegir Marca d'Aigua
|
||||||
watermark.header=Afegir Marca d'Aigua
|
watermark.header=Afegir Marca d'Aigua
|
||||||
|
watermark.customColor=Color de Text Personalitzat
|
||||||
watermark.selectText.1=Selecciona el PDF per afegir la Marca d'Aigua:
|
watermark.selectText.1=Selecciona el PDF per afegir la Marca d'Aigua:
|
||||||
watermark.selectText.2=Text de la Marca d'Aigua
|
watermark.selectText.2=Text de la Marca d'Aigua
|
||||||
watermark.selectText.3=Mida de la Font:
|
watermark.selectText.3=Mida de la Font:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Canvia
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF a PDF/A
|
pdfToPDFA.title=PDF a PDF/A
|
||||||
pdfToPDFA.header=PDF a PDF/A
|
pdfToPDFA.header=PDF a PDF/A
|
||||||
pdfToPDFA.credit=Utilitza Ghostscript per a la conversió a PDF/A
|
pdfToPDFA.credit=Utilitza qpdf per a la conversió a PDF/A
|
||||||
pdfToPDFA.submit=Converteix
|
pdfToPDFA.submit=Converteix
|
||||||
pdfToPDFA.tip=Actualment no funciona per a múltiples entrades al mateix temps
|
pdfToPDFA.tip=Actualment no funciona per a múltiples entrades al mateix temps
|
||||||
pdfToPDFA.outputFormat=Format de sortida
|
pdfToPDFA.outputFormat=Format de sortida
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Nivell de Marcadors: Tria el nivell de marcadors que s'ut
|
|||||||
splitByChapters.desc.3=Incloure Metadades: Si està marcat, les metadades del PDF original s'inclouran en cada PDF dividit.
|
splitByChapters.desc.3=Incloure Metadades: Si està marcat, les metadades del PDF original s'inclouran en cada PDF dividit.
|
||||||
splitByChapters.desc.4=Permetre Duplicats: Si està marcat, permet diversos marcadors a la mateixa pàgina per crear PDFs separats.
|
splitByChapters.desc.4=Permetre Duplicats: Si està marcat, permet diversos marcadors a la mateixa pàgina per crear PDFs separats.
|
||||||
splitByChapters.submit=Divideix PDF
|
splitByChapters.submit=Divideix PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Rozdělit PDF podle kapitol
|
|||||||
home.splitPdfByChapters.desc=Rozdělit PDF do více souborů na základě jeho struktury kapitol.
|
home.splitPdfByChapters.desc=Rozdělit PDF do více souborů na základě jeho struktury kapitol.
|
||||||
splitPdfByChapters.tags=rozdělení, kapitoly, zápisky, organizace
|
splitPdfByChapters.tags=rozdělení, kapitoly, zápisky, organizace
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Nahradit inverzní barvu PDF
|
replace-color.header=Nahradit inverzní barvu PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Režim OCR
|
|||||||
ocr.selectText.11=Odstranit obrázky po OCR (Odstraní VŠECHNY obrázky, užitečné pouze jako součást kroku konverze)
|
ocr.selectText.11=Odstranit obrázky po OCR (Odstraní VŠECHNY obrázky, užitečné pouze jako součást kroku konverze)
|
||||||
ocr.selectText.12=Typ vykreslení (Pokročilé)
|
ocr.selectText.12=Typ vykreslení (Pokročilé)
|
||||||
ocr.help=Prosím, přečtěte si tuto dokumentaci o použití pro jiné jazyky a/nebo použití mimo Docker
|
ocr.help=Prosím, přečtěte si tuto dokumentaci o použití pro jiné jazyky a/nebo použití mimo Docker
|
||||||
ocr.credit=Tato služba používá OCRmyPDF a Tesseract pro OCR.
|
ocr.credit=Tato služba používá qpdf a Tesseract pro OCR.
|
||||||
ocr.submit=Zpracovat PDF s OCR
|
ocr.submit=Zpracovat PDF s OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Převést na PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Komprese
|
compress.title=Komprese
|
||||||
compress.header=Komprimovat PDF
|
compress.header=Komprimovat PDF
|
||||||
compress.credit=Tato služba používá Ghostscript pro kompresi/optimalizaci PDF.
|
compress.credit=Tato služba používá qpdf pro kompresi/optimalizaci PDF.
|
||||||
compress.selectText.1=Ruční režim - Od 1 do 4
|
compress.selectText.1=Ruční režim - Od 1 do 4
|
||||||
compress.selectText.2=Úroveň optimalizace:
|
compress.selectText.2=Úroveň optimalizace:
|
||||||
compress.selectText.3=4 (Hrozné pro textové obrázky)
|
compress.selectText.3=4 (Hrozné pro textové obrázky)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Šifrovat
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Přidat vodoznak
|
watermark.title=Přidat vodoznak
|
||||||
watermark.header=Přidat vodoznak
|
watermark.header=Přidat vodoznak
|
||||||
|
watermark.customColor=Vlastní barva textu
|
||||||
watermark.selectText.1=Vyberte PDF, ke kterému chcete přidat vodoznak:
|
watermark.selectText.1=Vyberte PDF, ke kterému chcete přidat vodoznak:
|
||||||
watermark.selectText.2=Text vodoznaku:
|
watermark.selectText.2=Text vodoznaku:
|
||||||
watermark.selectText.3=Velikost písma:
|
watermark.selectText.3=Velikost písma:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Změnit
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF na PDF/A
|
pdfToPDFA.title=PDF na PDF/A
|
||||||
pdfToPDFA.header=PDF na PDF/A
|
pdfToPDFA.header=PDF na PDF/A
|
||||||
pdfToPDFA.credit=Tato služba používá ghostscript pro konverzi do formátu PDF/A
|
pdfToPDFA.credit=Tato služba používá qpdf pro konverzi do formátu PDF/A
|
||||||
pdfToPDFA.submit=Převést
|
pdfToPDFA.submit=Převést
|
||||||
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
||||||
pdfToPDFA.outputFormat=Výstupní formát
|
pdfToPDFA.outputFormat=Výstupní formát
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Úroveň záhlaví: Zvolte úroveň záhlaví pro použit
|
|||||||
splitByChapters.desc.3=Zahrnout metadatů: Pokud je zaškrtnuto, původní metadata PDF souboru budou zahrnuty do každého odděleného PDF souboru.
|
splitByChapters.desc.3=Zahrnout metadatů: Pokud je zaškrtnuto, původní metadata PDF souboru budou zahrnuty do každého odděleného PDF souboru.
|
||||||
splitByChapters.desc.4=Povolit duplicitní záznamy: Pokud je zaškrtnuto, návštěvníci mohou vytvořit samostatné PDF soubory z více záhlaví na stejné straně.
|
splitByChapters.desc.4=Povolit duplicitní záznamy: Pokud je zaškrtnuto, návštěvníci mohou vytvořit samostatné PDF soubory z více záhlaví na stejné straně.
|
||||||
splitByChapters.submit=Podělit se PDF
|
splitByChapters.submit=Podělit se PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Partitioner PDF efter kapitler
|
|||||||
home.splitPdfByChapters.desc=Partitioner en PDF i flere filer baseret på dens kapitelstruktur.
|
home.splitPdfByChapters.desc=Partitioner en PDF i flere filer baseret på dens kapitelstruktur.
|
||||||
splitPdfByChapters.tags=partitionering,kapitler,merker,organisering
|
splitPdfByChapters.tags=partitionering,kapitler,merker,organisering
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Erstat-omgivende Farve PDF
|
replace-color.header=Erstat-omgivende Farve PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR-tilstand
|
|||||||
ocr.selectText.11=Fjern billeder efter OCR (Fjerner ALLE billeder, kun nyttigt hvis det er en del af konverteringstrinnet)
|
ocr.selectText.11=Fjern billeder efter OCR (Fjerner ALLE billeder, kun nyttigt hvis det er en del af konverteringstrinnet)
|
||||||
ocr.selectText.12=Renderingstype (Avanceret)
|
ocr.selectText.12=Renderingstype (Avanceret)
|
||||||
ocr.help=Læs venligst denne dokumentation om, hvordan man bruger dette til andre sprog og/eller brug uden for docker
|
ocr.help=Læs venligst denne dokumentation om, hvordan man bruger dette til andre sprog og/eller brug uden for docker
|
||||||
ocr.credit=Denne tjeneste bruger OCRmyPDF og Tesseract til OCR.
|
ocr.credit=Denne tjeneste bruger qpdf og Tesseract til OCR.
|
||||||
ocr.submit=Behandl PDF med OCR
|
ocr.submit=Behandl PDF med OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Konvertér til PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Komprimer
|
compress.title=Komprimer
|
||||||
compress.header=Komprimer PDF
|
compress.header=Komprimer PDF
|
||||||
compress.credit=Denne tjeneste bruger Ghostscript til PDF Komprimering/Optimering.
|
compress.credit=Denne tjeneste bruger qpdf til PDF Komprimering/Optimering.
|
||||||
compress.selectText.1=Manuel Tilstand - Fra 1 til 4
|
compress.selectText.1=Manuel Tilstand - Fra 1 til 4
|
||||||
compress.selectText.2=Optimeringsniveau:
|
compress.selectText.2=Optimeringsniveau:
|
||||||
compress.selectText.3=4 (Forfærdelig for tekstbilleder)
|
compress.selectText.3=4 (Forfærdelig for tekstbilleder)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Kryptér
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Tilføj Vandmærke
|
watermark.title=Tilføj Vandmærke
|
||||||
watermark.header=Tilføj Vandmærke
|
watermark.header=Tilføj Vandmærke
|
||||||
|
watermark.customColor=Brugerdefineret Tekstfarve
|
||||||
watermark.selectText.1=Vælg PDF til at tilføje vandmærke:
|
watermark.selectText.1=Vælg PDF til at tilføje vandmærke:
|
||||||
watermark.selectText.2=Vandmærketekst:
|
watermark.selectText.2=Vandmærketekst:
|
||||||
watermark.selectText.3=Skriftstørrelse:
|
watermark.selectText.3=Skriftstørrelse:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Ændre
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF Til PDF/A
|
pdfToPDFA.title=PDF Til PDF/A
|
||||||
pdfToPDFA.header=PDF Til PDF/A
|
pdfToPDFA.header=PDF Til PDF/A
|
||||||
pdfToPDFA.credit=Denne tjeneste bruger ghostscript til PDF/A-konvertering
|
pdfToPDFA.credit=Denne tjeneste bruger qpdf til PDF/A-konvertering
|
||||||
pdfToPDFA.submit=Konvertér
|
pdfToPDFA.submit=Konvertér
|
||||||
pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang
|
pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang
|
||||||
pdfToPDFA.outputFormat=Outputformat
|
pdfToPDFA.outputFormat=Outputformat
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bogmærke niveau: Vælg nivået af bogmærker, der skal b
|
|||||||
splitByChapters.desc.3=Inkluder metadata: Hvis markeret, vil den originale PDF's metadata inkluderes i hver splitterdels PDF.
|
splitByChapters.desc.3=Inkluder metadata: Hvis markeret, vil den originale PDF's metadata inkluderes i hver splitterdels PDF.
|
||||||
splitByChapters.desc.4=Tillad duplikater: Hvis markeret, tillader det flere bogmærker på samme side til at oprette separate PDF'er.
|
splitByChapters.desc.4=Tillad duplikater: Hvis markeret, tillader det flere bogmærker på samme side til at oprette separate PDF'er.
|
||||||
splitByChapters.submit=Splitter PDF
|
splitByChapters.submit=Splitter PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ navbar.search=Suche
|
|||||||
navbar.sections.organize=Organisieren
|
navbar.sections.organize=Organisieren
|
||||||
navbar.sections.convertTo=In PDF konvertieren
|
navbar.sections.convertTo=In PDF konvertieren
|
||||||
navbar.sections.convertFrom=Konvertieren von PDF
|
navbar.sections.convertFrom=Konvertieren von PDF
|
||||||
navbar.sections.security=Zeichen und Sicherheit
|
navbar.sections.security=Signieren und Sicherheit
|
||||||
navbar.sections.advance=Fortschrittlich
|
navbar.sections.advance=Erweiterte Funktionen
|
||||||
navbar.sections.edit=Anzeigen und Bearbeiten
|
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||||
navbar.sections.popular=Beliebt
|
navbar.sections.popular=Beliebt
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ database.fileNullOrEmpty=Datei darf nicht null oder leer sein
|
|||||||
database.failedImportFile=Dateiimport fehlgeschlagen
|
database.failedImportFile=Dateiimport fehlgeschlagen
|
||||||
|
|
||||||
session.expired=Ihre Sitzung ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.
|
session.expired=Ihre Sitzung ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.
|
||||||
session.refreshPage=Refresh Page
|
session.refreshPage=Seite aktualisieren
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=PDF-Datei nach Kapiteln aufteilen
|
|||||||
home.splitPdfByChapters.desc=Aufteilung einer PDF-Datei in mehrere Dateien auf Basis der Kapitelstruktur.
|
home.splitPdfByChapters.desc=Aufteilung einer PDF-Datei in mehrere Dateien auf Basis der Kapitelstruktur.
|
||||||
splitPdfByChapters.tags=aufteilen,kapitel,lesezeichen,organisieren
|
splitPdfByChapters.tags=aufteilen,kapitel,lesezeichen,organisieren
|
||||||
|
|
||||||
|
home.validateSignature.title=PDF-Signatur überprüfen
|
||||||
|
home.validateSignature.desc=Digitale Signaturen und Zertifikate in PDF-Dokumenten überprüfen
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,digitale signatur,signatur validieren,überprüfen,Zertifikat,cert
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Farbe Ersetzen-Invertieren
|
replace-color.title=Farbe Ersetzen-Invertieren
|
||||||
replace-color.header=Farb-PDF Ersetzen-Invertieren
|
replace-color.header=Farb-PDF Ersetzen-Invertieren
|
||||||
@@ -818,12 +822,12 @@ sign.save=Signature speichern
|
|||||||
sign.personalSigs=Persönliche Signaturen
|
sign.personalSigs=Persönliche Signaturen
|
||||||
sign.sharedSigs=Geteilte Signaturen
|
sign.sharedSigs=Geteilte Signaturen
|
||||||
sign.noSavedSigs=Es wurden keine gespeicherten Signaturen gefunden
|
sign.noSavedSigs=Es wurden keine gespeicherten Signaturen gefunden
|
||||||
sign.addToAll=Add to all pages
|
sign.addToAll=Zu allen Seiten hinzufügen
|
||||||
sign.delete=Delete
|
sign.delete=Löschen
|
||||||
sign.first=First page
|
sign.first=Erste Seite
|
||||||
sign.last=Last page
|
sign.last=Letzte Seite
|
||||||
sign.next=Next page
|
sign.next=Nächste Seite
|
||||||
sign.previous=Previous page
|
sign.previous=Vorherige Seite
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
repair.title=Reparieren
|
repair.title=Reparieren
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR-Modus
|
|||||||
ocr.selectText.11=Bilder nach OCR entfernen (Entfernt ALLE Bilder, nur sinnvoll, wenn Teil des Konvertierungsschritts)
|
ocr.selectText.11=Bilder nach OCR entfernen (Entfernt ALLE Bilder, nur sinnvoll, wenn Teil des Konvertierungsschritts)
|
||||||
ocr.selectText.12=Rendertyp (Erweitert)
|
ocr.selectText.12=Rendertyp (Erweitert)
|
||||||
ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können
|
ocr.help=Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können
|
||||||
ocr.credit=Dieser Dienst verwendet OCRmyPDF und Tesseract für OCR.
|
ocr.credit=Dieser Dienst verwendet qpdf und Tesseract für OCR.
|
||||||
ocr.submit=PDF mit OCR verarbeiten
|
ocr.submit=PDF mit OCR verarbeiten
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=In PDF konvertieren
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Komprimieren
|
compress.title=Komprimieren
|
||||||
compress.header=PDF komprimieren
|
compress.header=PDF komprimieren
|
||||||
compress.credit=Dieser Dienst verwendet Ghostscript für die PDF-Komprimierung/-Optimierung.
|
compress.credit=Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.
|
||||||
compress.selectText.1=Manueller Modus – Von 1 bis 4
|
compress.selectText.1=Manueller Modus – Von 1 bis 4
|
||||||
compress.selectText.2=Optimierungsstufe:
|
compress.selectText.2=Optimierungsstufe:
|
||||||
compress.selectText.3=4 (Schrecklich für Textbilder)
|
compress.selectText.3=4 (Schrecklich für Textbilder)
|
||||||
@@ -949,15 +953,27 @@ multiTool.deleteSelected=Auswahl löschen
|
|||||||
multiTool.downloadAll=Downloaden
|
multiTool.downloadAll=Downloaden
|
||||||
multiTool.downloadSelected=Auswahl downloaden
|
multiTool.downloadSelected=Auswahl downloaden
|
||||||
|
|
||||||
multiTool.insertPageBreak=Insert Page Break
|
multiTool.insertPageBreak=Seitenumbruch einfügen
|
||||||
multiTool.addFile=Add File
|
multiTool.addFile=Datei hinzufügen
|
||||||
multiTool.rotateLeft=Rotate Left
|
multiTool.rotateLeft=Nach links drehen
|
||||||
multiTool.rotateRight=Rotate Right
|
multiTool.rotateRight=Nach rechts drehen
|
||||||
multiTool.split=Split
|
multiTool.split=Teilen
|
||||||
multiTool.moveLeft=Move Left
|
multiTool.moveLeft=Nach links verschieben
|
||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Nach rechts verschieben
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Löschen
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Ausgewählte Seite(n)
|
||||||
|
multiTool.undo=Rückgängig machen
|
||||||
|
multiTool.redo=Wiederherstellen
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
|
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Verschlüsseln
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Wasserzeichen hinzufügen
|
watermark.title=Wasserzeichen hinzufügen
|
||||||
watermark.header=Wasserzeichen hinzufügen
|
watermark.header=Wasserzeichen hinzufügen
|
||||||
|
watermark.customColor=Benutzerdefinierte Textfarbe
|
||||||
watermark.selectText.1=PDF auswählen, dem ein Wasserzeichen hinzugefügt werden soll:
|
watermark.selectText.1=PDF auswählen, dem ein Wasserzeichen hinzugefügt werden soll:
|
||||||
watermark.selectText.2=Wasserzeichen Text:
|
watermark.selectText.2=Wasserzeichen Text:
|
||||||
watermark.selectText.3=Schriftgröße:
|
watermark.selectText.3=Schriftgröße:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Ändern
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF zu PDF/A
|
pdfToPDFA.title=PDF zu PDF/A
|
||||||
pdfToPDFA.header=PDF zu PDF/A
|
pdfToPDFA.header=PDF zu PDF/A
|
||||||
pdfToPDFA.credit=Dieser Dienst verwendet ghostscript für die PDF/A-Konvertierung
|
pdfToPDFA.credit=Dieser Dienst verwendet qpdf für die PDF/A-Konvertierung
|
||||||
pdfToPDFA.submit=Konvertieren
|
pdfToPDFA.submit=Konvertieren
|
||||||
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
||||||
pdfToPDFA.outputFormat=Ausgabeformat
|
pdfToPDFA.outputFormat=Ausgabeformat
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Lesezeichenebene: Wählen Sie die Ebene der Lesezeichen,
|
|||||||
splitByChapters.desc.3=Metadaten einschließen: Wenn diese Option aktiviert ist, werden die Metadaten der ursprünglichen PDF-Datei in jede aufgeteilte PDF-Datei übernommen.
|
splitByChapters.desc.3=Metadaten einschließen: Wenn diese Option aktiviert ist, werden die Metadaten der ursprünglichen PDF-Datei in jede aufgeteilte PDF-Datei übernommen.
|
||||||
splitByChapters.desc.4=Duplikate erlauben: Wenn diese Option aktiviert ist, können mehrere Lesezeichen auf derselben Seite separate PDF Dateien erstellen.
|
splitByChapters.desc.4=Duplikate erlauben: Wenn diese Option aktiviert ist, können mehrere Lesezeichen auf derselben Seite separate PDF Dateien erstellen.
|
||||||
splitByChapters.submit=PDF teilen
|
splitByChapters.submit=PDF teilen
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Klicken
|
||||||
|
fileChooser.or=oder
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Datei(en) hierhin Ziehen & Fallenlassen
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Veröffentlichungen
|
||||||
|
releases.title=Versionshinweise
|
||||||
|
releases.header=Versionshinweise
|
||||||
|
releases.current.version=Aktuelle Version
|
||||||
|
releases.note=Versionshinweise sind nur auf Englisch verfügbar
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=PDF-Signaturen überprüfen
|
||||||
|
validateSignature.header=Digitale Signaturen überprüfen
|
||||||
|
validateSignature.selectPDF=Signierte PDF-Datei auswählen
|
||||||
|
validateSignature.submit=Signaturen überprüfen
|
||||||
|
validateSignature.results=Gültigkeitsprüfungsergebnisse
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Unterzeichner
|
||||||
|
validateSignature.date=Datum
|
||||||
|
validateSignature.reason=Grund
|
||||||
|
validateSignature.location=Ort
|
||||||
|
validateSignature.noSignatures=Keine digitalen Signaturen in diesem Dokument gefunden
|
||||||
|
validateSignature.status.valid=Gültig
|
||||||
|
validateSignature.status.invalid=Ungültig
|
||||||
|
validateSignature.chain.invalid=Zertifikatskettenprüfung fehlgeschlagen - kann die Identität des Unterzeichners nicht verifizieren
|
||||||
|
validateSignature.trust.invalid=Zertifikat nicht im Truststore - Quelle kann nicht verifiziert werden
|
||||||
|
validateSignature.cert.expired=Zertifikat ist abgelaufen
|
||||||
|
validateSignature.cert.revoked=Zertifikat wurde widerrufen
|
||||||
|
validateSignature.signature.info=Signaturinformationen
|
||||||
|
validateSignature.signature=Signatur
|
||||||
|
validateSignature.signature.mathValid=Signatur ist mathematisch gültig ABER:
|
||||||
|
validateSignature.selectCustomCert=Benutzerdefinierte Zertifikatsdatei X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Zertifikat Details
|
||||||
|
validateSignature.cert.issuer=Aussteller
|
||||||
|
validateSignature.cert.subject=Betreff
|
||||||
|
validateSignature.cert.serialNumber=Seriennummer
|
||||||
|
validateSignature.cert.validFrom=Gültig von
|
||||||
|
validateSignature.cert.validUntil=Gültig bis
|
||||||
|
validateSignature.cert.algorithm=Algorithmus
|
||||||
|
validateSignature.cert.keySize=Schlüsselgröße
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Schlüsselverwendung
|
||||||
|
validateSignature.cert.selfSigned=Selbstsigniert
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Διχοτομία PDF ανά Περιγραφές
|
|||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=διχοτομία,περιγραφές,κεφάλαια,συνορία
|
splitPdfByChapters.tags=διχοτομία,περιγραφές,κεφάλαια,συνορία
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Αντικατάσταση-Αντίστροφη Παθωμένη Πίντσουρ
|
replace-color.header=Αντικατάσταση-Αντίστροφη Παθωμένη Πίντσουρ
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Λειτουργία OCR
|
|||||||
ocr.selectText.11=Κατάργηση εικόνων μετά το OCR (Καταργεί ΟΛΕΣ τις εικόνες, είναι χρήσιμο μόνο αν αποτελεί μέρος του βήματος μετατροπής)
|
ocr.selectText.11=Κατάργηση εικόνων μετά το OCR (Καταργεί ΟΛΕΣ τις εικόνες, είναι χρήσιμο μόνο αν αποτελεί μέρος του βήματος μετατροπής)
|
||||||
ocr.selectText.12=Τύπος απόδοσης (Για προχωρημένους)
|
ocr.selectText.12=Τύπος απόδοσης (Για προχωρημένους)
|
||||||
ocr.help=Διαβάστε αυτήν την τεκμηρίωση σχετικά με τον τρόπο χρήσης αυτής για άλλες γλώσσες ή/και μη χρήσης σε docker
|
ocr.help=Διαβάστε αυτήν την τεκμηρίωση σχετικά με τον τρόπο χρήσης αυτής για άλλες γλώσσες ή/και μη χρήσης σε docker
|
||||||
ocr.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF και Tesseract για OCR.
|
ocr.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf και Tesseract για OCR.
|
||||||
ocr.submit=Επεξεργασία PDF με OCR
|
ocr.submit=Επεξεργασία PDF με OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Μετατροπή σε PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Συμπίεση
|
compress.title=Συμπίεση
|
||||||
compress.header=Συμπίεση PDF
|
compress.header=Συμπίεση PDF
|
||||||
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί Ghostscript για PDF Συμπίεση/Βελτιστοποίηση.
|
compress.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για PDF Συμπίεση/Βελτιστοποίηση.
|
||||||
compress.selectText.1=Χειροκίνητη Λειτουργία - Από 1 έως 4
|
compress.selectText.1=Χειροκίνητη Λειτουργία - Από 1 έως 4
|
||||||
compress.selectText.2=Επίπεδο Βελτιστοποίησης:
|
compress.selectText.2=Επίπεδο Βελτιστοποίησης:
|
||||||
compress.selectText.3=4 (Πολύ κακό για εικόνες κειμένου)
|
compress.selectText.3=4 (Πολύ κακό για εικόνες κειμένου)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,12 +1066,13 @@ addPassword.submit=Κρυπτογράφηση
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Προσθήκη Υδατογραφήματος
|
watermark.title=Προσθήκη Υδατογραφήματος
|
||||||
watermark.header=Προσθήκη Υδατογραφήματος
|
watermark.header=Προσθήκη Υδατογραφήματος
|
||||||
|
watermark.customColor=Προσαρμοσμένο χρώμα κειμένου
|
||||||
watermark.selectText.1=Επιλέξτε PDF για την προσθήκη του υδατογραφήματος:
|
watermark.selectText.1=Επιλέξτε PDF για την προσθήκη του υδατογραφήματος:
|
||||||
watermark.selectText.2=Κείμενο Υδατογραφήματος:
|
watermark.selectText.2=Κείμενο Υδατογραφήματος:
|
||||||
watermark.selectText.3=Μέγεθος Κειμένου:
|
watermark.selectText.3=Μέγεθος Κειμένου:
|
||||||
watermark.selectText.4=Περιστροφή (0-360):
|
watermark.selectText.4=Περιστροφή (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Κενό μεταξύ κάθε υδατογραφήματος οριζόντια):
|
watermark.selectText.5=Width Spacer (Κενό μεταξύ κάθε υδατογραφήματος οριζόντια):
|
||||||
watermark.selectText.6=heightSpacer (Κενό μεταξύ κάθε υδατογραφήματος κάθετα):
|
watermark.selectText.6=Height Spacer (Κενό μεταξύ κάθε υδατογραφήματος κάθετα):
|
||||||
watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
|
watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
|
||||||
watermark.selectText.8=Τύπος Υδατογραφήματος:
|
watermark.selectText.8=Τύπος Υδατογραφήματος:
|
||||||
watermark.selectText.9=Εικόνα Υδατογραφήματος:
|
watermark.selectText.9=Εικόνα Υδατογραφήματος:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Αλλαγή
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF σε PDF/A
|
pdfToPDFA.title=PDF σε PDF/A
|
||||||
pdfToPDFA.header=PDF σε PDF/A
|
pdfToPDFA.header=PDF σε PDF/A
|
||||||
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή
|
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί qpdf για PDF/A μετατροπή
|
||||||
pdfToPDFA.submit=Μετατροπή
|
pdfToPDFA.submit=Μετατροπή
|
||||||
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
||||||
pdfToPDFA.outputFormat=Εξόδος αναμορφώσεων
|
pdfToPDFA.outputFormat=Εξόδος αναμορφώσεων
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Επίπεδο Σήμανσης Σκέψης: Επιλέ
|
|||||||
splitByChapters.desc.3=Πρόσθεση Metadata: Αν επεξεργαστείται, οι αρχικές metadata του PDF θα προστεθούν σε κάθε διαλυμένο PDF.
|
splitByChapters.desc.3=Πρόσθεση Metadata: Αν επεξεργαστείται, οι αρχικές metadata του PDF θα προστεθούν σε κάθε διαλυμένο PDF.
|
||||||
splitByChapters.desc.4=Διάλυση Παρόντων Τίτλων Επιπέδου: Αν επεξεργαστείται, επιτρέπει τη δημιουργία αποκοπών PDF με βάση πλήρως καθορισμένους σήμαντες έδρας.
|
splitByChapters.desc.4=Διάλυση Παρόντων Τίτλων Επιπέδου: Αν επεξεργαστείται, επιτρέπει τη δημιουργία αποκοπών PDF με βάση πλήρως καθορισμένους σήμαντες έδρας.
|
||||||
splitByChapters.submit=Διαλύστε το PDF
|
splitByChapters.submit=Διαλύστε το PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ joinDiscord=Join our Discord server
|
|||||||
seeDockerHub=See Docker Hub
|
seeDockerHub=See Docker Hub
|
||||||
visitGithub=Visit Github Repository
|
visitGithub=Visit Github Repository
|
||||||
donate=Donate
|
donate=Donate
|
||||||
color=Color
|
color=Colour
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
@@ -419,9 +419,9 @@ 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
|
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||||
|
|
||||||
home.adjust-contrast.title=Adjust Colors/Contrast
|
home.adjust-contrast.title=Adjust Colours/Contrast
|
||||||
home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF
|
home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF
|
||||||
adjust-contrast.tags=color-correction,tune,modify,enhance
|
adjust-contrast.tags=color-correction,tune,modify,enhance,colour-correction
|
||||||
|
|
||||||
home.crop.title=Crop PDF
|
home.crop.title=Crop PDF
|
||||||
home.crop.desc=Crop a PDF to reduce its size (maintains text!)
|
home.crop.desc=Crop a PDF to reduce its size (maintains text!)
|
||||||
@@ -488,11 +488,11 @@ overlay-pdfs.tags=Overlay
|
|||||||
|
|
||||||
home.split-by-sections.title=Split PDF by Sections
|
home.split-by-sections.title=Split PDF by Sections
|
||||||
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
|
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
|
||||||
split-by-sections.tags=Section Split, Divide, Customize
|
split-by-sections.tags=Section Split, Divide, Customize,Customise
|
||||||
|
|
||||||
home.AddStampRequest.title=Add Stamp to PDF
|
home.AddStampRequest.title=Add Stamp to PDF
|
||||||
home.AddStampRequest.desc=Add text or add image stamps at set locations
|
home.AddStampRequest.desc=Add text or add image stamps at set locations
|
||||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDF to Book
|
home.PDFToBook.title=PDF to Book
|
||||||
@@ -512,23 +512,27 @@ home.splitPdfByChapters.title=Split PDF by Chapters
|
|||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Advanced Colour options
|
replace-color.title=Advanced Colour options
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=Replace-Invert Colour PDF
|
||||||
home.replaceColorPdf.title=Advanced Colour options
|
home.replaceColorPdf.title=Advanced Colour options
|
||||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
home.replaceColorPdf.desc=Replace colour for text and background in PDF and invert full colour of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
replaceColorPdf.tags=Replace Colour,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Replace or Invert color Options
|
replace-color.selectText.1=Replace or Invert colour Options
|
||||||
replace-color.selectText.2=Default(Default high contrast colors)
|
replace-color.selectText.2=Default(Default high contrast colours)
|
||||||
replace-color.selectText.3=Custom(Customized colors)
|
replace-color.selectText.3=Custom(Customised colours)
|
||||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
replace-color.selectText.4=Full-Invert(Invert all colours)
|
||||||
replace-color.selectText.5=High contrast color options
|
replace-color.selectText.5=High contrast colour options
|
||||||
replace-color.selectText.6=white text on black background
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Black text on white background
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Yellow text on black background
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Green text on black background
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Choose text Color
|
replace-color.selectText.10=Choose text Colour
|
||||||
replace-color.selectText.11=Choose background Color
|
replace-color.selectText.11=Choose background Colour
|
||||||
replace-color.submit=Replace
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
@@ -651,7 +655,7 @@ AddStampRequest.position=Position
|
|||||||
AddStampRequest.overrideX=Override X Coordinate
|
AddStampRequest.overrideX=Override X Coordinate
|
||||||
AddStampRequest.overrideY=Override Y Coordinate
|
AddStampRequest.overrideY=Override Y Coordinate
|
||||||
AddStampRequest.customMargin=Custom Margin
|
AddStampRequest.customMargin=Custom Margin
|
||||||
AddStampRequest.customColor=Custom Text Color
|
AddStampRequest.customColor=Custom Text Colour
|
||||||
AddStampRequest.submit=Submit
|
AddStampRequest.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
@@ -783,8 +787,8 @@ removeAnnotations.submit=Remove
|
|||||||
#compare
|
#compare
|
||||||
compare.title=Compare
|
compare.title=Compare
|
||||||
compare.header=Compare PDFs
|
compare.header=Compare PDFs
|
||||||
compare.highlightColor.1=Highlight Color 1:
|
compare.highlightColor.1=Highlight Colour 1:
|
||||||
compare.highlightColor.2=Highlight Color 2:
|
compare.highlightColor.2=Highlight Colour 2:
|
||||||
compare.document.1=Document 1
|
compare.document.1=Document 1
|
||||||
compare.document.2=Document 2
|
compare.document.2=Document 2
|
||||||
compare.submit=Compare
|
compare.submit=Compare
|
||||||
@@ -842,7 +846,7 @@ flatten.submit=Flatten
|
|||||||
ScannerImageSplit.selectText.1=Angle Threshold:
|
ScannerImageSplit.selectText.1=Angle Threshold:
|
||||||
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).
|
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).
|
||||||
ScannerImageSplit.selectText.3=Tolerance:
|
ScannerImageSplit.selectText.3=Tolerance:
|
||||||
ScannerImageSplit.selectText.4=Determines the range of color variation around the estimated background color (default: 30).
|
ScannerImageSplit.selectText.4=Determines the range of colour variation around the estimated background colour (default: 30).
|
||||||
ScannerImageSplit.selectText.5=Minimum Area:
|
ScannerImageSplit.selectText.5=Minimum Area:
|
||||||
ScannerImageSplit.selectText.6=Sets the minimum area threshold for a photo (default: 10000).
|
ScannerImageSplit.selectText.6=Sets the minimum area threshold for a photo (default: 10000).
|
||||||
ScannerImageSplit.selectText.7=Minimum Contour Area:
|
ScannerImageSplit.selectText.7=Minimum Contour Area:
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR Mode
|
|||||||
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
|
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
|
||||||
ocr.selectText.12=Render Type (Advanced)
|
ocr.selectText.12=Render Type (Advanced)
|
||||||
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
||||||
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
ocr.credit=This service uses qpdf and Tesseract for OCR.
|
||||||
ocr.submit=Process PDF with OCR
|
ocr.submit=Process PDF with OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,9 +896,9 @@ fileToPDF.submit=Convert to PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Compress
|
compress.title=Compress
|
||||||
compress.header=Compress PDF
|
compress.header=Compress PDF
|
||||||
compress.credit=This service uses Ghostscript for PDF Compress/Optimisation.
|
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
||||||
compress.selectText.1=Manual Mode - From 1 to 4
|
compress.selectText.1=Manual Mode - From 1 to 4
|
||||||
compress.selectText.2=Optimization level:
|
compress.selectText.2=Optimisation level:
|
||||||
compress.selectText.3=4 (Terrible for text images)
|
compress.selectText.3=4 (Terrible for text images)
|
||||||
compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size
|
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)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,12 +1066,13 @@ addPassword.submit=Encrypt
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Add Watermark
|
watermark.title=Add Watermark
|
||||||
watermark.header=Add Watermark
|
watermark.header=Add Watermark
|
||||||
|
watermark.customColor=Custom Text Colour
|
||||||
watermark.selectText.1=Select PDF to add watermark to:
|
watermark.selectText.1=Select PDF to add watermark to:
|
||||||
watermark.selectText.2=Watermark Text:
|
watermark.selectText.2=Watermark Text:
|
||||||
watermark.selectText.3=Font Size:
|
watermark.selectText.3=Font Size:
|
||||||
watermark.selectText.4=Rotation (0-360):
|
watermark.selectText.4=Rotation (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
watermark.selectText.5=Width Spacer (Space between each watermark horizontally):
|
||||||
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
watermark.selectText.6=Height Spacer (Space between each watermark vertically):
|
||||||
watermark.selectText.7=Opacity (0% - 100%):
|
watermark.selectText.7=Opacity (0% - 100%):
|
||||||
watermark.selectText.8=Watermark Type:
|
watermark.selectText.8=Watermark Type:
|
||||||
watermark.selectText.9=Watermark Image:
|
watermark.selectText.9=Watermark Image:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Change
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF To PDF/A
|
pdfToPDFA.title=PDF To PDF/A
|
||||||
pdfToPDFA.header=PDF To PDF/A
|
pdfToPDFA.header=PDF To PDF/A
|
||||||
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
pdfToPDFA.credit=This service uses qpdf for PDF/A conversion
|
||||||
pdfToPDFA.submit=Convert
|
pdfToPDFA.submit=Convert
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Split PDF by Chapters
|
|||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR Mode
|
|||||||
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
|
ocr.selectText.11=Remove images after OCR (Removes ALL images, only useful if part of conversion step)
|
||||||
ocr.selectText.12=Render Type (Advanced)
|
ocr.selectText.12=Render Type (Advanced)
|
||||||
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
ocr.help=Please read this documentation on how to use this for other languages and/or use not in docker
|
||||||
ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
ocr.credit=This service uses qpdf and Tesseract for OCR.
|
||||||
ocr.submit=Process PDF with OCR
|
ocr.submit=Process PDF with OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Convert to PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Compress
|
compress.title=Compress
|
||||||
compress.header=Compress PDF
|
compress.header=Compress PDF
|
||||||
compress.credit=This service uses Ghostscript for PDF Compress/Optimisation.
|
compress.credit=This service uses qpdf for PDF Compress/Optimisation.
|
||||||
compress.selectText.1=Manual Mode - From 1 to 4
|
compress.selectText.1=Manual Mode - From 1 to 4
|
||||||
compress.selectText.2=Optimization level:
|
compress.selectText.2=Optimization level:
|
||||||
compress.selectText.3=4 (Terrible for text images)
|
compress.selectText.3=4 (Terrible for text images)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,12 +1066,13 @@ addPassword.submit=Encrypt
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Add Watermark
|
watermark.title=Add Watermark
|
||||||
watermark.header=Add Watermark
|
watermark.header=Add Watermark
|
||||||
|
watermark.customColor=Custom Text Color
|
||||||
watermark.selectText.1=Select PDF to add watermark to:
|
watermark.selectText.1=Select PDF to add watermark to:
|
||||||
watermark.selectText.2=Watermark Text:
|
watermark.selectText.2=Watermark Text:
|
||||||
watermark.selectText.3=Font Size:
|
watermark.selectText.3=Font Size:
|
||||||
watermark.selectText.4=Rotation (0-360):
|
watermark.selectText.4=Rotation (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Space between each watermark horizontally):
|
watermark.selectText.5=Width Spacer (Space between each watermark horizontally):
|
||||||
watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
watermark.selectText.6=Height Spacer (Space between each watermark vertically):
|
||||||
watermark.selectText.7=Opacity (0% - 100%):
|
watermark.selectText.7=Opacity (0% - 100%):
|
||||||
watermark.selectText.8=Watermark Type:
|
watermark.selectText.8=Watermark Type:
|
||||||
watermark.selectText.9=Watermark Image:
|
watermark.selectText.9=Watermark Image:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Change
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF To PDF/A
|
pdfToPDFA.title=PDF To PDF/A
|
||||||
pdfToPDFA.header=PDF To PDF/A
|
pdfToPDFA.header=PDF To PDF/A
|
||||||
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
pdfToPDFA.credit=This service uses qpdf for PDF/A conversion
|
||||||
pdfToPDFA.submit=Convert
|
pdfToPDFA.submit=Convert
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Dividir PDF por capítulos
|
|||||||
home.splitPdfByChapters.desc=Divida un PDF en varios archivos según su estructura de capítulos.
|
home.splitPdfByChapters.desc=Divida un PDF en varios archivos según su estructura de capítulos.
|
||||||
splitPdfByChapters.tags=dividir,capítulos,marcadores,organizar
|
splitPdfByChapters.tags=dividir,capítulos,marcadores,organizar
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Reemplazar-Invertir-Color
|
replace-color.title=Reemplazar-Invertir-Color
|
||||||
replace-color.header=Reemplazar-Invertir Color en PDF
|
replace-color.header=Reemplazar-Invertir Color en PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Modo OCR
|
|||||||
ocr.selectText.11=Eliminar imágenes después de OCR (Elimina TODAS las imágenes, solo es útil si es parte del paso de conversión)
|
ocr.selectText.11=Eliminar imágenes después de OCR (Elimina TODAS las imágenes, solo es útil si es parte del paso de conversión)
|
||||||
ocr.selectText.12=Tipo de procesamiento (avanzado)
|
ocr.selectText.12=Tipo de procesamiento (avanzado)
|
||||||
ocr.help=Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en Docker
|
ocr.help=Lea esta documentación sobre cómo usar esto para otros idiomas y/o no usarlo en Docker
|
||||||
ocr.credit=Este servicio utiliza OCRmyPDF y Tesseract para OCR
|
ocr.credit=Este servicio utiliza qpdf y Tesseract para OCR
|
||||||
ocr.submit=Procesar PDF con OCR
|
ocr.submit=Procesar PDF con OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Convertir a PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Comprimir
|
compress.title=Comprimir
|
||||||
compress.header=Comprimir PDF
|
compress.header=Comprimir PDF
|
||||||
compress.credit=Este servicio utiliza Ghostscript para compresión/optimización de PDF
|
compress.credit=Este servicio utiliza qpdf para compresión/optimización de PDF
|
||||||
compress.selectText.1=Modo manual - De 1 a 4
|
compress.selectText.1=Modo manual - De 1 a 4
|
||||||
compress.selectText.2=Nivel de optimización:
|
compress.selectText.2=Nivel de optimización:
|
||||||
compress.selectText.3=4 (Terrible para imágenes de texto)
|
compress.selectText.3=4 (Terrible para imágenes de texto)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Encriptar
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Añadir marca de agua
|
watermark.title=Añadir marca de agua
|
||||||
watermark.header=Añadir marca de agua
|
watermark.header=Añadir marca de agua
|
||||||
|
watermark.customColor=Personalizar color de texto
|
||||||
watermark.selectText.1=Seleccionar PDF para añadir marca de agua:
|
watermark.selectText.1=Seleccionar PDF para añadir marca de agua:
|
||||||
watermark.selectText.2=Texto de la marca de agua:
|
watermark.selectText.2=Texto de la marca de agua:
|
||||||
watermark.selectText.3=Tamaño de la Fuente:
|
watermark.selectText.3=Tamaño de la Fuente:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Cambiar
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF a PDF/A
|
pdfToPDFA.title=PDF a PDF/A
|
||||||
pdfToPDFA.header=PDF a PDF/A
|
pdfToPDFA.header=PDF a PDF/A
|
||||||
pdfToPDFA.credit=Este servicio usa ghostscript para la conversión a PDF/A
|
pdfToPDFA.credit=Este servicio usa qpdf para la conversión a PDF/A
|
||||||
pdfToPDFA.submit=Convertir
|
pdfToPDFA.submit=Convertir
|
||||||
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
||||||
pdfToPDFA.outputFormat=Formato de salida
|
pdfToPDFA.outputFormat=Formato de salida
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Nivel de Marcador: Elige el nivel de marcadores para divi
|
|||||||
splitByChapters.desc.3=Incluir Metadatos: Si está seleccionado, los metadatos del PDF original se incluirán en cada PDF dividido.
|
splitByChapters.desc.3=Incluir Metadatos: Si está seleccionado, los metadatos del PDF original se incluirán en cada PDF dividido.
|
||||||
splitByChapters.desc.4=Permitir Duplicados: Si está seleccionado, permite que múltiples marcadores en la misma página creen archivos PDF separados.
|
splitByChapters.desc.4=Permitir Duplicados: Si está seleccionado, permite que múltiples marcadores en la misma página creen archivos PDF separados.
|
||||||
splitByChapters.submit=Dividir PDF
|
splitByChapters.submit=Dividir PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Split PDF by Chapters
|
|||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR modua
|
|||||||
ocr.selectText.11=Irudiak ezabatu OCR-ren ondoren (Irudi GUZTIAK ezabatzen ditu, bakarrik da erabilgarri bihurketa urratsaren parte baldin bada)
|
ocr.selectText.11=Irudiak ezabatu OCR-ren ondoren (Irudi GUZTIAK ezabatzen ditu, bakarrik da erabilgarri bihurketa urratsaren parte baldin bada)
|
||||||
ocr.selectText.12=Prozesaketa-mota (aurreratua)
|
ocr.selectText.12=Prozesaketa-mota (aurreratua)
|
||||||
ocr.help=Irakurri honen erabilerari buruzko dokumentazioa beste hizkuntza batzuetarako eta/edo ez erabili Docker-en
|
ocr.help=Irakurri honen erabilerari buruzko dokumentazioa beste hizkuntza batzuetarako eta/edo ez erabili Docker-en
|
||||||
ocr.credit=Zerbitzu honek OCRmyPDF eta OCR-rako Tesseract erabiltzen ditu
|
ocr.credit=Zerbitzu honek qpdf eta OCR-rako Tesseract erabiltzen ditu
|
||||||
ocr.submit=PDF prozesatu OCR-rekin
|
ocr.submit=PDF prozesatu OCR-rekin
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=PDF bihurtu
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Konprimatu
|
compress.title=Konprimatu
|
||||||
compress.header=PDFa konprimatu
|
compress.header=PDFa konprimatu
|
||||||
compress.credit=Zerbitzu honek Ghostscript erabiltzen du PDFak komprimatzeko/optimizatzeko
|
compress.credit=Zerbitzu honek qpdf erabiltzen du PDFak komprimatzeko/optimizatzeko
|
||||||
compress.selectText.1=Eskuz 1etik 4ra
|
compress.selectText.1=Eskuz 1etik 4ra
|
||||||
compress.selectText.2=Optimizazio maila:
|
compress.selectText.2=Optimizazio maila:
|
||||||
compress.selectText.3=4 (Izugarria testu-irudietarako)
|
compress.selectText.3=4 (Izugarria testu-irudietarako)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Enkriptatu
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Gehitu ur-marka
|
watermark.title=Gehitu ur-marka
|
||||||
watermark.header=Gehitu ur-marka
|
watermark.header=Gehitu ur-marka
|
||||||
|
watermark.customColor=Custom Text Color
|
||||||
watermark.selectText.1=Hautatu PDFa ur-marka gehitzeko:
|
watermark.selectText.1=Hautatu PDFa ur-marka gehitzeko:
|
||||||
watermark.selectText.2=Ur-markaren testua:
|
watermark.selectText.2=Ur-markaren testua:
|
||||||
watermark.selectText.3=Letra-tipoaren tamaina:
|
watermark.selectText.3=Letra-tipoaren tamaina:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Aldatu
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDFa PDF/A bihurtu
|
pdfToPDFA.title=PDFa PDF/A bihurtu
|
||||||
pdfToPDFA.header=PDFa PDF/A bihurtu
|
pdfToPDFA.header=PDFa PDF/A bihurtu
|
||||||
pdfToPDFA.credit=Zerbitzu honek ghostscript erabiltzen du PDFak PDF/A bihurtzeko
|
pdfToPDFA.credit=Zerbitzu honek qpdf erabiltzen du PDFak PDF/A bihurtzeko
|
||||||
pdfToPDFA.submit=Bihurtu
|
pdfToPDFA.submit=Bihurtu
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
1327
src/main/resources/messages_fa_IR.properties
Normal file
1327
src/main/resources/messages_fa_IR.properties
Normal file
File diff suppressed because it is too large
Load Diff
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Séparer un PDF par chapitres
|
|||||||
home.splitPdfByChapters.desc=Séparez un PDF en fichiers multiples en fonction de sa structure par chapitres.
|
home.splitPdfByChapters.desc=Séparez un PDF en fichiers multiples en fonction de sa structure par chapitres.
|
||||||
splitPdfByChapters.tags=séparer,chapitres,split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=séparer,chapitres,split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Remplacer-Inverser-Couleur
|
replace-color.title=Remplacer-Inverser-Couleur
|
||||||
replace-color.header=Remplacer-Inverser Couleur PDF
|
replace-color.header=Remplacer-Inverser Couleur PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Mode OCR
|
|||||||
ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion)
|
ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion)
|
||||||
ocr.selectText.12=Type de rendu (avancé)
|
ocr.selectText.12=Type de rendu (avancé)
|
||||||
ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l'OCR pour d'autres langues ou une utilisation hors Docker :
|
ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l'OCR pour d'autres langues ou une utilisation hors Docker :
|
||||||
ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR.
|
ocr.credit=Ce service utilise qpdf et Tesseract pour l'OCR.
|
||||||
ocr.submit=Traiter
|
ocr.submit=Traiter
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Convertir
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Compresser un PDF
|
compress.title=Compresser un PDF
|
||||||
compress.header=Compresser un PDF (lorsque c'est possible!)
|
compress.header=Compresser un PDF (lorsque c'est possible!)
|
||||||
compress.credit=Ce service utilise Ghostscript pour la compression et l'optimisation des PDF.
|
compress.credit=Ce service utilise qpdf pour la compression et l'optimisation des PDF.
|
||||||
compress.selectText.1=Mode manuel – de 1 à 4
|
compress.selectText.1=Mode manuel – de 1 à 4
|
||||||
compress.selectText.2=Niveau d'optimisation
|
compress.selectText.2=Niveau d'optimisation
|
||||||
compress.selectText.3=4 (terrible pour les images textuelles)
|
compress.selectText.3=4 (terrible pour les images textuelles)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Déplacer vers la gauche
|
|||||||
multiTool.moveRight=Déplacer vers la droite
|
multiTool.moveRight=Déplacer vers la droite
|
||||||
multiTool.delete=Supprimer
|
multiTool.delete=Supprimer
|
||||||
multiTool.dragDropMessage=Page(s) sélectionnées
|
multiTool.dragDropMessage=Page(s) sélectionnées
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
|
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
|
||||||
@@ -1050,12 +1066,13 @@ addPassword.submit=Chiffrer
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Ajouter un filigrane
|
watermark.title=Ajouter un filigrane
|
||||||
watermark.header=Ajouter un filigrane
|
watermark.header=Ajouter un filigrane
|
||||||
|
watermark.customColor=Couleur de texte personnalisée
|
||||||
watermark.selectText.1=PDF auquel ajouter un filigrane
|
watermark.selectText.1=PDF auquel ajouter un filigrane
|
||||||
watermark.selectText.2=Texte du filigrane
|
watermark.selectText.2=Texte du filigrane
|
||||||
watermark.selectText.3=Taille de police
|
watermark.selectText.3=Taille de police
|
||||||
watermark.selectText.4=Rotation (de 0 à 360 degrés)
|
watermark.selectText.4=Rotation (de 0 à 360 degrés)
|
||||||
watermark.selectText.5=widthSpacer (espace entre chaque filigrane horizontalement)
|
watermark.selectText.5=Width Spacer (espace entre chaque filigrane horizontalement)
|
||||||
watermark.selectText.6=heightSpacer (espace entre chaque filigrane verticalement)
|
watermark.selectText.6=Height Spacer (espace entre chaque filigrane verticalement)
|
||||||
watermark.selectText.7=Opacité (de 0% à 100%)
|
watermark.selectText.7=Opacité (de 0% à 100%)
|
||||||
watermark.selectText.8=Type de filigrane
|
watermark.selectText.8=Type de filigrane
|
||||||
watermark.selectText.9=Image du filigrane
|
watermark.selectText.9=Image du filigrane
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Modifier
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF en PDF/A
|
pdfToPDFA.title=PDF en PDF/A
|
||||||
pdfToPDFA.header=PDF en PDF/A
|
pdfToPDFA.header=PDF en PDF/A
|
||||||
pdfToPDFA.credit=Ce service utilise ghostscript pour la conversion en PDF/A.
|
pdfToPDFA.credit=Ce service utilise qpdf pour la conversion en PDF/A.
|
||||||
pdfToPDFA.submit=Convertir
|
pdfToPDFA.submit=Convertir
|
||||||
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
||||||
pdfToPDFA.outputFormat=Format de sortie
|
pdfToPDFA.outputFormat=Format de sortie
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Niveau de Signet : Choisissez le niveau de signets à uti
|
|||||||
splitByChapters.desc.3=Inclure les Métadonnées : Si coché, les métadonnées du PDF original seront incluses dans chaque PDF divisé.
|
splitByChapters.desc.3=Inclure les Métadonnées : Si coché, les métadonnées du PDF original seront incluses dans chaque PDF divisé.
|
||||||
splitByChapters.desc.4=Autoriser les Doublons : Si coché, permet à plusieurs signets sur la même page de créer des PDF séparés.
|
splitByChapters.desc.4=Autoriser les Doublons : Si coché, permet à plusieurs signets sur la même page de créer des PDF séparés.
|
||||||
splitByChapters.submit=Diviser le PDF
|
splitByChapters.submit=Diviser le PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Split PDF by Chapters
|
|||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Mód OCR
|
|||||||
ocr.selectText.11=Bain íomhánna tar éis OCR (Bain GACH íomhá, ní úsáideach ach amháin má tá siad mar chuid den chéim tiontaithe)
|
ocr.selectText.11=Bain íomhánna tar éis OCR (Bain GACH íomhá, ní úsáideach ach amháin má tá siad mar chuid den chéim tiontaithe)
|
||||||
ocr.selectText.12=Cineál Rindreála (Ardleibhéal)
|
ocr.selectText.12=Cineál Rindreála (Ardleibhéal)
|
||||||
ocr.help=Léigh le do thoil an doiciméadú seo ar conas é seo a úsáid do theangacha eile agus/nó úsáid nach bhfuil i ndugairí
|
ocr.help=Léigh le do thoil an doiciméadú seo ar conas é seo a úsáid do theangacha eile agus/nó úsáid nach bhfuil i ndugairí
|
||||||
ocr.credit=Úsáideann an tseirbhís seo OCRmyPDF agus Tesseract le haghaidh OCR.
|
ocr.credit=Úsáideann an tseirbhís seo qpdf agus Tesseract le haghaidh OCR.
|
||||||
ocr.submit=Próiseáil PDF le OCR
|
ocr.submit=Próiseáil PDF le OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Tiontaigh go PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Comhbhrúigh
|
compress.title=Comhbhrúigh
|
||||||
compress.header=Comhbhrúigh PDF
|
compress.header=Comhbhrúigh PDF
|
||||||
compress.credit=Úsáideann an tseirbhís seo Ghostscript le haghaidh Comhbhrú/Optimization PDF.
|
compress.credit=Úsáideann an tseirbhís seo qpdf le haghaidh Comhbhrú/Optimization PDF.
|
||||||
compress.selectText.1=Mód Láimhe - Ó 1 go 4
|
compress.selectText.1=Mód Láimhe - Ó 1 go 4
|
||||||
compress.selectText.2=Leibhéal optamaithe:
|
compress.selectText.2=Leibhéal optamaithe:
|
||||||
compress.selectText.3=4 (Uafásach le haghaidh íomhánna téacs)
|
compress.selectText.3=4 (Uafásach le haghaidh íomhánna téacs)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,11 +1066,12 @@ addPassword.submit=Criptigh
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Cuir Uisce leis
|
watermark.title=Cuir Uisce leis
|
||||||
watermark.header=Cuir Uisce leis
|
watermark.header=Cuir Uisce leis
|
||||||
|
watermark.customColor=Dath Téacs Saincheaptha
|
||||||
watermark.selectText.1=Roghnaigh PDF chun comhartha uisce a chur leis:
|
watermark.selectText.1=Roghnaigh PDF chun comhartha uisce a chur leis:
|
||||||
watermark.selectText.2=Téacs Comhartha Uisce:
|
watermark.selectText.2=Téacs Comhartha Uisce:
|
||||||
watermark.selectText.3=Méid cló:
|
watermark.selectText.3=Méid cló:
|
||||||
watermark.selectText.4=Rothlú (0-360):
|
watermark.selectText.4=Rothlú (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Spás idir gach comhartha uisce go cothrománach):
|
watermark.selectText.5=Width Spacer (Spás idir gach comhartha uisce go cothrománach):
|
||||||
watermark.selectText.6=spásaire airde (Spás idir gach comhartha uisce go hingearach):
|
watermark.selectText.6=spásaire airde (Spás idir gach comhartha uisce go hingearach):
|
||||||
watermark.selectText.7=Teimhneacht (0% - 100%):
|
watermark.selectText.7=Teimhneacht (0% - 100%):
|
||||||
watermark.selectText.8=Cineál Comhartha Uisce:
|
watermark.selectText.8=Cineál Comhartha Uisce:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Athrú
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF Go PDF/A
|
pdfToPDFA.title=PDF Go PDF/A
|
||||||
pdfToPDFA.header=PDF Go PDF/A
|
pdfToPDFA.header=PDF Go PDF/A
|
||||||
pdfToPDFA.credit=Úsáideann an tseirbhís seo ghostscript chun PDF/A a thiontú
|
pdfToPDFA.credit=Úsáideann an tseirbhís seo qpdf chun PDF/A a thiontú
|
||||||
pdfToPDFA.submit=Tiontaigh
|
pdfToPDFA.submit=Tiontaigh
|
||||||
pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna
|
pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna
|
||||||
pdfToPDFA.outputFormat=Formáid aschuir
|
pdfToPDFA.outputFormat=Formáid aschuir
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=अध्यायों पर अलग-कर
|
|||||||
home.splitPdfByChapters.desc=पुस्तक के अध्याय की संरचना पर आधारित एक PDF को बहिन-भागों में विभाजित करें
|
home.splitPdfByChapters.desc=पुस्तक के अध्याय की संरचना पर आधारित एक PDF को बहिन-भागों में विभाजित करें
|
||||||
splitPdfByChapters.tags=विभाजन,अध्याय,पसंदीदा,रजैत
|
splitPdfByChapters.tags=विभाजन,अध्याय,पसंदीदा,रजैत
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=चित्र रंग परिवर्तन/उलटकर परिवर्तन PDF
|
replace-color.header=चित्र रंग परिवर्तन/उलटकर परिवर्तन PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR मोड
|
|||||||
ocr.selectText.11=OCR के बाद छवियां हटाएँ (सभी छवियां हटाएँ, केवल परिवर्तन चरण का हिस्सा होता है)
|
ocr.selectText.11=OCR के बाद छवियां हटाएँ (सभी छवियां हटाएँ, केवल परिवर्तन चरण का हिस्सा होता है)
|
||||||
ocr.selectText.12=रेंडर टाइप (उन्नत)
|
ocr.selectText.12=रेंडर टाइप (उन्नत)
|
||||||
ocr.help=कृपया इस डॉक्यूमेंटेशन को पढ़ें कि इसे अन्य भाषाओं के लिए कैसे उपयोग किया जाता है और/या डॉकर में नहीं हैं
|
ocr.help=कृपया इस डॉक्यूमेंटेशन को पढ़ें कि इसे अन्य भाषाओं के लिए कैसे उपयोग किया जाता है और/या डॉकर में नहीं हैं
|
||||||
ocr.credit=इस सेवा में OCRmyPDF और टेसरेक्ट का उपयोग होता है।
|
ocr.credit=इस सेवा में qpdf और टेसरेक्ट का उपयोग होता है।
|
||||||
ocr.submit=OCR के साथ PDF प्रोसेस करें
|
ocr.submit=OCR के साथ PDF प्रोसेस करें
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=पीडीएफ़ में बदलें
|
|||||||
#compress
|
#compress
|
||||||
compress.title=संकुचित करें
|
compress.title=संकुचित करें
|
||||||
compress.header=PDF को संकुचित करें
|
compress.header=PDF को संकुचित करें
|
||||||
compress.credit=यह सेवा PDF संकुचन/अनुकूलन के लिए Ghostscript का उपयोग करती है।
|
compress.credit=यह सेवा PDF संकुचन/अनुकूलन के लिए qpdf का उपयोग करती है।
|
||||||
compress.selectText.1=मैनुअल मोड - 1 से 4 तक
|
compress.selectText.1=मैनुअल मोड - 1 से 4 तक
|
||||||
compress.selectText.2=अनुकूलन स्तर:
|
compress.selectText.2=अनुकूलन स्तर:
|
||||||
compress.selectText.3=4 (पाठ छवियों के लिए अत्यधिक)
|
compress.selectText.3=4 (पाठ छवियों के लिए अत्यधिक)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=एन्क्रिप्ट करें
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=वॉटरमार्क जोड़ें
|
watermark.title=वॉटरमार्क जोड़ें
|
||||||
watermark.header=वॉटरमार्क जोड़ें
|
watermark.header=वॉटरमार्क जोड़ें
|
||||||
|
watermark.customColor=संवैधित टेक्स्ट रंग
|
||||||
watermark.selectText.1=वॉटरमार्क जोड़ने के लिए पीडीएफ चुनें:
|
watermark.selectText.1=वॉटरमार्क जोड़ने के लिए पीडीएफ चुनें:
|
||||||
watermark.selectText.2=वॉटरमार्क टेक्स्ट:
|
watermark.selectText.2=वॉटरमार्क टेक्स्ट:
|
||||||
watermark.selectText.3=फ़ॉन्ट साइज़:
|
watermark.selectText.3=फ़ॉन्ट साइज़:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=बदलें
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF से PDF/A में
|
pdfToPDFA.title=PDF से PDF/A में
|
||||||
pdfToPDFA.header=PDF से PDF/A में
|
pdfToPDFA.header=PDF से PDF/A में
|
||||||
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए ghostscript का उपयोग किया जाता है।
|
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए qpdf का उपयोग किया जाता है।
|
||||||
pdfToPDFA.submit=परिवर्तित करें
|
pdfToPDFA.submit=परिवर्तित करें
|
||||||
pdfToPDFA.tip=यह सैकड़ों प्रविष्टियाँ एक ही समय में काम करते हैं
|
pdfToPDFA.tip=यह सैकड़ों प्रविष्टियाँ एक ही समय में काम करते हैं
|
||||||
pdfToPDFA.outputFormat=आउटपुट फॉर्मेट
|
pdfToPDFA.outputFormat=आउटपुट फॉर्मेट
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=लेमैक्स स्तर: विभाजन
|
|||||||
splitByChapters.desc.3=मॉडेटरेट का शामिल करें: यदि सत्यापित किया जाता है, प्रारंभिक PDF की मॉडेटरेट को प्रत्येक विभाग PDF में शामिल किया जाएगा।
|
splitByChapters.desc.3=मॉडेटरेट का शामिल करें: यदि सत्यापित किया जाता है, प्रारंभिक PDF की मॉडेटरेट को प्रत्येक विभाग PDF में शामिल किया जाएगा।
|
||||||
splitByChapters.desc.4=यादृच्छिक पुनरावृत्ति अनुमोदित: यदि सत्यापित किया जाता है, एक ही पेज पर दोहरे मूल्यांकन पब्लिक पीड़एफ बनाने की संभावना देता है।
|
splitByChapters.desc.4=यादृच्छिक पुनरावृत्ति अनुमोदित: यदि सत्यापित किया जाता है, एक ही पेज पर दोहरे मूल्यांकन पब्लिक पीड़एफ बनाने की संभावना देता है।
|
||||||
splitByChapters.submit=PDF विभाजित
|
splitByChapters.submit=PDF विभाजित
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Podijeli PDF prema glavama
|
|||||||
home.splitPdfByChapters.desc=Podijeli PDF na više datoteka prema njegovom strukturnom obliku glava.
|
home.splitPdfByChapters.desc=Podijeli PDF na više datoteka prema njegovom strukturnom obliku glava.
|
||||||
splitPdfByChapters.tags=podjela, glave, markere, organizacija
|
splitPdfByChapters.tags=podjela, glave, markere, organizacija
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Zameni-inverziranje boja u PDF-u
|
replace-color.header=Zameni-inverziranje boja u PDF-u
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR način
|
|||||||
ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je dio koraka konverzije)
|
ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je dio koraka konverzije)
|
||||||
ocr.selectText.12=Vrsta iscrtavanja (napredno)
|
ocr.selectText.12=Vrsta iscrtavanja (napredno)
|
||||||
ocr.help=Pročitajte ovu dokumentaciju o tome kako ovo koristiti za druge jezike i/ili koristiti ne u dockeru
|
ocr.help=Pročitajte ovu dokumentaciju o tome kako ovo koristiti za druge jezike i/ili koristiti ne u dockeru
|
||||||
ocr.credit=Ova usluga koristi OCRmyPDF i Tesseract za OCR.
|
ocr.credit=Ova usluga koristi qpdf i Tesseract za OCR.
|
||||||
ocr.submit=Obradi PDF sa OCR-om
|
ocr.submit=Obradi PDF sa OCR-om
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Pretvori u PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Komprimirajte
|
compress.title=Komprimirajte
|
||||||
compress.header=Komprimirajte PDF
|
compress.header=Komprimirajte PDF
|
||||||
compress.credit=Ova usluga koristi Ghostscript za komprimiranje / optimizaciju PDF-a.
|
compress.credit=Ova usluga koristi qpdf za komprimiranje / optimizaciju PDF-a.
|
||||||
compress.selectText.1=Ručni režim - Od 1 do 4
|
compress.selectText.1=Ručni režim - Od 1 do 4
|
||||||
compress.selectText.2=Nivo optimizacije:
|
compress.selectText.2=Nivo optimizacije:
|
||||||
compress.selectText.3=4 (Užasno za tekstualne slike)
|
compress.selectText.3=4 (Užasno za tekstualne slike)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Šifriraj
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Dodaj vodeni žig
|
watermark.title=Dodaj vodeni žig
|
||||||
watermark.header=Dodaj vodeni žig
|
watermark.header=Dodaj vodeni žig
|
||||||
|
watermark.customColor=Prilagođena boja teksta
|
||||||
watermark.selectText.1=Izaberite PDF za dodavanje vodenog žiga:
|
watermark.selectText.1=Izaberite PDF za dodavanje vodenog žiga:
|
||||||
watermark.selectText.2=Tekst vodenog žiga:
|
watermark.selectText.2=Tekst vodenog žiga:
|
||||||
watermark.selectText.3=Veličina fonta:
|
watermark.selectText.3=Veličina fonta:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Promijeniti
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF u PDF/A
|
pdfToPDFA.title=PDF u PDF/A
|
||||||
pdfToPDFA.header=PDF u PDF/A
|
pdfToPDFA.header=PDF u PDF/A
|
||||||
pdfToPDFA.credit=Ova usluga koristi ghostscript za PDF/A pretvorbu
|
pdfToPDFA.credit=Ova usluga koristi qpdf za PDF/A pretvorbu
|
||||||
pdfToPDFA.submit=Pretvoriti
|
pdfToPDFA.submit=Pretvoriti
|
||||||
pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom
|
pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom
|
||||||
pdfToPDFA.outputFormat=Izlazni format
|
pdfToPDFA.outputFormat=Izlazni format
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Nivo oznaka: Odaberite nivo oznaka koji će se koristiti
|
|||||||
splitByChapters.desc.3=Uključi metapodatke: Ako je pokušano, metapodaci iz originalne PDF datoteke će biti uključeni u svaku podijeljenu PDF datoteku.
|
splitByChapters.desc.3=Uključi metapodatke: Ako je pokušano, metapodaci iz originalne PDF datoteke će biti uključeni u svaku podijeljenu PDF datoteku.
|
||||||
splitByChapters.desc.4=Dopuštaj duplikate: Ako je ova opcija zaštićena, dozvoljava se da se na istoj strani mogu stvoriti posebne PDF datoteke s više oznaka.
|
splitByChapters.desc.4=Dopuštaj duplikate: Ako je ova opcija zaštićena, dozvoljava se da se na istoj strani mogu stvoriti posebne PDF datoteke s više oznaka.
|
||||||
splitByChapters.submit=Podijeli PDF
|
splitByChapters.submit=Podijeli PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=PDF felosztása fejezetek szerint
|
|||||||
home.splitPdfByChapters.desc=Fejezetei alapján egy PDF fájl több dokumentumba osztás.
|
home.splitPdfByChapters.desc=Fejezetei alapján egy PDF fájl több dokumentumba osztás.
|
||||||
splitPdfByChapters.tags=Osztás, fejezetek, jelezes, organizálás
|
splitPdfByChapters.tags=Osztás, fejezetek, jelezes, organizálás
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Visszaalakítás-összevétel a színekkel PDF-ben
|
replace-color.header=Visszaalakítás-összevétel a színekkel PDF-ben
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR mód
|
|||||||
ocr.selectText.11=Képek eltávolítása OCR után (Az ÖSSZES kép eltávolítása, csak akkor hasznos, ha a konverzió része)
|
ocr.selectText.11=Képek eltávolítása OCR után (Az ÖSSZES kép eltávolítása, csak akkor hasznos, ha a konverzió része)
|
||||||
ocr.selectText.12=Render típusa (Speciális)
|
ocr.selectText.12=Render típusa (Speciális)
|
||||||
ocr.help=Kérjük, olvassa el ezt a dokumentációt az egyéb nyelvek használatához és/vagy a nem Docker-es használathoz.
|
ocr.help=Kérjük, olvassa el ezt a dokumentációt az egyéb nyelvek használatához és/vagy a nem Docker-es használathoz.
|
||||||
ocr.credit=Ez a szolgáltatás az OCRmyPDF és a Tesseract OCR használatával működik.
|
ocr.credit=Ez a szolgáltatás az qpdf és a Tesseract OCR használatával működik.
|
||||||
ocr.submit=PDF feldolgozása OCR-rel
|
ocr.submit=PDF feldolgozása OCR-rel
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Konvertálás PDF dokumentummá
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Tömörítés
|
compress.title=Tömörítés
|
||||||
compress.header=PDF tömörítése
|
compress.header=PDF tömörítése
|
||||||
compress.credit=Ez a szolgáltatás a Ghostscript-et használja a PDF tömörítéséhez/optimalizálásához.
|
compress.credit=Ez a szolgáltatás a qpdf-et használja a PDF tömörítéséhez/optimalizálásához.
|
||||||
compress.selectText.1=Kézi mód - 1-től 4-ig
|
compress.selectText.1=Kézi mód - 1-től 4-ig
|
||||||
compress.selectText.2=Optimalizálási szint:
|
compress.selectText.2=Optimalizálási szint:
|
||||||
compress.selectText.3=4 (nem ajánlott a szöveges képekhez)
|
compress.selectText.3=4 (nem ajánlott a szöveges képekhez)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,12 +1066,13 @@ addPassword.submit=Titkosítás
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Vízjel hozzáadása
|
watermark.title=Vízjel hozzáadása
|
||||||
watermark.header=Vízjel hozzáadása
|
watermark.header=Vízjel hozzáadása
|
||||||
|
watermark.customColor=Egyéni szövegszín
|
||||||
watermark.selectText.1=Válassza ki a PDF-t, amelyhez vízjelet kíván hozzáadni:
|
watermark.selectText.1=Válassza ki a PDF-t, amelyhez vízjelet kíván hozzáadni:
|
||||||
watermark.selectText.2=Vízjel szövege:
|
watermark.selectText.2=Vízjel szövege:
|
||||||
watermark.selectText.3=Betűméret:
|
watermark.selectText.3=Betűméret:
|
||||||
watermark.selectText.4=Forgatás (0-360):
|
watermark.selectText.4=Forgatás (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Hely a vízjelek között vízszintesen):
|
watermark.selectText.5=Width Spacer (Hely a vízjelek között vízszintesen):
|
||||||
watermark.selectText.6=heightSpacer (Hely a vízjelek között függőlegesen):
|
watermark.selectText.6=Height Spacer (Hely a vízjelek között függőlegesen):
|
||||||
watermark.selectText.7=Átlátszóság (0% - 100%):
|
watermark.selectText.7=Átlátszóság (0% - 100%):
|
||||||
watermark.selectText.8=Vízjel típusa:
|
watermark.selectText.8=Vízjel típusa:
|
||||||
watermark.selectText.9=Vízjel képe:
|
watermark.selectText.9=Vízjel képe:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Módosítás
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF >> PDF/A
|
pdfToPDFA.title=PDF >> PDF/A
|
||||||
pdfToPDFA.header=PDF >> PDF/A
|
pdfToPDFA.header=PDF >> PDF/A
|
||||||
pdfToPDFA.credit=Ez a szolgáltatás az ghostscript-t használja a PDF/A konverzióhoz
|
pdfToPDFA.credit=Ez a szolgáltatás az qpdf-t használja a PDF/A konverzióhoz
|
||||||
pdfToPDFA.submit=Konvertálás
|
pdfToPDFA.submit=Konvertálás
|
||||||
pdfToPDFA.tip=Jelenleg egyszerre több fájl nem működik ezzel a funkcióval
|
pdfToPDFA.tip=Jelenleg egyszerre több fájl nem működik ezzel a funkcióval
|
||||||
pdfToPDFA.outputFormat=Kimeneti formátum
|
pdfToPDFA.outputFormat=Kimeneti formátum
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Metaadatok belefoglalása: Ha bevanítva van, az eredeti PDF fájl metaadatai megtartódnak minden osztott fájlban.
|
splitByChapters.desc.3=Metaadatok belefoglalása: Ha bevanítva van, az eredeti PDF fájl metaadatai megtartódnak minden osztott fájlban.
|
||||||
splitByChapters.desc.4=Duplikációk engedélyezése: Ha bevanítva van, lehetővé teszi a megadott oldalon lévő több kijelzőszint alapján új PDF-ek létrehozása.
|
splitByChapters.desc.4=Duplikációk engedélyezése: Ha bevanítva van, lehetővé teszi a megadott oldalon lévő több kijelzőszint alapján új PDF-ek létrehozása.
|
||||||
splitByChapters.submit=PDF osztás
|
splitByChapters.submit=PDF osztás
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Pisahkan PDF berdasarkan Bab
|
|||||||
home.splitPdfByChapters.desc=Memisahkan PDF menjadi beberapa file berdasarkan struktur babnya.
|
home.splitPdfByChapters.desc=Memisahkan PDF menjadi beberapa file berdasarkan struktur babnya.
|
||||||
splitPdfByChapters.tags=pemisahan,bab,bookmark,atur
|
splitPdfByChapters.tags=pemisahan,bab,bookmark,atur
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Ganti-Inversi-Warna
|
replace-color.title=Ganti-Inversi-Warna
|
||||||
replace-color.header=Ganti-Inversi Warna PDF
|
replace-color.header=Ganti-Inversi Warna PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Mode OCR
|
|||||||
ocr.selectText.11=Hapus gambar setelah OCR (Menghapus Semua gambar, hanya berguna jika merupakan bagian dari langkah konversi)
|
ocr.selectText.11=Hapus gambar setelah OCR (Menghapus Semua gambar, hanya berguna jika merupakan bagian dari langkah konversi)
|
||||||
ocr.selectText.12=Jenis Render (Lanjutan)
|
ocr.selectText.12=Jenis Render (Lanjutan)
|
||||||
ocr.help=Silakan baca dokumentasi ini tentang cara menggunakan ini untuk bahasa lain dan/atau penggunaan yang tidak ada di docker
|
ocr.help=Silakan baca dokumentasi ini tentang cara menggunakan ini untuk bahasa lain dan/atau penggunaan yang tidak ada di docker
|
||||||
ocr.credit=Layanan ini menggunakan OCRmyPDF dan Tesseract untuk OCR.
|
ocr.credit=Layanan ini menggunakan qpdf dan Tesseract untuk OCR.
|
||||||
ocr.submit=Memproses PDF dengan OCR
|
ocr.submit=Memproses PDF dengan OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Konversi ke PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Kompres
|
compress.title=Kompres
|
||||||
compress.header=Kompres PDF
|
compress.header=Kompres PDF
|
||||||
compress.credit=Layanan ini menggunakan Ghostscript untuk Kompresi/Optimalisasi PDF.
|
compress.credit=Layanan ini menggunakan qpdf untuk Kompresi/Optimalisasi PDF.
|
||||||
compress.selectText.1=Mode Manual - Dari 1 hingga 4
|
compress.selectText.1=Mode Manual - Dari 1 hingga 4
|
||||||
compress.selectText.2=Tingkat Optimalisasi:
|
compress.selectText.2=Tingkat Optimalisasi:
|
||||||
compress.selectText.3=4 (Buruk untuk gambar teks)
|
compress.selectText.3=4 (Buruk untuk gambar teks)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,12 +1066,13 @@ addPassword.submit=Enkripsi
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Tambahkan Watermark
|
watermark.title=Tambahkan Watermark
|
||||||
watermark.header=Tambahkan Watermark
|
watermark.header=Tambahkan Watermark
|
||||||
|
watermark.customColor=Warna Teks Kustom
|
||||||
watermark.selectText.1=Pilih PDF untuk menambahkan watermark:
|
watermark.selectText.1=Pilih PDF untuk menambahkan watermark:
|
||||||
watermark.selectText.2=Text Watermark:
|
watermark.selectText.2=Text Watermark:
|
||||||
watermark.selectText.3=Ukuran Huruf:
|
watermark.selectText.3=Ukuran Huruf:
|
||||||
watermark.selectText.4=Rotasi (0-360):
|
watermark.selectText.4=Rotasi (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Spasi diantara setiap watermark horisontal):
|
watermark.selectText.5=Width Spacer (Spasi diantara setiap watermark horisontal):
|
||||||
watermark.selectText.6=heightSpacer (Spasi diantara setiap watermark vertikal):
|
watermark.selectText.6=Height Spacer (Spasi diantara setiap watermark vertikal):
|
||||||
watermark.selectText.7=Kejernihan (0% - 100%):
|
watermark.selectText.7=Kejernihan (0% - 100%):
|
||||||
watermark.selectText.8=Tipe Watermark:
|
watermark.selectText.8=Tipe Watermark:
|
||||||
watermark.selectText.9=Gambar Watermark:
|
watermark.selectText.9=Gambar Watermark:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Ganti
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF Ke PDF/A
|
pdfToPDFA.title=PDF Ke PDF/A
|
||||||
pdfToPDFA.header=PDF ke PDF/A
|
pdfToPDFA.header=PDF ke PDF/A
|
||||||
pdfToPDFA.credit=Layanan ini menggunakan ghostscript untuk konversi PDF/A.
|
pdfToPDFA.credit=Layanan ini menggunakan qpdf untuk konversi PDF/A.
|
||||||
pdfToPDFA.submit=Konversi
|
pdfToPDFA.submit=Konversi
|
||||||
pdfToPDFA.tip=Saat ini tidak dapat digunakan untuk beberapa input sekaligus
|
pdfToPDFA.tip=Saat ini tidak dapat digunakan untuk beberapa input sekaligus
|
||||||
pdfToPDFA.outputFormat=Format keluaran
|
pdfToPDFA.outputFormat=Format keluaran
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Tingkatan Markah: Pilih tingkatan markah yang digunakan u
|
|||||||
splitByChapters.desc.3=Termasuk Metadata: Jika dicentang, metadata asli PDF akan disertakan dalam setiap PDF yang dibagi.
|
splitByChapters.desc.3=Termasuk Metadata: Jika dicentang, metadata asli PDF akan disertakan dalam setiap PDF yang dibagi.
|
||||||
splitByChapters.desc.4=Izinkan Duplikat: Jika dicentang, mengizinkan beberapa markah pada halaman yang sama untuk membuat PDF terpisah.
|
splitByChapters.desc.4=Izinkan Duplikat: Jika dicentang, mengizinkan beberapa markah pada halaman yang sama untuk membuat PDF terpisah.
|
||||||
splitByChapters.submit=Pecah PDF
|
splitByChapters.submit=Pecah PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Dividi PDF per capitoli
|
|||||||
home.splitPdfByChapters.desc=Dividi un PDF in più file in base alla struttura dei capitoli.
|
home.splitPdfByChapters.desc=Dividi un PDF in più file in base alla struttura dei capitoli.
|
||||||
splitPdfByChapters.tags=dividi, capitoli, segnalibri, organizza
|
splitPdfByChapters.tags=dividi, capitoli, segnalibri, organizza
|
||||||
|
|
||||||
|
home.validateSignature.title=Convalida la firma PDF
|
||||||
|
home.validateSignature.desc=Verificare le firme digitali e i certificati nei documenti PDF
|
||||||
|
validateSignature.tags=firma,verifica,convalida,pdf,certificato,firma digitale,convalida firma,convalida certificato
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Sostituisci-Inverti-Colore
|
replace-color.title=Sostituisci-Inverti-Colore
|
||||||
replace-color.header=Sostituisci-Inverti colore PDF
|
replace-color.header=Sostituisci-Inverti colore PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=Modalità OCR
|
|||||||
ocr.selectText.11=Rimuovi immagini dopo la scansione (Rimuove TUTTE le immagini, utile solo come parte del processo di conversione)
|
ocr.selectText.11=Rimuovi immagini dopo la scansione (Rimuove TUTTE le immagini, utile solo come parte del processo di conversione)
|
||||||
ocr.selectText.12=Modalità di rendering (avanzato)
|
ocr.selectText.12=Modalità di rendering (avanzato)
|
||||||
ocr.help=Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker
|
ocr.help=Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker
|
||||||
ocr.credit=Questo servizio utilizza OCRmyPDF e Tesseract per l'OCR.
|
ocr.credit=Questo servizio utilizza qpdf e Tesseract per l'OCR.
|
||||||
ocr.submit=Scansiona testo nel PDF con OCR
|
ocr.submit=Scansiona testo nel PDF con OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Converti in PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Comprimi
|
compress.title=Comprimi
|
||||||
compress.header=Comprimi PDF
|
compress.header=Comprimi PDF
|
||||||
compress.credit=Questo servizio utilizza Ghostscript per la compressione/ottimizzazione dei PDF.
|
compress.credit=Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.
|
||||||
compress.selectText.1=Modalità manuale - Da 1 a 4
|
compress.selectText.1=Modalità manuale - Da 1 a 4
|
||||||
compress.selectText.2=Livello di ottimizzazione:
|
compress.selectText.2=Livello di ottimizzazione:
|
||||||
compress.selectText.3=4 (Terribile per le immagini di testo)
|
compress.selectText.3=4 (Terribile per le immagini di testo)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Sposta a sinistra
|
|||||||
multiTool.moveRight=Sposta a destra
|
multiTool.moveRight=Sposta a destra
|
||||||
multiTool.delete=Elimina
|
multiTool.delete=Elimina
|
||||||
multiTool.dragDropMessage=Pagina(e) selezionata(e)
|
multiTool.dragDropMessage=Pagina(e) selezionata(e)
|
||||||
|
multiTool.undo=Annulla
|
||||||
|
multiTool.redo=Rifai
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=Questo file è protetto da password. Inserisci la password:
|
||||||
|
decrypt.cancelled=Operazione annullata per il PDF: {0}
|
||||||
|
decrypt.noPassword=Nessuna password fornita per il PDF crittografato: {0}
|
||||||
|
decrypt.invalidPassword=Riprova con la password corretta.
|
||||||
|
decrypt.invalidPasswordHeader=Password errata o crittografia non supportata per il PDF: {0}
|
||||||
|
decrypt.unexpectedError=Si è verificato un errore durante l'elaborazione del file. Riprova..
|
||||||
|
decrypt.serverError=Errore del server durante la decrittazione: {0}
|
||||||
|
decrypt.success=File decrittografato con successo.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
|
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Crittografa
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Aggiungi Filigrana
|
watermark.title=Aggiungi Filigrana
|
||||||
watermark.header=Aggiungi filigrana
|
watermark.header=Aggiungi filigrana
|
||||||
|
watermark.customColor=Colore testo personalizzato
|
||||||
watermark.selectText.1=Seleziona PDF a cui aggiungere la filigrana:
|
watermark.selectText.1=Seleziona PDF a cui aggiungere la filigrana:
|
||||||
watermark.selectText.2=Testo:
|
watermark.selectText.2=Testo:
|
||||||
watermark.selectText.3=Dimensione carattere:
|
watermark.selectText.3=Dimensione carattere:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Cambia proprietà
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=Da PDF a PDF/A
|
pdfToPDFA.title=Da PDF a PDF/A
|
||||||
pdfToPDFA.header=Da PDF a PDF/A
|
pdfToPDFA.header=Da PDF a PDF/A
|
||||||
pdfToPDFA.credit=Questo servizio utilizza Ghostscript per la conversione in PDF/A.
|
pdfToPDFA.credit=Questo servizio utilizza qpdf per la conversione in PDF/A.
|
||||||
pdfToPDFA.submit=Converti
|
pdfToPDFA.submit=Converti
|
||||||
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
|
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
|
||||||
pdfToPDFA.outputFormat=Formato di output
|
pdfToPDFA.outputFormat=Formato di output
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Livello segnalibro: seleziona il livello dei segnalibri d
|
|||||||
splitByChapters.desc.3=Includi metadati: se selezionato, i metadati del PDF originale verranno inclusi in ogni PDF diviso.
|
splitByChapters.desc.3=Includi metadati: se selezionato, i metadati del PDF originale verranno inclusi in ogni PDF diviso.
|
||||||
splitByChapters.desc.4=Consenti duplicati: se selezionata, consente più segnalibri sulla stessa pagina per creare PDF separati.
|
splitByChapters.desc.4=Consenti duplicati: se selezionata, consente più segnalibri sulla stessa pagina per creare PDF separati.
|
||||||
splitByChapters.submit=Dividi PDF
|
splitByChapters.submit=Dividi PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Clicca
|
||||||
|
fileChooser.or=o
|
||||||
|
fileChooser.dragAndDrop=Trascina & Rilascia
|
||||||
|
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Rilasci
|
||||||
|
releases.title=Note di rilascio
|
||||||
|
releases.header=Note di rilascio
|
||||||
|
releases.current.version=Rilascio corrente
|
||||||
|
releases.note=Le note di rilascio sono disponibili solo in inglese
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validare le firme PDF
|
||||||
|
validateSignature.header=Convalidare le firme digitali
|
||||||
|
validateSignature.selectPDF=Seleziona il file PDF firmato
|
||||||
|
validateSignature.submit=Convalida firme
|
||||||
|
validateSignature.results=Risultati di convalida
|
||||||
|
validateSignature.status=Stato
|
||||||
|
validateSignature.signer=Firmatario
|
||||||
|
validateSignature.date=Data
|
||||||
|
validateSignature.reason=Ragione
|
||||||
|
validateSignature.location=Posizione
|
||||||
|
validateSignature.noSignatures=Nessuna firma digitale trovata in questo documento
|
||||||
|
validateSignature.status.valid=Valida
|
||||||
|
validateSignature.status.invalid=Invalida
|
||||||
|
validateSignature.chain.invalid=Convalida della catena di certificati non riuscita: impossibile verificare l'identità del firmatario
|
||||||
|
validateSignature.trust.invalid=Certificato non presente nell'archivio attendibile: la fonte non può essere verificata
|
||||||
|
validateSignature.cert.expired=Il certificato è scaduto
|
||||||
|
validateSignature.cert.revoked=Il certificato è stato revocato
|
||||||
|
validateSignature.signature.info=Informazioni sulla firma
|
||||||
|
validateSignature.signature=Firma
|
||||||
|
validateSignature.signature.mathValid=La firma è matematicamente valida MA:
|
||||||
|
validateSignature.selectCustomCert=File di certificato personalizzato X.509 (opzionale)
|
||||||
|
validateSignature.cert.info=Dettagli del certificato
|
||||||
|
validateSignature.cert.issuer=Emittente
|
||||||
|
validateSignature.cert.subject=Soggetto
|
||||||
|
validateSignature.cert.serialNumber=Numero di serie
|
||||||
|
validateSignature.cert.validFrom=Valido da
|
||||||
|
validateSignature.cert.validUntil=Valido fino a
|
||||||
|
validateSignature.cert.algorithm=Algoritmo
|
||||||
|
validateSignature.cert.keySize=Dimensione chiave
|
||||||
|
validateSignature.cert.version=Versione
|
||||||
|
validateSignature.cert.keyUsage=Utilizzo della chiave
|
||||||
|
validateSignature.cert.selfSigned=Autofirmato
|
||||||
|
validateSignature.cert.bits=bit
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ userNotFoundMessage=ユーザーが見つかりません。
|
|||||||
incorrectPasswordMessage=現在のパスワードが正しくありません。
|
incorrectPasswordMessage=現在のパスワードが正しくありません。
|
||||||
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
||||||
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||||
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
invalidPasswordMessage=パスワードは空にすることはできません。また、先頭・末尾にスペースを含めることもできません。
|
||||||
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
|
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
|
||||||
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
|
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
|
||||||
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
|
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
|
||||||
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
|
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
|
||||||
disabledCurrentUserMessage=The current user cannot be disabled
|
disabledCurrentUserMessage=現在のユーザーを無効にすることはできません
|
||||||
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
|
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
|
||||||
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
|
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
|
||||||
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
|
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
|
||||||
@@ -76,12 +76,12 @@ donate=寄付する
|
|||||||
color=色
|
color=色
|
||||||
sponsor=スポンサー
|
sponsor=スポンサー
|
||||||
info=Info
|
info=Info
|
||||||
pro=Pro
|
pro=pro
|
||||||
page=Page
|
page=ページ
|
||||||
pages=Pages
|
pages=ページ
|
||||||
loading=Loading...
|
loading=読込中...
|
||||||
addToDoc=Add to Document
|
addToDoc=ドキュメントに追加
|
||||||
reset=Reset
|
reset=リセット
|
||||||
|
|
||||||
legal.privacy=プライバシーポリシー
|
legal.privacy=プライバシーポリシー
|
||||||
legal.terms=利用規約
|
legal.terms=利用規約
|
||||||
@@ -92,7 +92,7 @@ legal.impressum=著作権利者情報
|
|||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
###############
|
###############
|
||||||
pipeline.header=パイプラインメニュー (Alpha)
|
pipeline.header=パイプラインメニュー (Beta)
|
||||||
pipeline.uploadButton=カスタムのアップロード
|
pipeline.uploadButton=カスタムのアップロード
|
||||||
pipeline.configureButton=設定
|
pipeline.configureButton=設定
|
||||||
pipeline.defaultOption=カスタム
|
pipeline.defaultOption=カスタム
|
||||||
@@ -117,21 +117,21 @@ pipelineOptions.validateButton=検証
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Upgrade to Pro
|
enterpriseEdition.button=Proにアップグレード
|
||||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
enterpriseEdition.warning=この機能はProユーザーのみが利用できます。
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
enterpriseEdition.yamlAdvert=Stirling PDF Proは、YAML構成ファイルやその他のSSO機能をサポートしています。
|
||||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=より多くのユーザー管理機能をお探しですか? Stirling PDF Proをご覧ください
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Do you want make Stirling PDF better?
|
analytics.title=Stirling PDFをもっと良くしたいですか?
|
||||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
analytics.paragraph1=Stirling PDFでは、製品の改善に役立つ分析機能をオプトインしています。個人情報やファイルの内容を追跡することはありません。
|
||||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
analytics.paragraph2=Stirling-PDFの成長を支援しユーザーをより深く理解できるように分析を有効にすることを検討してください。
|
||||||
analytics.enable=Enable analytics
|
analytics.enable=分析を有効にする
|
||||||
analytics.disable=Disable analytics
|
analytics.disable=分析を無効にする
|
||||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
analytics.settings=config/settings.ymlファイルでアナリティクスの設定を変更できます。
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +142,14 @@ navbar.language=言語
|
|||||||
navbar.settings=設定
|
navbar.settings=設定
|
||||||
navbar.allTools=ツール
|
navbar.allTools=ツール
|
||||||
navbar.multiTool=マルチツール
|
navbar.multiTool=マルチツール
|
||||||
navbar.search=Search
|
navbar.search=検索
|
||||||
navbar.sections.organize=整理
|
navbar.sections.organize=整理
|
||||||
navbar.sections.convertTo=PDFへ変換
|
navbar.sections.convertTo=PDFへ変換
|
||||||
navbar.sections.convertFrom=PDFから変換
|
navbar.sections.convertFrom=PDFから変換
|
||||||
navbar.sections.security=署名とセキュリティ
|
navbar.sections.security=署名とセキュリティ
|
||||||
navbar.sections.advance=アドバンスド
|
navbar.sections.advance=アドバンスド
|
||||||
navbar.sections.edit=閲覧と編集
|
navbar.sections.edit=閲覧と編集
|
||||||
navbar.sections.popular=Popular
|
navbar.sections.popular=人気
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -208,7 +208,7 @@ adminUserSettings.user=ユーザー
|
|||||||
adminUserSettings.addUser=新しいユーザを追加
|
adminUserSettings.addUser=新しいユーザを追加
|
||||||
adminUserSettings.deleteUser=ユーザの削除
|
adminUserSettings.deleteUser=ユーザの削除
|
||||||
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
|
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
|
||||||
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする必要がありますか?
|
||||||
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||||
adminUserSettings.roles=役割
|
adminUserSettings.roles=役割
|
||||||
adminUserSettings.role=役割
|
adminUserSettings.role=役割
|
||||||
@@ -247,8 +247,8 @@ database.fileNotFound=ファイルが見つかりません
|
|||||||
database.fileNullOrEmpty=ファイルは null または空であってはなりません
|
database.fileNullOrEmpty=ファイルは null または空であってはなりません
|
||||||
database.failedImportFile=ファイルのインポートに失敗
|
database.failedImportFile=ファイルのインポートに失敗
|
||||||
|
|
||||||
session.expired=Your session has expired. Please refresh the page and try again.
|
session.expired=セッションが期限切れです。ページを更新してもう一度お試しください。
|
||||||
session.refreshPage=Refresh Page
|
session.refreshPage=ページを更新
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -488,48 +488,52 @@ overlay-pdfs.tags=Overlay
|
|||||||
|
|
||||||
home.split-by-sections.title=PDFをセクションで分割
|
home.split-by-sections.title=PDFをセクションで分割
|
||||||
home.split-by-sections.desc=PDFの各ページを縦横に分割します。
|
home.split-by-sections.desc=PDFの各ページを縦横に分割します。
|
||||||
split-by-sections.tags=Section Split, Divide, Customize
|
split-by-sections.tags=Section Split, Divide, Customize,Customise
|
||||||
|
|
||||||
home.AddStampRequest.title=PDFにスタンプを追加
|
home.AddStampRequest.title=PDFにスタンプを追加
|
||||||
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
|
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
|
||||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDFを書籍に変換
|
home.PDFToBook.title=PDFを書籍に変換
|
||||||
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.BookToPDF.title=PDFを書籍に変換
|
home.BookToPDF.title=PDFを書籍に変換
|
||||||
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.removeImagePdf.title=画像の削除
|
home.removeImagePdf.title=画像の削除
|
||||||
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
|
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
home.splitPdfByChapters.title=PDFをチャプターごとに分割
|
||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=チャプターの構造に基づいてPDFを複数のファイルに分割します
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=PDF署名の検証
|
||||||
|
home.validateSignature.desc=PDF文書のデジタル署名と証明書を検証します
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=色の置換・反転
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=PDFの色の置換・反転
|
||||||
home.replaceColorPdf.title=Replace and Invert Color
|
home.replaceColorPdf.title=色の置換と反転
|
||||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
home.replaceColorPdf.desc=PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。
|
||||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Replace or Invert color Options
|
replace-color.selectText.1=色の置換または反転オプション
|
||||||
replace-color.selectText.2=Default(Default high contrast colors)
|
replace-color.selectText.2=デフォルト(デフォルトの高コントラスト色)
|
||||||
replace-color.selectText.3=Custom(Customized colors)
|
replace-color.selectText.3=カスタム(カスタマイズされた色)
|
||||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
replace-color.selectText.4=フル反転(すべての色を反転)
|
||||||
replace-color.selectText.5=High contrast color options
|
replace-color.selectText.5=高コントラストカラーオプション
|
||||||
replace-color.selectText.6=white text on black background
|
replace-color.selectText.6=黒背景に白文字
|
||||||
replace-color.selectText.7=Black text on white background
|
replace-color.selectText.7=白背景に黒文字
|
||||||
replace-color.selectText.8=Yellow text on black background
|
replace-color.selectText.8=黒背景に黄色文字
|
||||||
replace-color.selectText.9=Green text on black background
|
replace-color.selectText.9=黒背景に緑文字
|
||||||
replace-color.selectText.10=Choose text Color
|
replace-color.selectText.10=テキストの色を選択
|
||||||
replace-color.selectText.11=Choose background Color
|
replace-color.selectText.11=背景色を選択
|
||||||
replace-color.submit=Replace
|
replace-color.submit=置換
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -556,9 +560,9 @@ login.oauth2AccessDenied=アクセス拒否
|
|||||||
login.oauth2InvalidTokenResponse=無効なトークン応答
|
login.oauth2InvalidTokenResponse=無効なトークン応答
|
||||||
login.oauth2InvalidIdToken=無効なIDトークン
|
login.oauth2InvalidIdToken=無効なIDトークン
|
||||||
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
|
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
|
||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=すでにログインしています
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=アクティブなセッションが多すぎます
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=自動塗りつぶし
|
autoRedact.title=自動塗りつぶし
|
||||||
@@ -574,8 +578,8 @@ autoRedact.submitButton=送信
|
|||||||
|
|
||||||
|
|
||||||
#showJS
|
#showJS
|
||||||
showJS.title=JavaScriptを表示
|
showJS.title=Javascriptを表示
|
||||||
showJS.header=JavaScriptを表示
|
showJS.header=Javascriptを表示
|
||||||
showJS.downloadJS=Javascriptをダウンロード
|
showJS.downloadJS=Javascriptをダウンロード
|
||||||
showJS.submit=表示
|
showJS.submit=表示
|
||||||
|
|
||||||
@@ -753,7 +757,7 @@ certSign.showSig=署名を表示
|
|||||||
certSign.reason=理由
|
certSign.reason=理由
|
||||||
certSign.location=場所
|
certSign.location=場所
|
||||||
certSign.name=名前
|
certSign.name=名前
|
||||||
certSign.showLogo=Show Logo
|
certSign.showLogo=ロゴを表示
|
||||||
certSign.submit=PDFに署名
|
certSign.submit=PDFに署名
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +792,9 @@ compare.highlightColor.2=ハイライトカラー 2:
|
|||||||
compare.document.1=ドキュメント 1
|
compare.document.1=ドキュメント 1
|
||||||
compare.document.2=ドキュメント 2
|
compare.document.2=ドキュメント 2
|
||||||
compare.submit=比較
|
compare.submit=比較
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
compare.complex.message=提供された文書の一方または両方が大きなファイルであるため、比較の精度が低下する可能性があります。
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
compare.large.file.message=提供された文書の1つまたは両方が大きすぎて処理できません
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
compare.no.text.message=選択したPDFの1つまたは両方にテキストコンテンツがありません。比較するには、テキストを含むPDFを選択してください。
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=書籍やコミックをPDFに変換
|
BookToPDF.title=書籍やコミックをPDFに変換
|
||||||
@@ -799,8 +803,8 @@ BookToPDF.credit=calibreを使用
|
|||||||
BookToPDF.submit=変換
|
BookToPDF.submit=変換
|
||||||
|
|
||||||
#PDFToBook
|
#PDFToBook
|
||||||
PDFToBook.title=書籍をPDFに変換
|
PDFToBook.title=PDFを書籍に変換
|
||||||
PDFToBook.header=書籍をPDFに変換
|
PDFToBook.header=PDFを書籍に変換
|
||||||
PDFToBook.selectText.1=フォーマット
|
PDFToBook.selectText.1=フォーマット
|
||||||
PDFToBook.credit=calibreを使用
|
PDFToBook.credit=calibreを使用
|
||||||
PDFToBook.submit=変換
|
PDFToBook.submit=変換
|
||||||
@@ -813,17 +817,17 @@ sign.draw=署名を書く
|
|||||||
sign.text=テキスト入力
|
sign.text=テキスト入力
|
||||||
sign.clear=クリア
|
sign.clear=クリア
|
||||||
sign.add=追加
|
sign.add=追加
|
||||||
sign.saved=Saved Signatures
|
sign.saved=保存された署名
|
||||||
sign.save=Save Signature
|
sign.save=署名を保存
|
||||||
sign.personalSigs=Personal Signatures
|
sign.personalSigs=個人署名
|
||||||
sign.sharedSigs=Shared Signatures
|
sign.sharedSigs=共有署名
|
||||||
sign.noSavedSigs=No saved signatures found
|
sign.noSavedSigs=保存された署名が見つかりません
|
||||||
sign.addToAll=Add to all pages
|
sign.addToAll=すべてのページに追加
|
||||||
sign.delete=Delete
|
sign.delete=削除
|
||||||
sign.first=First page
|
sign.first=最初のページ
|
||||||
sign.last=Last page
|
sign.last=最後のページ
|
||||||
sign.next=Next page
|
sign.next=次のページ
|
||||||
sign.previous=Previous page
|
sign.previous=前のページ
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
repair.title=修復
|
repair.title=修復
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCRモード
|
|||||||
ocr.selectText.11=OCR後に画像を削除する (すべての画像を削除します。変換ステップの一部である場合にのみ有効です)。
|
ocr.selectText.11=OCR後に画像を削除する (すべての画像を削除します。変換ステップの一部である場合にのみ有効です)。
|
||||||
ocr.selectText.12=レンダリングタイプ (高度)
|
ocr.selectText.12=レンダリングタイプ (高度)
|
||||||
ocr.help=他の言語でこれを使用する方法やDocker以外で使用する方法についてはこのドキュメントをお読みください。
|
ocr.help=他の言語でこれを使用する方法やDocker以外で使用する方法についてはこのドキュメントをお読みください。
|
||||||
ocr.credit=本サービスにはOCRにOCRmyPDFとTesseractを使用しています。
|
ocr.credit=本サービスにはOCRにqpdfとTesseractを使用しています。
|
||||||
ocr.submit=OCRでPDFを処理する
|
ocr.submit=OCRでPDFを処理する
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=PDFを変換
|
|||||||
#compress
|
#compress
|
||||||
compress.title=圧縮
|
compress.title=圧縮
|
||||||
compress.header=PDFを圧縮
|
compress.header=PDFを圧縮
|
||||||
compress.credit=本サービスはPDFの圧縮/最適化にGhostscriptを使用しています。
|
compress.credit=本サービスはPDFの圧縮/最適化にqpdfを使用しています。
|
||||||
compress.selectText.1=手動モード - 1 から 4
|
compress.selectText.1=手動モード - 1 から 4
|
||||||
compress.selectText.2=品質レベル:
|
compress.selectText.2=品質レベル:
|
||||||
compress.selectText.3=4 (テキスト画像は最悪)
|
compress.selectText.3=4 (テキスト画像は最悪)
|
||||||
@@ -940,27 +944,39 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
|
|||||||
multiTool.title=PDFマルチツール
|
multiTool.title=PDFマルチツール
|
||||||
multiTool.header=PDFマルチツール
|
multiTool.header=PDFマルチツール
|
||||||
multiTool.uploadPrompts=ファイル名
|
multiTool.uploadPrompts=ファイル名
|
||||||
multiTool.selectAll=Select All
|
multiTool.selectAll=すべて選択
|
||||||
multiTool.deselectAll=Deselect All
|
multiTool.deselectAll=選択を解除
|
||||||
multiTool.selectPages=Page Select
|
multiTool.selectPages=ページ選択
|
||||||
multiTool.selectedPages=Selected Pages
|
multiTool.selectedPages=選択したページ
|
||||||
multiTool.page=Page
|
multiTool.page=ページ
|
||||||
multiTool.deleteSelected=Delete Selected
|
multiTool.deleteSelected=選択項目を削除
|
||||||
multiTool.downloadAll=Export
|
multiTool.downloadAll=エクスポート
|
||||||
multiTool.downloadSelected=Export Selected
|
multiTool.downloadSelected=選択項目をエクスポート
|
||||||
|
|
||||||
multiTool.insertPageBreak=Insert Page Break
|
multiTool.insertPageBreak=改ページを挿入
|
||||||
multiTool.addFile=Add File
|
multiTool.addFile=ファイルを追加
|
||||||
multiTool.rotateLeft=Rotate Left
|
multiTool.rotateLeft=左回転
|
||||||
multiTool.rotateRight=Rotate Right
|
multiTool.rotateRight=右回転
|
||||||
multiTool.split=Split
|
multiTool.split=分割
|
||||||
multiTool.moveLeft=Move Left
|
multiTool.moveLeft=左に移動
|
||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=右に移動
|
||||||
multiTool.delete=Delete
|
multiTool.delete=削除
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=選択されたページ
|
||||||
|
multiTool.undo=元に戻す
|
||||||
|
multiTool.redo=やり直す
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=このファイルはパスワードで保護されています。パスワードを入力してください:
|
||||||
|
decrypt.cancelled=PDFの操作がキャンセルされました: {0}
|
||||||
|
decrypt.noPassword=暗号化されたPDFにパスワードが指定されていません: {0}
|
||||||
|
decrypt.invalidPassword=正しいパスワードでもう一度お試しください。
|
||||||
|
decrypt.invalidPasswordHeader=PDFのパスワードが正しくないか、暗号化がサポートされていません: {0}
|
||||||
|
decrypt.unexpectedError=ファイルの処理中にエラーが発生しました。もう一度お試しください。
|
||||||
|
decrypt.serverError=復号化中にサーバーエラーが発生しました: {0}
|
||||||
|
decrypt.success=ファイルの暗号化が正常に完了しました。
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=この機能は、<a href="{0}">マルチツール</a>でもご利用いただけます。強化されたページごとのUIと追加機能についてはこちらをご覧ください。
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=PDFを表示
|
viewPdf.title=PDFを表示
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=暗号化
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=透かしの追加
|
watermark.title=透かしの追加
|
||||||
watermark.header=透かしの追加
|
watermark.header=透かしの追加
|
||||||
|
watermark.customColor=文字色のカスタム
|
||||||
watermark.selectText.1=透かしを追加するPDFを選択:
|
watermark.selectText.1=透かしを追加するPDFを選択:
|
||||||
watermark.selectText.2=透かしのテキスト:
|
watermark.selectText.2=透かしのテキスト:
|
||||||
watermark.selectText.3=文字サイズ:
|
watermark.selectText.3=文字サイズ:
|
||||||
@@ -1112,11 +1129,11 @@ changeMetadata.submit=変更
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDFをPDF/Aに変換
|
pdfToPDFA.title=PDFをPDF/Aに変換
|
||||||
pdfToPDFA.header=PDFをPDF/Aに変換
|
pdfToPDFA.header=PDFをPDF/Aに変換
|
||||||
pdfToPDFA.credit=本サービスはPDF/Aの変換にghostscriptを使用しています。
|
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
|
||||||
pdfToPDFA.submit=変換
|
pdfToPDFA.submit=変換
|
||||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=出力形式
|
||||||
pdfToPDFA.pdfWithDigitalSignature=PDF にはデジタル署名が含まれています。これは次の手順で削除されます。
|
pdfToPDFA.pdfWithDigitalSignature=PDFにはデジタル署名が含まれています。これは次の手順で削除されます。
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@@ -1221,8 +1238,8 @@ licenses.license=ライセンス
|
|||||||
survey.nav=アンケート
|
survey.nav=アンケート
|
||||||
survey.title=Stirling-PDFのアンケート
|
survey.title=Stirling-PDFのアンケート
|
||||||
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
|
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
|
||||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
survey.changes=Stirling-PDFは前回の調査から変更されました。詳細についてはこちらのブログ投稿をご覧ください。
|
||||||
survey.changes2=With these changes we are getting paid business support and funding
|
survey.changes2=これらの変更により私たちは有償のビジネスサポートと資金援助を受けています
|
||||||
survey.please=アンケートにご協力ください!
|
survey.please=アンケートにご協力ください!
|
||||||
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
|
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
|
||||||
survey.button=アンケートに答える
|
survey.button=アンケートに答える
|
||||||
@@ -1250,13 +1267,61 @@ removeImage.removeImage=画像の削除
|
|||||||
removeImage.submit=画像を削除
|
removeImage.submit=画像を削除
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Split PDF by Chapters
|
splitByChapters.title=PDFをチャプターごとに分割
|
||||||
splitByChapters.header=Split PDF by Chapters
|
splitByChapters.header=PDFをチャプターごとに分割
|
||||||
splitByChapters.bookmarkLevel=Bookmark Level
|
splitByChapters.bookmarkLevel=ブックマークレベル
|
||||||
splitByChapters.includeMetadata=Include Metadata
|
splitByChapters.includeMetadata=メタデータを含める
|
||||||
splitByChapters.allowDuplicates=Allow Duplicates
|
splitByChapters.allowDuplicates=重複を許可する
|
||||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
splitByChapters.desc.1=このツールは、チャプター構造に基づいてPDFファイルを複数のPDFに分割します。
|
||||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
splitByChapters.desc.2=ブックマークレベル:分割に使用するブックマークのレベルを選択します(最上位レベルの場合は0、第2レベルの場合は1など)。
|
||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=メタデータを含める:チェックすると、元のPDFのメタデータが各分割PDFに含まれます。
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=重複を許可:チェックすると同じページ上の複数のブックマークから個別のPDFを作成できます。
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=PDFを分割
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=クリック
|
||||||
|
fileChooser.or=または
|
||||||
|
fileChooser.dragAndDrop=ドラッグ&ドロップ
|
||||||
|
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=リリース
|
||||||
|
releases.title=リリースノート
|
||||||
|
releases.header=リリースノート
|
||||||
|
releases.current.version=現在のリリース
|
||||||
|
releases.note=リリースノートは英語でのみで提供されています
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=PDF署名の検証
|
||||||
|
validateSignature.header=デジタル署名の検証
|
||||||
|
validateSignature.selectPDF=署名済みPDFファイルを選択
|
||||||
|
validateSignature.submit=署名の検証
|
||||||
|
validateSignature.results=検証結果
|
||||||
|
validateSignature.status=状態
|
||||||
|
validateSignature.signer=署名者
|
||||||
|
validateSignature.date=日付
|
||||||
|
validateSignature.reason=理由
|
||||||
|
validateSignature.location=場所
|
||||||
|
validateSignature.noSignatures=この文書にはデジタル署名が見つかりません
|
||||||
|
validateSignature.status.valid=有効
|
||||||
|
validateSignature.status.invalid=無効
|
||||||
|
validateSignature.chain.invalid=証明書チェーンの検証に失敗しました - 署名者の身元を確認できません
|
||||||
|
validateSignature.trust.invalid=証明書が信頼ストアにありません - ソースを検証できません
|
||||||
|
validateSignature.cert.expired=証明書の有効期限が切れています
|
||||||
|
validateSignature.cert.revoked=証明書は取り消されました
|
||||||
|
validateSignature.signature.info=署名情報
|
||||||
|
validateSignature.signature=署名
|
||||||
|
validateSignature.signature.mathValid=署名は数学的には有効ですが:
|
||||||
|
validateSignature.selectCustomCert=カスタム証明書ファイル X.509 (オプション)
|
||||||
|
validateSignature.cert.info=証明書の詳細
|
||||||
|
validateSignature.cert.issuer=発行者
|
||||||
|
validateSignature.cert.subject=主題
|
||||||
|
validateSignature.cert.serialNumber=シリアルナンバー
|
||||||
|
validateSignature.cert.validFrom=有効開始日
|
||||||
|
validateSignature.cert.validUntil=有効期限
|
||||||
|
validateSignature.cert.algorithm=アルゴリズム
|
||||||
|
validateSignature.cert.keySize=キーサイズ
|
||||||
|
validateSignature.cert.version=バージョン
|
||||||
|
validateSignature.cert.keyUsage=キーの使用法
|
||||||
|
validateSignature.cert.selfSigned=自己署名
|
||||||
|
validateSignature.cert.bits=ビット
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=챕터별로 PDF 분할
|
|||||||
home.splitPdfByChapters.desc=PDF를 여러 파일로 나눕니다. 각 장의 구조에 따라.
|
home.splitPdfByChapters.desc=PDF를 여러 파일로 나눕니다. 각 장의 구조에 따라.
|
||||||
splitPdfByChapters.tags=분할, 챕터, 북마크, 조직화
|
splitPdfByChapters.tags=분할, 챕터, 북마크, 조직화
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=색상 교체/반전 PDF
|
replace-color.header=색상 교체/반전 PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR 모드
|
|||||||
ocr.selectText.11=OCR 후 이미지 제거(모든 이미지 제거, 변환 단계의 일부인 경우에만 유용)
|
ocr.selectText.11=OCR 후 이미지 제거(모든 이미지 제거, 변환 단계의 일부인 경우에만 유용)
|
||||||
ocr.selectText.12=렌더 유형(고급)
|
ocr.selectText.12=렌더 유형(고급)
|
||||||
ocr.help=다른 언어 또는 Docker에 포함되지 않은 언어에 대해 사용하는 방법에 대해서는 이 문서를 참조합니다.
|
ocr.help=다른 언어 또는 Docker에 포함되지 않은 언어에 대해 사용하는 방법에 대해서는 이 문서를 참조합니다.
|
||||||
ocr.credit=이 서비스는 OCR에 OCRmyPDF와 Tesseract를 사용합니다.
|
ocr.credit=이 서비스는 OCR에 qpdf와 Tesseract를 사용합니다.
|
||||||
ocr.submit=인식
|
ocr.submit=인식
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=PDF로 변환
|
|||||||
#compress
|
#compress
|
||||||
compress.title=압축
|
compress.title=압축
|
||||||
compress.header=PDF 압축
|
compress.header=PDF 압축
|
||||||
compress.credit=이 서비스는 PDF 압축 및 최적화를 위해 Ghostscript를 사용합니다.
|
compress.credit=이 서비스는 PDF 압축 및 최적화를 위해 qpdf를 사용합니다.
|
||||||
compress.selectText.1=수동 모드 - 1에서 4
|
compress.selectText.1=수동 모드 - 1에서 4
|
||||||
compress.selectText.2=최적화 수준:
|
compress.selectText.2=최적화 수준:
|
||||||
compress.selectText.3=4 (텍스트 이미지에 적합하지 않음)
|
compress.selectText.3=4 (텍스트 이미지에 적합하지 않음)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=암호화
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=워터마크 추가
|
watermark.title=워터마크 추가
|
||||||
watermark.header=워터마크 추가
|
watermark.header=워터마크 추가
|
||||||
|
watermark.customColor=사용자 정의 텍스트 색상
|
||||||
watermark.selectText.1=워터마크를 추가할 PDF 선택:
|
watermark.selectText.1=워터마크를 추가할 PDF 선택:
|
||||||
watermark.selectText.2=워터마크 텍스트:
|
watermark.selectText.2=워터마크 텍스트:
|
||||||
watermark.selectText.3=폰트 크기:
|
watermark.selectText.3=폰트 크기:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=변경
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF를 PDF/A로
|
pdfToPDFA.title=PDF를 PDF/A로
|
||||||
pdfToPDFA.header=PDF 문서를 PDF/A로 변환
|
pdfToPDFA.header=PDF 문서를 PDF/A로 변환
|
||||||
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 ghostscript 문서를 사용합니다.
|
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 qpdf 문서를 사용합니다.
|
||||||
pdfToPDFA.submit=변환
|
pdfToPDFA.submit=변환
|
||||||
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
|
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
|
||||||
pdfToPDFA.outputFormat=출력 형식
|
pdfToPDFA.outputFormat=출력 형식
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=북마크 레벨: 분할에 사용할 북마크 레벨을
|
|||||||
splitByChapters.desc.3=메타데이터 포함: 체크하면 각 분할된 PDF에는 원본 PDF의 메타데이터가 포함됩니다.
|
splitByChapters.desc.3=메타데이터 포함: 체크하면 각 분할된 PDF에는 원본 PDF의 메타데이터가 포함됩니다.
|
||||||
splitByChapters.desc.4=중복 허용: 중복 북마크가 있는 같은 페이지에 여러 번 분할 PDF를 생성합니다.
|
splitByChapters.desc.4=중복 허용: 중복 북마크가 있는 같은 페이지에 여러 번 분할 PDF를 생성합니다.
|
||||||
splitByChapters.submit=PDF 분할
|
splitByChapters.submit=PDF 분할
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=PDF op hoofdstukken splitsen
|
|||||||
home.splitPdfByChapters.desc=Splits een PDF op basis van zijn hoofdstukstructuur in meerdere bestanden.
|
home.splitPdfByChapters.desc=Splits een PDF op basis van zijn hoofdstukstructuur in meerdere bestanden.
|
||||||
splitPdfByChapters.tags=splitsen, hoofdstukken, bookmarks, organiseren
|
splitPdfByChapters.tags=splitsen, hoofdstukken, bookmarks, organiseren
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Kleur-instellingen voor PDF's
|
replace-color.header=Kleur-instellingen voor PDF's
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR-modus
|
|||||||
ocr.selectText.11=Verwijder afbeeldingen na OCR (Verwijdert ALLE afbeeldingen, alleen nuttig als onderdeel van conversiestap)
|
ocr.selectText.11=Verwijder afbeeldingen na OCR (Verwijdert ALLE afbeeldingen, alleen nuttig als onderdeel van conversiestap)
|
||||||
ocr.selectText.12=Weergave Type (Geavanceerd)
|
ocr.selectText.12=Weergave Type (Geavanceerd)
|
||||||
ocr.help=Lees deze documentatie over hoe dit te gebruiken voor andere talen en/of gebruik buiten docker
|
ocr.help=Lees deze documentatie over hoe dit te gebruiken voor andere talen en/of gebruik buiten docker
|
||||||
ocr.credit=Deze dienst maakt gebruik van OCRmyPDF en Tesseract voor OCR.
|
ocr.credit=Deze dienst maakt gebruik van qpdf en Tesseract voor OCR.
|
||||||
ocr.submit=Verwerk PDF met OCR
|
ocr.submit=Verwerk PDF met OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Omzetten naar PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Comprimeren
|
compress.title=Comprimeren
|
||||||
compress.header=PDF comprimeren
|
compress.header=PDF comprimeren
|
||||||
compress.credit=Deze functie gebruikt Ghostscript voor PDF Compressie/Optimalisatie.
|
compress.credit=Deze functie gebruikt qpdf voor PDF Compressie/Optimalisatie.
|
||||||
compress.selectText.1=Handmatige modus - Van 1 tot 4
|
compress.selectText.1=Handmatige modus - Van 1 tot 4
|
||||||
compress.selectText.2=Optimalisatieniveau:
|
compress.selectText.2=Optimalisatieniveau:
|
||||||
compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen)
|
compress.selectText.3=4 (Verschrikkelijk voor tekstafbeeldingen)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Versleutelen
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Watermerk toevoegen
|
watermark.title=Watermerk toevoegen
|
||||||
watermark.header=Watermerk toevoegen
|
watermark.header=Watermerk toevoegen
|
||||||
|
watermark.customColor=Aangepaste tekstkleur
|
||||||
watermark.selectText.1=Selecteer PDF om watermerk toe te voegen:
|
watermark.selectText.1=Selecteer PDF om watermerk toe te voegen:
|
||||||
watermark.selectText.2=Watermerk tekst:
|
watermark.selectText.2=Watermerk tekst:
|
||||||
watermark.selectText.3=Tekengrootte:
|
watermark.selectText.3=Tekengrootte:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Wijzigen
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF naar PDF/A
|
pdfToPDFA.title=PDF naar PDF/A
|
||||||
pdfToPDFA.header=PDF naar PDF/A
|
pdfToPDFA.header=PDF naar PDF/A
|
||||||
pdfToPDFA.credit=Deze service gebruikt ghostscript voor PDF/A-conversie
|
pdfToPDFA.credit=Deze service gebruikt qpdf voor PDF/A-conversie
|
||||||
pdfToPDFA.submit=Converteren
|
pdfToPDFA.submit=Converteren
|
||||||
pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd.
|
pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd.
|
||||||
pdfToPDFA.outputFormat=Uitvoerindeling
|
pdfToPDFA.outputFormat=Uitvoerindeling
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Boekmarkeer niveau: Kies het boekmarkeer niveau om te geb
|
|||||||
splitByChapters.desc.3=Metadata inclusief: Als gecijfeld, de originele PDF's metadata wordt ingevoegd in elk gesplitst PDF-bestand.
|
splitByChapters.desc.3=Metadata inclusief: Als gecijfeld, de originele PDF's metadata wordt ingevoegd in elk gesplitst PDF-bestand.
|
||||||
splitByChapters.desc.4=Dubbele items toestaan: Als gecijfeld, zorgen multiple boekmarkeersymboolen op dezelfde pagina voor het maken van aparte PDF-bestanden.
|
splitByChapters.desc.4=Dubbele items toestaan: Als gecijfeld, zorgen multiple boekmarkeersymboolen op dezelfde pagina voor het maken van aparte PDF-bestanden.
|
||||||
splitByChapters.submit=PDF splitsen
|
splitByChapters.submit=PDF splitsen
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
@@ -512,6 +512,10 @@ home.splitPdfByChapters.title=Split PDF by Chapters
|
|||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
home.validateSignature.title=Validate PDF Signature
|
||||||
|
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
||||||
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
@@ -868,7 +872,7 @@ ocr.selectText.10=OCR-modus
|
|||||||
ocr.selectText.11=Fjern bilder etter OCR (Fjerner ALLE bilder, kun nyttig hvis det er en del av konverteringsprosessen)
|
ocr.selectText.11=Fjern bilder etter OCR (Fjerner ALLE bilder, kun nyttig hvis det er en del av konverteringsprosessen)
|
||||||
ocr.selectText.12=Renderingstype (Avansert)
|
ocr.selectText.12=Renderingstype (Avansert)
|
||||||
ocr.help=Vennligst les denne dokumentasjonen for hvordan du bruker dette for andre språk og/eller bruk utenfor Docker.
|
ocr.help=Vennligst les denne dokumentasjonen for hvordan du bruker dette for andre språk og/eller bruk utenfor Docker.
|
||||||
ocr.credit=Denne tjenesten bruker OCRmyPDF og Tesseract for OCR.
|
ocr.credit=Denne tjenesten bruker qpdf og Tesseract for OCR.
|
||||||
ocr.submit=Behandle PDF med OCR
|
ocr.submit=Behandle PDF med OCR
|
||||||
|
|
||||||
|
|
||||||
@@ -892,7 +896,7 @@ fileToPDF.submit=Konverter til PDF
|
|||||||
#compress
|
#compress
|
||||||
compress.title=Komprimer
|
compress.title=Komprimer
|
||||||
compress.header=Komprimer PDF
|
compress.header=Komprimer PDF
|
||||||
compress.credit=Denne tjenesten bruker Ghostscript for PDF-komprimering/optimisering.
|
compress.credit=Denne tjenesten bruker qpdf for PDF-komprimering/optimisering.
|
||||||
compress.selectText.1=Manuell modus - Fra 1 til 4
|
compress.selectText.1=Manuell modus - Fra 1 til 4
|
||||||
compress.selectText.2=Optimeringsnivå:
|
compress.selectText.2=Optimeringsnivå:
|
||||||
compress.selectText.3=4 (Dårlig for tekstbilder)
|
compress.selectText.3=4 (Dårlig for tekstbilder)
|
||||||
@@ -958,6 +962,18 @@ multiTool.moveLeft=Move Left
|
|||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=Move Right
|
||||||
multiTool.delete=Delete
|
multiTool.delete=Delete
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=Page(s) Selected
|
||||||
|
multiTool.undo=Undo
|
||||||
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
@@ -1050,6 +1066,7 @@ addPassword.submit=Krypter
|
|||||||
#watermark
|
#watermark
|
||||||
watermark.title=Legg til vannmerke
|
watermark.title=Legg til vannmerke
|
||||||
watermark.header=Legg til vannmerke
|
watermark.header=Legg til vannmerke
|
||||||
|
watermark.customColor=Tilpasset Tekstfarge
|
||||||
watermark.selectText.1=Velg PDF-fil å legge til vannmerke på:
|
watermark.selectText.1=Velg PDF-fil å legge til vannmerke på:
|
||||||
watermark.selectText.2=Vannmerketekst:
|
watermark.selectText.2=Vannmerketekst:
|
||||||
watermark.selectText.3=Skriftstørrelse:
|
watermark.selectText.3=Skriftstørrelse:
|
||||||
@@ -1112,7 +1129,7 @@ changeMetadata.submit=Endre
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF til PDF/A
|
pdfToPDFA.title=PDF til PDF/A
|
||||||
pdfToPDFA.header=PDF til PDF/A
|
pdfToPDFA.header=PDF til PDF/A
|
||||||
pdfToPDFA.credit=Denne tjenesten bruker ghostscript for PDF/A-konvertering
|
pdfToPDFA.credit=Denne tjenesten bruker qpdf for PDF/A-konvertering
|
||||||
pdfToPDFA.submit=Konverter
|
pdfToPDFA.submit=Konverter
|
||||||
pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig
|
pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig
|
||||||
pdfToPDFA.outputFormat=Utdataformat
|
pdfToPDFA.outputFormat=Utdataformat
|
||||||
@@ -1260,3 +1277,51 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
#File Chooser
|
||||||
|
fileChooser.click=Click
|
||||||
|
fileChooser.or=or
|
||||||
|
fileChooser.dragAndDrop=Drag & Drop
|
||||||
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
|
|
||||||
|
#release notes
|
||||||
|
releases.footer=Releases
|
||||||
|
releases.title=Release Notes
|
||||||
|
releases.header=Release Notes
|
||||||
|
releases.current.version=Current Release
|
||||||
|
releases.note=Release notes are only available in English
|
||||||
|
|
||||||
|
#Validate Signature
|
||||||
|
validateSignature.title=Validate PDF Signatures
|
||||||
|
validateSignature.header=Validate Digital Signatures
|
||||||
|
validateSignature.selectPDF=Select signed PDF file
|
||||||
|
validateSignature.submit=Validate Signatures
|
||||||
|
validateSignature.results=Validation Results
|
||||||
|
validateSignature.status=Status
|
||||||
|
validateSignature.signer=Signer
|
||||||
|
validateSignature.date=Date
|
||||||
|
validateSignature.reason=Reason
|
||||||
|
validateSignature.location=Location
|
||||||
|
validateSignature.noSignatures=No digital signatures found in this document
|
||||||
|
validateSignature.status.valid=Valid
|
||||||
|
validateSignature.status.invalid=Invalid
|
||||||
|
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
||||||
|
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
||||||
|
validateSignature.cert.expired=Certificate has expired
|
||||||
|
validateSignature.cert.revoked=Certificate has been revoked
|
||||||
|
validateSignature.signature.info=Signature Information
|
||||||
|
validateSignature.signature=Signature
|
||||||
|
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
||||||
|
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
||||||
|
validateSignature.cert.info=Certificate Details
|
||||||
|
validateSignature.cert.issuer=Issuer
|
||||||
|
validateSignature.cert.subject=Subject
|
||||||
|
validateSignature.cert.serialNumber=Serial Number
|
||||||
|
validateSignature.cert.validFrom=Valid From
|
||||||
|
validateSignature.cert.validUntil=Valid Until
|
||||||
|
validateSignature.cert.algorithm=Algorithm
|
||||||
|
validateSignature.cert.keySize=Key Size
|
||||||
|
validateSignature.cert.version=Version
|
||||||
|
validateSignature.cert.keyUsage=Key Usage
|
||||||
|
validateSignature.cert.selfSigned=Self-Signed
|
||||||
|
validateSignature.cert.bits=bits
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user