Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
908b409155 | ||
|
|
4ad716f281 | ||
|
|
148feda83f | ||
|
|
771b312ee8 | ||
|
|
00a0670954 | ||
|
|
39423c247c | ||
|
|
6d8d0bad56 | ||
|
|
a3374745f8 | ||
|
|
d65a637a46 | ||
|
|
d0bf385d69 | ||
|
|
bc35745768 | ||
|
|
e50391a44a | ||
|
|
96b080528b | ||
|
|
f35cbc4310 | ||
|
|
c09fc1541f | ||
|
|
dff53310a7 | ||
|
|
ec537c6fde | ||
|
|
ce70796fff | ||
|
|
7db7192d95 | ||
|
|
d00e7fe958 | ||
|
|
510f39ad41 | ||
|
|
950a0c4b21 | ||
|
|
e6793bd04a | ||
|
|
0f60974a57 | ||
|
|
0ed4c16dc0 | ||
|
|
ea6d4a293e | ||
|
|
191e79da18 | ||
|
|
c54c18b247 | ||
|
|
39cbb5e7d9 | ||
|
|
3df0474ed2 | ||
|
|
9ff2cb63d0 | ||
|
|
d8087d8c55 | ||
|
|
0dfb4d77c0 | ||
|
|
065f53e577 | ||
|
|
c899f605a9 | ||
|
|
47de0f84db | ||
|
|
543b96c033 | ||
|
|
c1126e57bd | ||
|
|
7c5077006d | ||
|
|
3e7889cee8 | ||
|
|
281047f42a | ||
|
|
07f85ea8b4 | ||
|
|
e07f73dce7 | ||
|
|
bfe38c71e8 | ||
|
|
072090d41b | ||
|
|
560936e182 | ||
|
|
6eb79e65fa | ||
|
|
cbe92269f4 | ||
|
|
81871a6f10 | ||
|
|
cf2a7896da | ||
|
|
6a3d95ba09 | ||
|
|
85ed0c38d1 | ||
|
|
6c7dc34640 | ||
|
|
ecfdfa5644 | ||
|
|
11e279bd12 | ||
|
|
929f0bbbe5 | ||
|
|
5751b1ac2d | ||
|
|
4bf78ffd5d | ||
|
|
b7d37deb85 | ||
|
|
2a65fd0825 | ||
|
|
422264a288 | ||
|
|
695fbb0150 | ||
|
|
a17105e650 | ||
|
|
32ac38e93f | ||
|
|
3c0d2b908f | ||
|
|
ab62a93a0d | ||
|
|
5189708d25 | ||
|
|
19831c050c | ||
|
|
e426d99145 | ||
|
|
4088208fc8 | ||
|
|
31ce5b1221 | ||
|
|
be05db22f5 | ||
|
|
79927416e5 | ||
|
|
f95ee31bbd | ||
|
|
40042c37f2 | ||
|
|
1c90b65bca | ||
|
|
2971425544 | ||
|
|
a9ee698432 | ||
|
|
15b5d51957 | ||
|
|
92893b8d4c | ||
|
|
3a6969cad0 | ||
|
|
88e8663d44 | ||
|
|
a2d7490d45 | ||
|
|
c363a1c4e0 | ||
|
|
9e84836cfe | ||
|
|
c8a2789caf | ||
|
|
202b996c2b | ||
|
|
e766a5f583 | ||
|
|
c7d18939fc | ||
|
|
34e198d8e6 | ||
|
|
f6a31e6ed0 | ||
|
|
7768dc9fd9 | ||
|
|
b739d3847e | ||
|
|
a3e5cb51b0 | ||
|
|
ab7a41d155 | ||
|
|
3576c32c52 | ||
|
|
f43fe15193 | ||
|
|
234ae17dc8 | ||
|
|
7ee14ac794 | ||
|
|
ba604bda3e | ||
|
|
036c10fc27 | ||
|
|
baa9410242 | ||
|
|
ab2fe5ef81 | ||
|
|
a926289fb0 | ||
|
|
b711be3061 | ||
|
|
f07ba9192b | ||
|
|
dc2f891632 | ||
|
|
fe9c5a7351 |
2
.github/ISSUE_TEMPLATE/2-feature.yml
vendored
2
.github/ISSUE_TEMPLATE/2-feature.yml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Feature Request
|
||||
description: Submit a new feature request.
|
||||
title: "[Feature Request]: "
|
||||
labels:
|
||||
- enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
49
.github/labeler-config.yml
vendored
Normal file
49
.github/labeler-config.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
Translation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/main/resources/messages_*_*.properties'
|
||||
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
|
||||
|
||||
Front End:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/main/resources/templates/**/*'
|
||||
- any-glob-to-any-file: 'src/main/resources/static/**/*'
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/**'
|
||||
|
||||
Java:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/main/java/**/*.java'
|
||||
|
||||
Back End:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/model/provider/**/*'
|
||||
- any-glob-to-any-file: 'src/main/resources/settings.yml.template'
|
||||
- any-glob-to-any-file: 'src/main/resources/banner.txt'
|
||||
|
||||
Security:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/model/provider/**/*'
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/model/AuthenticationType.java'
|
||||
|
||||
API:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*'
|
||||
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.md'
|
||||
- any-glob-to-any-file: 'scripts/counter_translation.py'
|
||||
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
|
||||
|
||||
Docker:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'Dockerfile'
|
||||
- any-glob-to-any-file: 'Dockerfile-*'
|
||||
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
|
||||
|
||||
Test:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'cucumber/**/*'
|
||||
- any-glob-to-any-file: 'src/test**/*'
|
||||
93
.github/labels.yml
vendored
Normal file
93
.github/labels.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# Labels names are important as they are used by Release Drafter to decide
|
||||
# regarding where to record them in changelog or if to skip them.
|
||||
#
|
||||
# The repository labels will be automatically configured using this file and
|
||||
# the GitHub Action https://github.com/marketplace/actions/github-labeler.
|
||||
- name: "Back End"
|
||||
color: "20CE6C"
|
||||
description: "Issues related to back-end development"
|
||||
from_name: "Back end"
|
||||
- name: "Bug"
|
||||
description: "Something isn't working"
|
||||
color: "EB9CA6"
|
||||
from_name: "bug"
|
||||
- name: "dependencies"
|
||||
description: "Pull requests that update a dependency file"
|
||||
color: "5AA8FC"
|
||||
- name: "Docker"
|
||||
description: "Pull requests that update Docker code"
|
||||
color: "1FCEFF"
|
||||
from_name: "docker"
|
||||
- name: "Documentation"
|
||||
description: "Improvements or additions to documentation"
|
||||
color: "35ABFF"
|
||||
from_name: "documentation"
|
||||
- name: "Done for next release"
|
||||
color: "0CDBD1"
|
||||
- name: "Done"
|
||||
color: "60F13B"
|
||||
- name: "duplicate"
|
||||
description: "This issue or pull request already exists"
|
||||
color: "CDD1D5"
|
||||
- name: "enhancement"
|
||||
description: "New feature or request"
|
||||
color: "A0EEEE"
|
||||
- name: "fix needs confirmation"
|
||||
color: "60A1E7"
|
||||
description: "Fix needs to be confirmed"
|
||||
- name: "Front End"
|
||||
color: "BBD2F1"
|
||||
description: "Issues related to front-end development"
|
||||
- name: "github-actions"
|
||||
description: "Pull requests that update GitHub Actions code"
|
||||
color: "999999"
|
||||
from_name: "github_actions"
|
||||
- name: "good first issue"
|
||||
description: "Good for newcomers"
|
||||
color: "C1B8FF"
|
||||
- name: "help wanted"
|
||||
description: "Extra attention is needed"
|
||||
color: "00E6C4"
|
||||
- name: "invalid"
|
||||
description: "This doesn't seem right"
|
||||
color: "E5E566"
|
||||
- name: "Java"
|
||||
description: "Pull requests that update Java code"
|
||||
color: "FF9E1F"
|
||||
from_name: "java"
|
||||
- name: "Long-term Enhancement"
|
||||
color: "BFDEC3"
|
||||
description: "Enhancements planned for the long term"
|
||||
- name: "more-info-needed"
|
||||
color: "00E4F8"
|
||||
description: "More information is needed"
|
||||
- name: "needs investigation"
|
||||
color: "B8C3A7"
|
||||
description: "Issues that require further investigation"
|
||||
- name: "Prioritised enhancement"
|
||||
color: "4BA2EE"
|
||||
description: "High-priority enhancements"
|
||||
- name: "question"
|
||||
description: "Further information is requested"
|
||||
color: "D97EE5"
|
||||
- name: "Translation"
|
||||
color: "9FABF9"
|
||||
from_name: "translation"
|
||||
- name: "upstream"
|
||||
color: "DEDEDE"
|
||||
- name: "v2"
|
||||
color: "FFFF00"
|
||||
- name: "wontfix"
|
||||
description: "This will not be worked on"
|
||||
color: "FFFFFF"
|
||||
- name: "Security"
|
||||
color: "000000"
|
||||
description: "Security-related issues or pull requests"
|
||||
- name: "API"
|
||||
color: "FFFF00"
|
||||
description: "API-related issues or pull requests"
|
||||
- name: "Test"
|
||||
color: "FF9E1F"
|
||||
description: "Testing-related issues or pull requests"
|
||||
- name: "Stale"
|
||||
color: "000000"
|
||||
1
.github/scripts/check_tabulator.py
vendored
1
.github/scripts/check_tabulator.py
vendored
@@ -1,4 +1,5 @@
|
||||
"""check_tabulator.py"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
18
.github/workflows/auto-labeler.yml
vendored
Normal file
18
.github/workflows/auto-labeler.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
configuration-path: .github/labeler-config.yml
|
||||
sync-labels: true
|
||||
24
.github/workflows/manage-label.yml
vendored
Normal file
24
.github/workflows/manage-label.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Manage labels
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 20 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: Labeler
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Labeler
|
||||
uses: crazy-max/ghaction-github-labeler@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
yaml-file: .github/labels.yml
|
||||
skip-delete: true
|
||||
32
.github/workflows/stale.yml
vendored
Normal file
32
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Close stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: 30 days stale issues
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has had no recent activity.
|
||||
It will be closed if no further activity occurs. Thank you for your contributions.
|
||||
close-issue-message: >
|
||||
This issue has been automatically closed because it has had no recent activity after being marked as stale.
|
||||
Please reopen if you need further assistance.
|
||||
stale-issue-label: "Stale"
|
||||
remove-stale-when-updated: true
|
||||
only-issue-labels: "more-info-needed"
|
||||
days-before-pr-stale: -1 # Prevents PRs from being marked as stale
|
||||
days-before-pr-close: -1 # Prevents PRs from being closed
|
||||
start-date: '2024-07-06T00:00:00Z' # ISO 8601 Format
|
||||
16
.github/workflows/sync_files.yml
vendored
16
.github/workflows/sync_files.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
paths:
|
||||
- "build.gradle"
|
||||
- "src/main/resources/messages_*.properties"
|
||||
- "scripts/translation_status.toml"
|
||||
- "scripts/ignore_translation.toml"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
sync-versions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
git diff --staged --quiet || git commit -m ":floppy_disk: Sync Versions
|
||||
> Made via sync_files.yml" || echo "no changes"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update files
|
||||
@@ -51,12 +51,13 @@ jobs:
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
||||
labels: github-actions
|
||||
sync-readme:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
@@ -73,7 +74,7 @@ jobs:
|
||||
git diff --staged --quiet || git commit -m ":memo: Sync README
|
||||
> Made via sync_files.yml" || echo "no changes"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update files
|
||||
@@ -88,3 +89,4 @@ jobs:
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
||||
labels: Documentation,Translation,github-actions
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -29,8 +29,8 @@ jobs:
|
||||
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
# sudo chmod +x /usr/local/bin/docker-compose
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
45
.gitignore
vendored
45
.gitignore
vendored
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
### Eclipse ###
|
||||
.metadata
|
||||
bin/
|
||||
@@ -22,7 +20,6 @@ customFiles/
|
||||
configs/
|
||||
watchedFolders/
|
||||
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
.lock
|
||||
@@ -119,12 +116,48 @@ watchedFolders/
|
||||
*.db
|
||||
/build
|
||||
|
||||
/.vscode
|
||||
/.idea
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
|
||||
# Virtual environments
|
||||
.env*
|
||||
.venv*
|
||||
env*/
|
||||
venv*/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# VS Code
|
||||
/.vscode/**/*
|
||||
!/.vscode/settings.json
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
out/
|
||||
|
||||
# Ignore Mac DS_Store files
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
|
||||
#cucumber
|
||||
# cucumber
|
||||
/cucumber/reports/**
|
||||
|
||||
# Certs
|
||||
*.p12
|
||||
*.pem
|
||||
*.crt
|
||||
*.cer
|
||||
*.der
|
||||
*.key
|
||||
*.csr
|
||||
|
||||
# cache
|
||||
.ruff_cache
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.ipynb_checkpoints
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ repos:
|
||||
args:
|
||||
- --fix
|
||||
- --line-length=127
|
||||
files: ^((.github/scripts)/.+)?[^/]+\.py$
|
||||
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
exclude: (split_photos.py)
|
||||
- id: ruff-format
|
||||
files: ^((.github/scripts)/.+)?[^/]+\.py$
|
||||
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
|
||||
exclude: (split_photos.py)
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
@@ -33,5 +35,5 @@ repos:
|
||||
# args: ["--replace_with= "]
|
||||
entry: python .github/scripts/check_tabulator.py
|
||||
language: python
|
||||
exclude: ^src/main/resources/static/pdfjs/
|
||||
exclude: ^(src/main/resources/static/pdfjs|src/main/resources/static/pdfjs-legacy)
|
||||
files: ^.*(\.html|\.css|\.js)$
|
||||
|
||||
53
.vscode/settings.json
vendored
Normal file
53
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"files.eol": "auto",
|
||||
"java.configuration.updateBuildConfiguration": "interactive",
|
||||
"black-formatter.args": ["--line-length", "127"],
|
||||
"flake8.args": ["--max-line-length", "127"],
|
||||
"pylint.args": ["max-line-length", "127"],
|
||||
"[java]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
},
|
||||
"[python]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
},
|
||||
"[gradle-build]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
},
|
||||
"[gradle]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.rulers": [127]
|
||||
},
|
||||
"[html]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.rulers": [127],
|
||||
"files.trimFinalNewlines": false,
|
||||
"files.insertFinalNewline": false
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.rulers": [127]
|
||||
},
|
||||
"[yaml]": {
|
||||
"files.trimFinalNewlines": false,
|
||||
"files.insertFinalNewline": false
|
||||
},
|
||||
"diffEditor.maxComputationTime": 0,
|
||||
"editor.wordSegmenterLocales": null,
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"editor.guides.bracketPairsHorizontal": "active",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"editor.indentSize": "tabSize",
|
||||
"editor.stickyScroll.enabled": false,
|
||||
"editor.minimap.enabled": false,
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
40
DATABASE.md
Normal file
40
DATABASE.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# New Database Backup and Import Functionality
|
||||
|
||||
**Full activation will take place on approximately January 5th, 2025!**
|
||||
|
||||
Why is the waiting time six months?
|
||||
|
||||
There are users who only install updates sporadically; if they skip the preparation, it can/will lead to data loss in the database.
|
||||
|
||||
## Functionality Overview
|
||||
|
||||
The newly introduced feature enhances the application with robust database backup and import capabilities. This feature is designed to ensure data integrity and provide a straightforward way to manage database backups. Here's how it works:
|
||||
|
||||
1. Automatic Backup Creation
|
||||
- The system automatically creates a database backup every day at midnight. This ensures that there is always a recent backup available, minimizing the risk of data loss.
|
||||
2. Manual Backup Export
|
||||
- Admin actions that modify the user database trigger a manual export of the database. This keeps the backup up-to-date with the latest changes and provides an extra layer of data security.
|
||||
3. Importing Database Backups
|
||||
- Admin users can import a database backup either via the web interface or API endpoints. This allows for easy restoration of the database to a previous state in case of data corruption or other issues.
|
||||
- The import process ensures that the database structure and data are correctly restored, maintaining the integrity of the application.
|
||||
4. Managing Backup Files
|
||||
- Admins can view a list of all existing backup files, along with their creation dates and sizes. This helps in managing storage and identifying the most recent or relevant backups.
|
||||
- Backup files can be downloaded for offline storage or transferred to other environments, providing flexibility in database management.
|
||||
- Unnecessary backup files can be deleted through the interface to free up storage space and maintain an organized backup directory.
|
||||
|
||||
## User Interface
|
||||
|
||||
### Web Interface
|
||||
|
||||
1. Upload SQL files to import database backups.
|
||||
2. View details of existing backups, such as file names, creation dates, and sizes.
|
||||
3. Download backup files for offline storage.
|
||||
4. Delete outdated or unnecessary backup files.
|
||||
|
||||
### API Endpoints
|
||||
|
||||
1. Import database backups by uploading SQL files.
|
||||
2. Download backup files.
|
||||
3. Delete backup files.
|
||||
|
||||
This new functionality streamlines database management, ensuring that backups are always available and easy to manage, thus improving the reliability and resilience of the application.
|
||||
@@ -1,5 +1,5 @@
|
||||
# Main stage
|
||||
FROM alpine:3.20.0
|
||||
FROM alpine:3.20.2
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
@@ -45,8 +45,8 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
# CV
|
||||
py3-opencv \
|
||||
# python3/pip
|
||||
python3 && \
|
||||
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
||||
python3 \
|
||||
py3-pip && \
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
|
||||
./gradlew clean build
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.20.0
|
||||
FROM alpine:3.20.2
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
@@ -31,7 +31,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
PGID=1000 \
|
||||
UMASK=022 \
|
||||
FAT_DOCKER=true \
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=true
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
||||
|
||||
|
||||
# JDK for app
|
||||
@@ -45,7 +45,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
calibre@testing \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
@@ -62,8 +61,8 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
# CV
|
||||
py3-opencv \
|
||||
# python3/pip
|
||||
python3 && \
|
||||
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
||||
python3 \
|
||||
py3-pip && \
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use alpine
|
||||
FROM alpine:3.20.0
|
||||
FROM alpine:3.20.2
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
|
||||
162
README.md
162
README.md
@@ -9,6 +9,7 @@
|
||||
[](https://github.com/sponsors/Frooodle)
|
||||
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
||||
[<img src="https://www.ssdnodes.com/wp-content/uploads/2023/11/footer-logo.svg" alt="Name" height="40">](https://www.ssdnodes.com/manage/aff.php?aff=2216®ister=true)
|
||||
|
||||
This is a robust, locally hosted web-based PDF manipulation tool using Docker. It enables you to carry out various operations on PDF files, including splitting, merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application has evolved to encompass a comprehensive set of features, addressing all your PDF requirements.
|
||||
|
||||
@@ -21,10 +22,11 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
||||
## Features
|
||||
|
||||
- Dark mode support.
|
||||
- Custom download options (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example)
|
||||
- Custom download options
|
||||
- Parallel file processing and downloads
|
||||
- API for integration with external scripts
|
||||
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
|
||||
- Database Backup and Import (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DATABASE.md) for documentation)
|
||||
|
||||
## **PDF Features**
|
||||
|
||||
@@ -82,7 +84,8 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
||||
- Get all information on a PDF to view or export as JSON.
|
||||
|
||||
For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
|
||||
Demo of the app is available [here](https://stirlingpdf.io). username: demo, password: demo
|
||||
|
||||
Demo of the app is available [here](https://stirlingpdf.io).
|
||||
|
||||
## Technologies used
|
||||
|
||||
@@ -105,33 +108,36 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGui
|
||||
|
||||
https://hub.docker.com/r/frooodle/s-pdf
|
||||
|
||||
Stirling PDF has 2 different versions, a Full version and ultra-Lite version. Depending on the types of features you use you may want a smaller image to save on space.
|
||||
Stirling PDF has 3 different versions, a Full version and ultra-Lite version as well as a 'Fat' version. Depending on the types of features you use you may want a smaller image to save on space.
|
||||
To see what the different versions offer please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md)
|
||||
For people that don't mind about space optimization just use the latest tag.
|
||||

|
||||

|
||||

|
||||
|
||||
Docker Run
|
||||
Please note in below examples you may need to change the volume paths as needed, current examples install them to the current working directory
|
||||
eg ``./extraConfigs:/configs`` to ``/opt/stirlingpdf/extraConfigs:/configs``
|
||||
|
||||
### Docker Run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 8080:8080 \
|
||||
-v /location/of/trainingData:/usr/share/tessdata \
|
||||
-v /location/of/extraConfigs:/configs \
|
||||
-v /location/of/logs:/logs \
|
||||
-v ./trainingData:/usr/share/tessdata \
|
||||
-v ./extraConfigs:/configs \
|
||||
-v ./logs:/logs \
|
||||
-e DOCKER_ENABLE_SECURITY=false \
|
||||
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
||||
-e LANGS=en_GB \
|
||||
--name stirling-pdf \
|
||||
frooodle/s-pdf:latest
|
||||
|
||||
|
||||
Can also add these for customisation but are not required
|
||||
|
||||
-v /location/of/customFiles:/customFiles \
|
||||
```
|
||||
|
||||
Docker Compose
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
@@ -141,10 +147,10 @@ services:
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- /location/of/trainingData:/usr/share/tessdata #Required for extra OCR languages
|
||||
- /location/of/extraConfigs:/configs
|
||||
# - /location/of/customFiles:/customFiles/
|
||||
# - /location/of/logs:/logs/
|
||||
- ./trainingData:/usr/share/tessdata #Required for extra OCR languages
|
||||
- ./extraConfigs:/configs
|
||||
# - ./customFiles:/customFiles/
|
||||
# - ./logs:/logs/
|
||||
environment:
|
||||
- DOCKER_ENABLE_SECURITY=false
|
||||
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
||||
@@ -159,41 +165,46 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Stirling PDF currently supports 32!
|
||||
Stirling PDF currently supports 38!
|
||||
|
||||
| Language | Progress |
|
||||
| ------------------------------------------- | -------------------------------------- |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||
|
||||
@@ -214,11 +225,11 @@ Environment variables are also supported and would override the settings file
|
||||
For example in the settings.yml you have
|
||||
|
||||
```yaml
|
||||
system:
|
||||
security:
|
||||
enableLogin: 'true'
|
||||
```
|
||||
|
||||
To have this via an environment variable you would have ``SYSTEM_ENABLELOGIN``
|
||||
To have this via an environment variable you would have ``SECURITY_ENABLELOGIN``
|
||||
|
||||
The Current list of settings is
|
||||
|
||||
@@ -228,35 +239,36 @@ security:
|
||||
csrfDisabled: true # Set to 'true' to disable CSRF protection (not recommended for production)
|
||||
loginAttemptCount: 5 # lock user account after 5 tries
|
||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||
# initialLogin:
|
||||
# username: "admin" # Initial username for the first login
|
||||
# password: "stirling" # Initial password for the first login
|
||||
# oauth2:
|
||||
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||
# issuer: "" # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||
# clientId: "" # Client ID from your provider
|
||||
# clientSecret: "" # Client Secret from your provider
|
||||
# autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
# client:
|
||||
# google:
|
||||
# clientId: "" # Client ID for Google OAuth2
|
||||
# clientSecret: "" # Client Secret for Google OAuth2
|
||||
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||
# github:
|
||||
# clientId: "" # Client ID for GitHub OAuth2
|
||||
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||
# keycloak:
|
||||
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||
loginMethod: all # 'all' (Login Username/Password and OAuth2[must be enabled and configured]), 'normal'(only Login with Username/Password) or 'oauth2'(only Login with OAuth2)
|
||||
initialLogin:
|
||||
username: '' # Initial username for the first login
|
||||
password: '' # Initial password for the first login
|
||||
oauth2:
|
||||
enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||
client:
|
||||
keycloak:
|
||||
issuer: '' # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||
clientId: '' # Client ID for Keycloak OAuth2
|
||||
clientSecret: '' # Client Secret for Keycloak OAuth2
|
||||
scopes: openid, profile, email # Scopes for Keycloak OAuth2
|
||||
useAsUsername: preferred_username # Field to use as the username for Keycloak OAuth2
|
||||
google:
|
||||
clientId: '' # Client ID for Google OAuth2
|
||||
clientSecret: '' # Client Secret for Google OAuth2
|
||||
scopes: https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile # Scopes for Google OAuth2
|
||||
useAsUsername: email # Field to use as the username for Google OAuth2
|
||||
github:
|
||||
clientId: '' # Client ID for GitHub OAuth2
|
||||
clientSecret: '' # Client Secret for GitHub OAuth2
|
||||
scopes: read:user # Scope for GitHub OAuth2
|
||||
useAsUsername: login # Field to use as the username for GitHub OAuth2
|
||||
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||
clientId: '' # Client ID from your provider
|
||||
clientSecret: '' # Client Secret from your provider
|
||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
||||
useAsUsername: email # Default is 'email'; custom fields can be used as the username
|
||||
scopes: openid, profile, email # Specify the scopes for which the application will request permissions
|
||||
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
@@ -267,9 +279,9 @@ system:
|
||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
||||
|
||||
ui:
|
||||
appName: null # Application's visible name
|
||||
homeDescription: null # Short description or tagline shown on homepage.
|
||||
appNameNavbar: null # Name displayed on the navigation bar
|
||||
appName: '' # Application's visible name
|
||||
homeDescription: '' # Short description or tagline shown on homepage.
|
||||
appNameNavbar: '' # Name displayed on the navigation bar
|
||||
|
||||
endpoints:
|
||||
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||
@@ -303,7 +315,7 @@ For those wanting to use Stirling-PDFs backend API to link with their own custom
|
||||
|
||||

|
||||
|
||||
### Prerequisites:
|
||||
### Prerequisites
|
||||
|
||||
- User must have the folder ./configs volumed within docker so that it is retained during updates.
|
||||
- Docker users must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
| Technology | Ultra-Lite | Full |
|
||||
|All versions in a Docker environment can download Calibre as a optional extra at runtime to support `book-to-pdf` and `pdf-to-book` using parameter ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS``.
|
||||
The 'Fat' container contains all those found in 'Full' with security jar along with this Calibre install.
|
||||
|
||||
Technology | Ultra-Lite | Full |
|
||||
| ---------- | :--------: | :---: |
|
||||
| Java | ✔️ | ✔️ |
|
||||
| JavaScript | ✔️ | ✔️ |
|
||||
|
||||
226
build.gradle
226
build.gradle
@@ -1,24 +1,31 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.2.4'
|
||||
id 'io.spring.dependency-management' version '1.1.3'
|
||||
id 'org.springdoc.openapi-gradle-plugin' version '1.8.0'
|
||||
id "java"
|
||||
id "org.springframework.boot" version "3.3.2"
|
||||
id "io.spring.dependency-management" version "1.1.6"
|
||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||
id "io.swagger.swaggerhub" version "1.3.2"
|
||||
id 'edu.sc.seis.launch4j' version '3.0.5'
|
||||
id 'com.diffplug.spotless' version '6.25.0'
|
||||
id 'com.github.jk1.dependency-license-report' version '2.6'
|
||||
id "edu.sc.seis.launch4j" version "3.0.5"
|
||||
id "com.diffplug.spotless" version "6.25.0"
|
||||
id "com.github.jk1.dependency-license-report" version "2.8"
|
||||
}
|
||||
|
||||
import com.github.jk1.license.render.*
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.26.1'
|
||||
ext {
|
||||
springBootVersion = "3.3.2"
|
||||
}
|
||||
|
||||
//17 is lowest but we support and recommend 21
|
||||
sourceCompatibility = '17'
|
||||
group = "stirling.software"
|
||||
version = "0.27.0"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
licenseReport {
|
||||
@@ -28,15 +35,18 @@ licenseReport {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false') {
|
||||
exclude 'stirling/software/SPDF/config/security/**'
|
||||
exclude 'stirling/software/SPDF/controller/api/UserController.java'
|
||||
exclude 'stirling/software/SPDF/controller/web/AccountWebController.java'
|
||||
exclude 'stirling/software/SPDF/model/ApiKeyAuthenticationToken.java'
|
||||
exclude 'stirling/software/SPDF/model/Authority.java'
|
||||
exclude 'stirling/software/SPDF/model/PersistentLogin.java'
|
||||
exclude 'stirling/software/SPDF/model/User.java'
|
||||
exclude 'stirling/software/SPDF/repository/**'
|
||||
if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") {
|
||||
exclude "stirling/software/SPDF/config/security/**"
|
||||
exclude "stirling/software/SPDF/controller/api/UserController.java"
|
||||
exclude "stirling/software/SPDF/controller/api/DatabaseController.java"
|
||||
exclude "stirling/software/SPDF/controller/web/AccountWebController.java"
|
||||
exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java"
|
||||
exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java"
|
||||
exclude "stirling/software/SPDF/model/AttemptCounter.java"
|
||||
exclude "stirling/software/SPDF/model/Authority.java"
|
||||
exclude "stirling/software/SPDF/model/PersistentLogin.java"
|
||||
exclude "stirling/software/SPDF/model/User.java"
|
||||
exclude "stirling/software/SPDF/repository/**"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,34 +59,34 @@ openApi {
|
||||
}
|
||||
|
||||
launch4j {
|
||||
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
||||
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
||||
|
||||
outfile="Stirling-PDF.exe"
|
||||
headerType="console"
|
||||
jarTask = tasks.bootJar
|
||||
outfile="Stirling-PDF.exe"
|
||||
headerType="console"
|
||||
jarTask = tasks.bootJar
|
||||
|
||||
errTitle="Encountered error, Do you have Java 21?"
|
||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||
variables=["BROWSER_OPEN=true", "ENDPOINTS_GROUPS_TO_REMOVE=CLI"]
|
||||
jreMinVersion="17"
|
||||
errTitle="Encountered error, Do you have Java 21?"
|
||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||
variables=["BROWSER_OPEN=true", "ENDPOINTS_GROUPS_TO_REMOVE=CLI"]
|
||||
jreMinVersion="17"
|
||||
|
||||
mutexName="Stirling-PDF"
|
||||
windowTitle="Stirling-PDF"
|
||||
mutexName="Stirling-PDF"
|
||||
windowTitle="Stirling-PDF"
|
||||
|
||||
messagesStartupError="An error occurred while starting Stirling-PDF"
|
||||
//messagesJreNotFoundError="This application requires a Java Runtime Environment, Please download Java 17."
|
||||
messagesJreVersionError="You are running the wrong version of Java, Please download Java 21."
|
||||
messagesLauncherError="Java is corrupted. Please uninstall and then install Java 21."
|
||||
messagesInstanceAlreadyExists="Stirling-PDF is already running."
|
||||
messagesStartupError="An error occurred while starting Stirling-PDF"
|
||||
// messagesJreNotFoundError="This application requires a Java Runtime Environment, Please download Java 17."
|
||||
messagesJreVersionError="You are running the wrong version of Java, Please download Java 21."
|
||||
messagesLauncherError="Java is corrupted. Please uninstall and then install Java 21."
|
||||
messagesInstanceAlreadyExists="Stirling-PDF is already running."
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
target project.fileTree('src/main/java')
|
||||
|
||||
googleJavaFormat('1.19.1').aosp().reorderImports(false)
|
||||
googleJavaFormat("1.22.0").aosp().reorderImports(false)
|
||||
|
||||
importOrder('java', 'javax', 'org', 'com', 'net', 'io')
|
||||
importOrder("java", "javax", "org", "com", "net", "io")
|
||||
toggleOffOn()
|
||||
trimTrailingWhitespace()
|
||||
indentWithSpaces()
|
||||
@@ -84,130 +94,140 @@ spotless {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.wrapper {
|
||||
gradleVersion = "8.7"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//security updates
|
||||
implementation 'ch.qos.logback:logback-classic:1.5.3'
|
||||
implementation 'ch.qos.logback:logback-core:1.5.3'
|
||||
implementation 'org.springframework:spring-webmvc:6.1.5'
|
||||
implementation "ch.qos.logback:logback-classic:1.5.6"
|
||||
implementation "ch.qos.logback:logback-core:1.5.6"
|
||||
implementation "org.springframework:spring-webmvc:6.1.9"
|
||||
|
||||
implementation("io.github.pixee:java-security-toolkit:1.1.3")
|
||||
|
||||
implementation 'org.yaml:snakeyaml:2.2'
|
||||
// implementation "org.yaml:snakeyaml:2.2"
|
||||
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
|
||||
|
||||
// Exclude Tomcat and include Jetty
|
||||
implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') {
|
||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion") {
|
||||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
||||
}
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jetty:3.2.4'
|
||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4'
|
||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||
|
||||
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4'
|
||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.4"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client:3.2.4'
|
||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||
|
||||
//2.2.x requires rebuild of DB file.. need migration path
|
||||
implementation "com.h2database:h2:2.1.214"
|
||||
// implementation "com.h2database:h2:2.2.224"
|
||||
}
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.4'
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||
|
||||
// Batik
|
||||
implementation 'org.apache.xmlgraphics:batik-all:1.17'
|
||||
implementation "org.apache.xmlgraphics:batik-all:1.17"
|
||||
|
||||
// TwelveMonkeys
|
||||
implementation 'com.twelvemonkeys.imageio:imageio-batik:3.10.1'
|
||||
implementation 'com.twelvemonkeys.imageio:imageio-bmp:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-hdr:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-icns:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-iff:3.10.1'
|
||||
implementation 'com.twelvemonkeys.imageio:imageio-jpeg:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-pcx:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-pict:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-pnm:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-psd:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-sgi:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-tga:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-thumbsdb:3.10.1'
|
||||
implementation 'com.twelvemonkeys.imageio:imageio-tiff:3.10.1'
|
||||
implementation 'com.twelvemonkeys.imageio:imageio-webp:3.10.1'
|
||||
// implementation 'com.twelvemonkeys.imageio:imageio-xwd:3.10.1'
|
||||
implementation "com.twelvemonkeys.imageio:imageio-batik:3.11.0"
|
||||
implementation "com.twelvemonkeys.imageio:imageio-bmp:3.11.0"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-hdr:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-icns:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-iff:3.10.1"
|
||||
implementation "com.twelvemonkeys.imageio:imageio-jpeg:3.11.0"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-pcx:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-pict:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-pnm:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-psd:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-sgi:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-tga:3.10.1"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-thumbsdb:3.10.1"
|
||||
implementation "com.twelvemonkeys.imageio:imageio-tiff:3.11.0"
|
||||
implementation "com.twelvemonkeys.imageio:imageio-webp:3.11.0"
|
||||
// implementation "com.twelvemonkeys.imageio:imageio-xwd:3.10.1"
|
||||
|
||||
implementation 'commons-io:commons-io:2.16.1'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
|
||||
implementation "commons-io:commons-io:2.16.1"
|
||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||
|
||||
//general PDF
|
||||
|
||||
// https://mvnrepository.com/artifact/com.opencsv/opencsv
|
||||
implementation ('com.opencsv:opencsv:5.9') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
implementation ("com.opencsv:opencsv:5.9") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
}
|
||||
|
||||
implementation ('org.apache.pdfbox:pdfbox:3.0.2'){
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
implementation ("org.apache.pdfbox:pdfbox:3.0.2") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
}
|
||||
|
||||
implementation ('org.apache.pdfbox:xmpbox:3.0.2'){
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
implementation ("org.apache.pdfbox:xmpbox:3.0.2") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
}
|
||||
implementation "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
|
||||
|
||||
implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.4'
|
||||
implementation 'io.micrometer:micrometer-core:1.12.4'
|
||||
implementation group: 'com.google.zxing', name: 'core', version: '3.5.3'
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:1.78.1"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:1.78.1"
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||
implementation "io.micrometer:micrometer-core:1.13.0"
|
||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation 'org.commonmark:commonmark:0.22.0'
|
||||
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.22.0'
|
||||
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
|
||||
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
|
||||
implementation "org.commonmark:commonmark:0.22.0"
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
|
||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.12.1"
|
||||
|
||||
implementation 'com.fathzer:javaluator:3.0.3'
|
||||
implementation "com.fathzer:javaluator:3.0.4"
|
||||
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools:3.2.4")
|
||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||
compileOnly "org.projectlombok:lombok:1.18.32"
|
||||
annotationProcessor "org.projectlombok:lombok:1.18.32"
|
||||
|
||||
testImplementation 'org.mockito:mockito-inline:5.2.0'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
dependsOn 'spotlessApply'
|
||||
options.encoding = "UTF-8"
|
||||
dependsOn "spotlessApply"
|
||||
}
|
||||
compileJava {
|
||||
options.compilerArgs << '-parameters'
|
||||
options.compilerArgs << "-parameters"
|
||||
}
|
||||
|
||||
task writeVersion {
|
||||
def propsFile = file('src/main/resources/version.properties')
|
||||
task writeVersion {
|
||||
def propsFile = file("src/main/resources/version.properties")
|
||||
def props = new Properties()
|
||||
props.setProperty('version', version)
|
||||
props.setProperty("version", version)
|
||||
props.store(propsFile.newWriter(), null)
|
||||
}
|
||||
|
||||
swaggerhubUpload {
|
||||
//dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs
|
||||
api 'Stirling-PDF' // The name of your API on SwaggerHub
|
||||
owner 'Frooodle' // Your SwaggerHub username (or organization name)
|
||||
api "Stirling-PDF" // The name of your API on SwaggerHub
|
||||
owner "Frooodle" // Your SwaggerHub username (or organization name)
|
||||
version project.version // The version of your API
|
||||
inputFile './SwaggerDoc.json' // The path to your Swagger docs
|
||||
token "${System.getenv('SWAGGERHUB_API_KEY')}" // Your SwaggerHub API key, passed as an environment variable
|
||||
oas '3.0.0' // The version of the OpenAPI Specification you're using
|
||||
inputFile "./SwaggerDoc.json" // The path to your Swagger docs
|
||||
token "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
|
||||
oas "3.0.0" // The version of the OpenAPI Specification you"re using
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
manifest {
|
||||
attributes 'Implementation-Title': 'Stirling-PDF',
|
||||
'Implementation-Version': project.version
|
||||
attributes "Implementation-Title": "Stirling-PDF",
|
||||
"Implementation-Version": project.version
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
tasks.named("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
task printVersion {
|
||||
task printVersion {
|
||||
println project.version
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.26.1
|
||||
appVersion: 0.27.0
|
||||
description: locally hosted web application that allows you to perform various operations
|
||||
on PDF files
|
||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||
|
||||
@@ -62,8 +62,10 @@ spec:
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.containerSecurityContext | nindent 10 }}
|
||||
{{- if .Values.envs }}
|
||||
env:
|
||||
- name: SYSTEM_ROOTURIPATH
|
||||
value: {{ .Values.rootPath}}
|
||||
{{- if .Values.envs }}
|
||||
{{ toYaml .Values.envs | indent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.extraArgs }}
|
||||
@@ -75,13 +77,13 @@ spec:
|
||||
containerPort: 8080
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
path: {{ .Values.rootPath}}
|
||||
port: http
|
||||
{{ toYaml .Values.probes.livenessHttpGetConfig | indent 12 }}
|
||||
{{ toYaml .Values.probes.liveness | indent 10 }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
path: {{ .Values.rootPath}}
|
||||
port: http
|
||||
{{ toYaml .Values.probes.readinessHttpGetConfig | indent 12 }}
|
||||
{{ toYaml .Values.probes.readiness | indent 10 }}
|
||||
|
||||
@@ -15,6 +15,9 @@ secret:
|
||||
commonLabels: {}
|
||||
# team_name: dev
|
||||
|
||||
# rootpath for the application
|
||||
rootPath: /
|
||||
|
||||
envs: []
|
||||
# - name: UI_APP_NAME
|
||||
# value: "Stirling PDF"
|
||||
@@ -24,8 +27,6 @@ envs: []
|
||||
# value: "Stirling PDF"
|
||||
# - name: ALLOW_GOOGLE_VISIBILITY
|
||||
# value: "true"
|
||||
# - name: APP_ROOT_PATH
|
||||
# value: "/"
|
||||
# - name: APP_LOCALE
|
||||
# value: "en_GB"
|
||||
|
||||
|
||||
106
cucumber/exampleFiles/ghost1.pdf
Normal file
106
cucumber/exampleFiles/ghost1.pdf
Normal file
@@ -0,0 +1,106 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:20240718233034+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20240718233034+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 210
|
||||
>>
|
||||
stream
|
||||
Gap@Gb79+X'F"5[`EfJOD4:mD<%*=m+N>oDG,>NK`<U'B^0WYY,dWl^i_UcRk`<"L=<NPC$BtQ<5l$3<Y!?BuoCSYQ6GSt25lpqr0IrP?S[b)9%M"e'HHFqcRO'9eRaR0'DYi*Y.:nEMFAoTM;rPL%EF]`CfoELVl_Q,"LS:%iI;Nc[&bG.*65O]ecfK1'*<>5P_s[usI/ph*0pV~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@Gb79+X'F"5Y`EfJOV2A9=!fB]F'tK1LS`,]G+MiTenb&V2-^hqa(5IE#Nr59/!"Qm*5_(BdF!0&h!Yhk/A+\iS'%6tuO$O)9LaZS+flr([1p2&#RS1p/gT[B;rDj-=&=iqUlj(P^/5U@eCFqn4:<lU`l`.HXqG-',hJH.DI.(6L\luSAW`Q'oje[qgVLVIXg%PXe+,<$7('~>endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@GbmK%f(e+0_`ODoa2.):e/i+N3r(.o*Qf\gSNb(bt4FIubi@GIOE=p8Ir3;CbQ@KuG^cdJhODZKQ*upt+*rdZ%!mFmN$*.P)K;`s#]G=8AO3s3DGB.RCOn?[F]bEIg,a>25?B%dh\Z/C6opFE'el@I,P\u\V\]:*JYrrsNJ&d,11VL;$h!43eGu&1X6$+5-h\Vr6!+>4Je,~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000598 00000 n
|
||||
0000000792 00000 n
|
||||
0000000860 00000 n
|
||||
0000001156 00000 n
|
||||
0000001227 00000 n
|
||||
0000001527 00000 n
|
||||
0000001827 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<0d5cf047e754e05f8d574f067785875c><0d5cf047e754e05f8d574f067785875c>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 12
|
||||
>>
|
||||
startxref
|
||||
2127
|
||||
%%EOF
|
||||
106
cucumber/exampleFiles/ghost2.pdf
Normal file
106
cucumber/exampleFiles/ghost2.pdf
Normal file
@@ -0,0 +1,106 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:20240718233034+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20240718233034+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 207
|
||||
>>
|
||||
stream
|
||||
Gap@G:CDb.*/<p2MVk["e@)7*Z0@"b%+@f/9pA%_U<oOkVp?PnGRb81iPg?0i?(]%^_CSf##%;<!7Ne/-%RR^p@t7hKYZ9eJVHV]fjjHIB:6DrW+2\p16@*`r^CpQZZH'2Pjqd<.&hM2UO%$Wi$te%4QmS;<E"QS\!deQG_XtuEK>b(UbS>%`/0S`k\\5'TNY0mmgH?`8]i_0~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 207
|
||||
>>
|
||||
stream
|
||||
Gap@G]afWJ'Lm;=if<;s>V*7BTJ]oQ@P!(q5S+WG1%>L@?8Ue;c>[fY&&IOd5@t@TY@+q.5T<Z'81"J("KhsBa+&u4"n'#6)AjfImh)%$0tVC:aGk",=aJJH#/4]i.WJr9c"cibYm:M-44<%FFlG0Cl\Z'nmo7C"TR+7dk3T#iD(9Pq'\;rQku%o>A_`50SO&7M04=8M'O<Am~>endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@GYmu@>'Ld5[if35r/JNaJ.A.7fP9RpSN*8k^-sEER0,enq1Rsuo@R/uCO-^&Y`F'9d^a?9)?ns+F&dXm[HMgPn6Ep+%TRk5Nh+!(+[H#H:U^.^(YL,PKS'%j/:3O\hJVEK-UUekJTd[A$N^((K^#0Du`i@,/^f5KiUISGr")3/+f9NF8NO1+iUgm^b"X\cE^+[:s!0]Gu6i~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000598 00000 n
|
||||
0000000792 00000 n
|
||||
0000000860 00000 n
|
||||
0000001156 00000 n
|
||||
0000001227 00000 n
|
||||
0000001524 00000 n
|
||||
0000001822 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<407fc55425168745e56176202aad30c9><407fc55425168745e56176202aad30c9>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 12
|
||||
>>
|
||||
startxref
|
||||
2122
|
||||
%%EOF
|
||||
106
cucumber/exampleFiles/ghost3.pdf
Normal file
106
cucumber/exampleFiles/ghost3.pdf
Normal file
@@ -0,0 +1,106 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:20240718233034+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20240718233034+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@G]+0EH(e/_@iZH]:>:>hu1e>07BJg5<'#:.C1n)e#(QJ6R1Rsuo_gpn.+0-H5$/#"iYR[B.9\'>7!aDAC*rf/t&6O#aH<?-7IT'\?X(&TcABG=ON*Nq`4k=o&p@3,0*31r<)TAP2Pk94p0\"R-_sY1$AYo[8B\?4R>feLAB\mpjZhp"`@J3;"Fm97#9+W,"eb95\+#p\^HN~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@G]+0EX'Eriuig+>QHNeD'#n%Sq#n%BW`C'uDUOYK)HdS4E9JMsp+HUmDj&H-t*4?UamXX0peVspk"i_@ba+&u"J>UYDKV_^G,7V==aTZZ<YO7:sNSQ[6"Ja-29NtYjd#=`J@D'h+[QW=:EEb?A<k!f+\`g^?,Vgp7_)91[lR\f.Tkf7VIPLVYM&deF!aYt9Ip^"N",3F'*W~>endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@G]+0EH(e/_@iZH]:>J`g!jPCLm;?AgU"fdk"PQZD\d?lRI_oWc[$tp^]O\:3fK8kWeX2&Jcg0+RoJ]j;2j*upu!b4.o&f)b$I@7CfIYjP^#\VjhC=QhQ]^lV-@<0Tam!0.+Dn@("AK%N,Uc7hb+6VoQ$q2q[7]BB92RoY/.j2N028i1jNf'@<1+Fqf$1&"8omHk`#DHP>OT~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000598 00000 n
|
||||
0000000792 00000 n
|
||||
0000000860 00000 n
|
||||
0000001156 00000 n
|
||||
0000001227 00000 n
|
||||
0000001526 00000 n
|
||||
0000001826 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<80da26147a484f2b7573da8151a93d2e><80da26147a484f2b7573da8151a93d2e>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 12
|
||||
>>
|
||||
startxref
|
||||
2126
|
||||
%%EOF
|
||||
1255
cucumber/exampleFiles/images.pdf
Normal file
1255
cucumber/exampleFiles/images.pdf
Normal file
File diff suppressed because it is too large
Load Diff
106
cucumber/exampleFiles/pdfa1.pdf
Normal file
106
cucumber/exampleFiles/pdfa1.pdf
Normal file
@@ -0,0 +1,106 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:20240718233034+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20240718233034+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 206
|
||||
>>
|
||||
stream
|
||||
Gap@G\IO3f&4Lr[@S4&T2aReWZ3N'9",Ncra>5AuK^J(o@r?=EP>b]h[L@XZ8q7#[c:#H2:^/=b,p3^,&f-Q.'H%!U?%N\iVa1pLMlh/41\A8@dF5@0al:-1?L;D%LpL3g\9`.3c6N/Mp=sE/nO%^@%Cc3`]e`qqS@[pkUWemMZC<P\fkqa55u)*hIUoU437-gb!e_*&B/,&~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@G\IO3V'LdA_ig"8P1PS=kA5Q_GQ\P]*S3\>Q`jHYt?8UdkV`6]UV*On)+1VMV+A@.iF:*6sWfM9f"s.NmVuMto!p7-+,Rb<.h,pdi-&OQ5KO\RRFj.j"A)ScTQ7$hudF^TnZ'XuQA5"O]rYkt><-DJmj'"Ri>n!4`^m409XX`e)AR'*rGsn6m79.18+^ba=qRuss"-A3k+9~>endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 210
|
||||
>>
|
||||
stream
|
||||
Gap@G]+0EH(e/_@iZH]:.1fBHK`Xl'[i1&AjX(\k8hbgo(QJ6R1Rsuo6_I1A5Gg$JL;D#$J2CX;+Cf*cUHk2%H1XmpWe+qZ5moJ#B]>b%%[d,mfSSkS4A:Q4NlOFfrL7eA,s45"eUSakM;927AA,1"-LZ)&nZ/ah=8_X7:?ZMj@J@;r7d`t]Z0\d39M%:$k8[S5D"2oSap4s80l?~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000598 00000 n
|
||||
0000000792 00000 n
|
||||
0000000860 00000 n
|
||||
0000001156 00000 n
|
||||
0000001227 00000 n
|
||||
0000001523 00000 n
|
||||
0000001823 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<88edee24ee67bd7d6b7cf53cfa2222b0><88edee24ee67bd7d6b7cf53cfa2222b0>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 12
|
||||
>>
|
||||
startxref
|
||||
2124
|
||||
%%EOF
|
||||
106
cucumber/exampleFiles/pdfa2.pdf
Normal file
106
cucumber/exampleFiles/pdfa2.pdf
Normal file
@@ -0,0 +1,106 @@
|
||||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:20240718233034+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20240718233034+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@GYmu@>'Ld5[if35rI0]sG)F[U^"c>T)"\\os-r:1V0,enq1Rsuo,*67.@k7U.LRF-P.e"CM2V!>iYi<g`nXh!K?n@$t^rY1$+^0'>=B8H6e;F1WmG#,(eS00(Qe9&:O@nI879DTsT,njXAB?`8:>,Hn3*RV!qh4;&@6%]<9Y*>QZ].Z5o;RAZXg7d[#+bphHs_Ep!QR2TZ2~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 210
|
||||
>>
|
||||
stream
|
||||
Gap@G]+0EH(e/_@iZH]:>=,iY1bE)XN?M;1'J/>i&HY;gks]*rj:!DKpb8@`prC#N+9E#o#-<G*!#p7e6j-1sX2k5S,6XmM"taYkfK^k">%usEeEk=sR<UT"dm`rXD;!S`_jS9LU+(R%e'V%WSMfHP.pXZEQqTQq=&D[I[PS(41(NIAZ1R/U?:Z=hSXu!NDF)bpG2F+/I/q/u1-Y~>endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 209
|
||||
>>
|
||||
stream
|
||||
Gap@G_$YcZ'LhbF`EQB$nqi=8S<;#HbK3&f>rnodRPo`Vf4P[3cJidY(I=[K5NWCT'<lHgci?oCRVNST&[k#q4oSC0FWgAt1pD4d_(hIRjn_Nt+cFgJlfm[1U8@/M4r^Pk<@F!@e?%/!-Vq;]nfdLi9]P2M)ck9?)%oNXa_\N<-d"(pjlH%-G`T@Sj&P(j6.@#Xh\Vr6!1iI2/H~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000598 00000 n
|
||||
0000000792 00000 n
|
||||
0000000860 00000 n
|
||||
0000001156 00000 n
|
||||
0000001227 00000 n
|
||||
0000001526 00000 n
|
||||
0000001827 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<4fcc82a085fe71e34a32d1b23c8b939f><4fcc82a085fe71e34a32d1b23c8b939f>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 12
|
||||
>>
|
||||
startxref
|
||||
2127
|
||||
%%EOF
|
||||
@@ -14,3 +14,8 @@ def after_scenario(context, scenario):
|
||||
os.remove('response_file')
|
||||
if hasattr(context, 'file_name') and os.path.exists(context.file_name):
|
||||
os.remove(context.file_name)
|
||||
|
||||
# Remove any temporary files
|
||||
for temp_file in os.listdir('.'):
|
||||
if temp_file.startswith('genericNonCustomisableName') or temp_file.startswith('temp_image_'):
|
||||
os.remove(temp_file)
|
||||
@@ -1,4 +1,4 @@
|
||||
@example
|
||||
@example @general
|
||||
Feature: API Validation
|
||||
|
||||
@positive @password
|
||||
@@ -92,10 +92,10 @@ Feature: API Validation
|
||||
| threshold | 90 |
|
||||
| whitePercent | 99.9 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/remove-blanks"
|
||||
Then the response content type should be "application/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 1 files
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF should contain 0 pages
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @flatten
|
||||
Scenario: Flatten PDF
|
||||
|
||||
@@ -32,7 +32,7 @@ Feature: API Validation
|
||||
@ocr @positive
|
||||
Scenario: Extract Image Scans
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 images on 2 pages
|
||||
And the pdf contains 3 images of size 300x300 on 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| angleThreshold | 5 |
|
||||
@@ -125,8 +125,7 @@ Feature: API Validation
|
||||
|
||||
@ocr
|
||||
Scenario: PDFA
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | pdfa |
|
||||
@@ -137,8 +136,7 @@ Feature: API Validation
|
||||
|
||||
@ocr
|
||||
Scenario: PDFA1
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | pdfa-1 |
|
||||
@@ -149,8 +147,7 @@ Feature: API Validation
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
Given I use an example file at "exampleFiles/ghost3.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 4 |
|
||||
@@ -161,8 +158,7 @@ Feature: API Validation
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
Given I use an example file at "exampleFiles/ghost2.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 1 |
|
||||
@@ -175,8 +171,7 @@ Feature: API Validation
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
Given I use an example file at "exampleFiles/ghost1.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 1 |
|
||||
|
||||
@@ -94,3 +94,23 @@ Feature: API Validation
|
||||
| 1 | 10 | 2 | 10 |
|
||||
|
||||
|
||||
@extract-images
|
||||
Scenario Outline: Extract Image Scans
|
||||
Given I use an example file at "exampleFiles/images.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| format | <format> |
|
||||
When I send the API request to the endpoint "/api/v1/misc/extract-images"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response file should have extension ".zip"
|
||||
And the response ZIP should contain 20 files
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
Examples:
|
||||
| format |
|
||||
| png |
|
||||
| gif |
|
||||
| jpeg |
|
||||
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@ import io
|
||||
import random
|
||||
import string
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from reportlab.pdfgen import canvas
|
||||
import mimetypes
|
||||
import requests
|
||||
import zipfile
|
||||
import shutil
|
||||
import re
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
#########
|
||||
# GIVEN #
|
||||
@@ -43,8 +46,6 @@ def step_use_example_file(context, filePath, fileInput):
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"The example file '{filePath}' does not exist.")
|
||||
|
||||
|
||||
|
||||
@given('the pdf contains {page_count:d} pages')
|
||||
def step_pdf_contains_pages(context, page_count):
|
||||
writer = PdfWriter()
|
||||
@@ -66,8 +67,6 @@ def step_pdf_contains_blank_pages(context, page_count):
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
|
||||
def create_black_box_image(file_name, size):
|
||||
can = canvas.Canvas(file_name, pagesize=size)
|
||||
width, height = size
|
||||
@@ -76,9 +75,25 @@ def create_black_box_image(file_name, size):
|
||||
can.showPage()
|
||||
can.save()
|
||||
|
||||
def create_pdf_with_black_boxes(file_name, image_count, page_count):
|
||||
page_width, page_height = letter
|
||||
box_size = 72 # 1 inch by 1 inch black box
|
||||
@given(u'the pdf contains {image_count:d} images of size {width:d}x{height:d} on {page_count:d} pages')
|
||||
def step_impl(context, image_count, width, height, page_count):
|
||||
context.param_name = "fileInput"
|
||||
context.file_name = "genericNonCustomisableName.pdf"
|
||||
create_pdf_with_images_and_boxes(context.file_name, image_count, page_count, width, height)
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
def add_black_boxes_to_image(image):
|
||||
if isinstance(image, str):
|
||||
image = Image.open(image)
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.rectangle([(0, 0), image.size], fill=(0, 0, 0)) # Fill image with black
|
||||
return image
|
||||
|
||||
def create_pdf_with_images_and_boxes(file_name, image_count, page_count, image_width, image_height):
|
||||
page_width, page_height = max(letter[0], image_width), max(letter[1], image_height)
|
||||
boxes_per_page = image_count // page_count + (1 if image_count % page_count != 0 else 0)
|
||||
|
||||
writer = PdfWriter()
|
||||
@@ -86,15 +101,31 @@ def create_pdf_with_black_boxes(file_name, image_count, page_count):
|
||||
|
||||
for page in range(page_count):
|
||||
packet = io.BytesIO()
|
||||
can = canvas.Canvas(packet, pagesize=letter)
|
||||
can = canvas.Canvas(packet, pagesize=(page_width, page_height))
|
||||
|
||||
for i in range(boxes_per_page):
|
||||
if box_counter >= image_count:
|
||||
break
|
||||
x = (i % (page_width // box_size)) * box_size
|
||||
y = page_height - ((i // (page_width // box_size) + 1) * box_size)
|
||||
can.setFillColorRGB(0, 0, 0)
|
||||
can.rect(x, y, box_size, box_size, fill=1)
|
||||
|
||||
# Simulating a dynamic image creation (replace this with your actual image creation logic)
|
||||
# For demonstration, we'll create a simple black image
|
||||
dummy_image = Image.new('RGB', (image_width, image_height), color='white') # Create a white image
|
||||
dummy_image = add_black_boxes_to_image(dummy_image) # Add black boxes
|
||||
|
||||
# Convert the PIL Image to bytes to pass to drawImage
|
||||
image_bytes = io.BytesIO()
|
||||
dummy_image.save(image_bytes, format='PNG')
|
||||
image_bytes.seek(0)
|
||||
|
||||
# Check if the image fits in the current page dimensions
|
||||
x = (i % (page_width // image_width)) * image_width
|
||||
y = page_height - (((i % (page_height // image_height)) + 1) * image_height)
|
||||
|
||||
if x + image_width > page_width or y < 0:
|
||||
break
|
||||
|
||||
# Add the image to the PDF
|
||||
can.drawImage(ImageReader(image_bytes), x, y, width=image_width, height=image_height)
|
||||
box_counter += 1
|
||||
|
||||
can.showPage()
|
||||
@@ -103,9 +134,16 @@ def create_pdf_with_black_boxes(file_name, image_count, page_count):
|
||||
new_pdf = PdfReader(packet)
|
||||
writer.add_page(new_pdf.pages[0])
|
||||
|
||||
# Write the PDF to file
|
||||
with open(file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
|
||||
# Clean up temporary image files
|
||||
for i in range(image_count):
|
||||
temp_image_path = f"temp_image_{i}.png"
|
||||
if os.path.exists(temp_image_path):
|
||||
os.remove(temp_image_path)
|
||||
|
||||
@given('the pdf contains {image_count:d} images on {page_count:d} pages')
|
||||
def step_pdf_contains_images(context, image_count, page_count):
|
||||
if not hasattr(context, 'param_name'):
|
||||
@@ -118,7 +156,6 @@ def step_pdf_contains_images(context, image_count, page_count):
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
@given('the pdf contains {page_count:d} pages with random text')
|
||||
def step_pdf_contains_pages_with_random_text(context, page_count):
|
||||
buffer = io.BytesIO()
|
||||
@@ -186,6 +223,21 @@ def save_generated_pdf(context, filename):
|
||||
# WHEN #
|
||||
########
|
||||
|
||||
@when('I send a GET request to "{endpoint}"')
|
||||
def step_send_get_request(context, endpoint):
|
||||
base_url = "http://localhost:8080"
|
||||
full_url = f"{base_url}{endpoint}"
|
||||
response = requests.get(full_url)
|
||||
context.response = response
|
||||
|
||||
@when('I send a GET request to "{endpoint}" with parameters')
|
||||
def step_send_get_request_with_params(context, endpoint):
|
||||
base_url = "http://localhost:8080"
|
||||
params = {row['parameter']: row['value'] for row in context.table}
|
||||
full_url = f"{base_url}{endpoint}"
|
||||
response = requests.get(full_url, params=params)
|
||||
context.response = response
|
||||
|
||||
@when('I send the API request to the endpoint "{endpoint}"')
|
||||
def step_send_api_request(context, endpoint):
|
||||
url = f"http://localhost:8080{endpoint}"
|
||||
@@ -278,7 +330,6 @@ def step_save_response_file(context, filename):
|
||||
f.write(context.response.content)
|
||||
print(f"Saved response content to {filename}")
|
||||
|
||||
|
||||
@then('the response PDF should contain {page_count:d} pages')
|
||||
def step_check_response_pdf_page_count(context, page_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
@@ -305,3 +356,26 @@ def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc):
|
||||
reader = PdfReader(pdf_file)
|
||||
actual_pages_per_doc = len(reader.pages)
|
||||
assert actual_pages_per_doc == pages_per_doc, f"Expected {pages_per_doc} pages per document but got {actual_pages_per_doc} pages in document {file_name}"
|
||||
|
||||
@then('the JSON value of "{key}" should be "{expected_value}"')
|
||||
def step_check_json_value(context, key, expected_value):
|
||||
actual_value = context.response.json().get(key)
|
||||
assert actual_value == expected_value, \
|
||||
f"Expected JSON value for '{key}' to be '{expected_value}' but got '{actual_value}'"
|
||||
|
||||
@then('JSON list entry containing "{identifier_key}" as "{identifier_value}" should have "{target_key}" as "{target_value}"')
|
||||
def step_check_json_list_entry(context, identifier_key, identifier_self, target_key, target_value):
|
||||
json_response = context.response.json()
|
||||
for entry in json_response:
|
||||
if entry.get(identifier_key) == identifier_value:
|
||||
assert entry.get(target_key) == target_value, \
|
||||
f"Expected {target_key} to be {target_value} in entry where {identifier_key} is {identifier_value}, but found {entry.get(target_key)}"
|
||||
break
|
||||
else:
|
||||
raise AssertionError(f"No entry with {identifier_key} as {identifier_value} found")
|
||||
|
||||
@then('the response should match the regex "{pattern}"')
|
||||
def step_response_matches_regex(context, pattern):
|
||||
response_text = context.response.text
|
||||
assert re.match(pattern, response_text), \
|
||||
f"Response '{response_text}' does not match the expected pattern '{pattern}'"
|
||||
|
||||
@@ -22,7 +22,6 @@ services:
|
||||
DOCKER_ENABLE_SECURITY: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS: "true"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
||||
|
||||
@@ -79,7 +79,9 @@ def write_readme(progress_list: list[tuple[str, int]]) -> None:
|
||||
file.writelines(content)
|
||||
|
||||
|
||||
def compare_files(default_file_path, file_paths, translation_status_file) -> list[tuple[str, int]]:
|
||||
def compare_files(
|
||||
default_file_path, file_paths, ignore_translation_file
|
||||
) -> list[tuple[str, int]]:
|
||||
"""Compares the default properties file with other
|
||||
properties files in the directory.
|
||||
|
||||
@@ -92,18 +94,24 @@ def compare_files(default_file_path, file_paths, translation_status_file) -> lis
|
||||
language and progress percentage.
|
||||
""" # noqa: D205
|
||||
num_lines = sum(
|
||||
1 for line in open(default_file_path, encoding="utf-8") if line.strip() and not line.strip().startswith("#")
|
||||
1
|
||||
for line in open(default_file_path, encoding="utf-8")
|
||||
if line.strip() and not line.strip().startswith("#")
|
||||
)
|
||||
|
||||
result_list = []
|
||||
sort_translation_status: tomlkit.TOMLDocument
|
||||
sort_ignore_translation: tomlkit.TOMLDocument
|
||||
|
||||
# read toml
|
||||
with open(translation_status_file, encoding="utf-8") as f:
|
||||
sort_translation_status = tomlkit.parse(f.read())
|
||||
with open(ignore_translation_file, encoding="utf-8") as f:
|
||||
sort_ignore_translation = tomlkit.parse(f.read())
|
||||
|
||||
for file_path in file_paths:
|
||||
language = os.path.basename(file_path).split("messages_", 1)[1].split(".properties", 1)[0]
|
||||
language = (
|
||||
os.path.basename(file_path)
|
||||
.split("messages_", 1)[1]
|
||||
.split(".properties", 1)[0]
|
||||
)
|
||||
|
||||
fails = 0
|
||||
if "en_GB" in language or "en_US" in language:
|
||||
@@ -111,21 +119,25 @@ def compare_files(default_file_path, file_paths, translation_status_file) -> lis
|
||||
result_list.append(("en_US", 100))
|
||||
continue
|
||||
|
||||
if language not in sort_translation_status:
|
||||
sort_translation_status[language] = tomlkit.table()
|
||||
if language not in sort_ignore_translation:
|
||||
sort_ignore_translation[language] = tomlkit.table()
|
||||
|
||||
if (
|
||||
"ignore" not in sort_translation_status[language]
|
||||
or len(sort_translation_status[language].get("ignore", [])) < 1
|
||||
"ignore" not in sort_ignore_translation[language]
|
||||
or len(sort_ignore_translation[language].get("ignore", [])) < 1
|
||||
):
|
||||
sort_translation_status[language]["ignore"] = tomlkit.array(["language.direction"])
|
||||
sort_ignore_translation[language]["ignore"] = tomlkit.array(
|
||||
["language.direction"]
|
||||
)
|
||||
|
||||
# if "missing" not in sort_translation_status[language]:
|
||||
# sort_translation_status[language]["missing"] = tomlkit.array()
|
||||
# elif "language.direction" in sort_translation_status[language]["missing"]:
|
||||
# sort_translation_status[language]["missing"].remove("language.direction")
|
||||
# if "missing" not in sort_ignore_translation[language]:
|
||||
# sort_ignore_translation[language]["missing"] = tomlkit.array()
|
||||
# elif "language.direction" in sort_ignore_translation[language]["missing"]:
|
||||
# sort_ignore_translation[language]["missing"].remove("language.direction")
|
||||
|
||||
with open(default_file_path, encoding="utf-8") as default_file, open(file_path, encoding="utf-8") as file:
|
||||
with open(default_file_path, encoding="utf-8") as default_file, open(
|
||||
file_path, encoding="utf-8"
|
||||
) as file:
|
||||
for _ in range(5):
|
||||
next(default_file)
|
||||
try:
|
||||
@@ -133,34 +145,45 @@ def compare_files(default_file_path, file_paths, translation_status_file) -> lis
|
||||
except StopIteration:
|
||||
fails = num_lines
|
||||
|
||||
for line_num, (line_default, line_file) in enumerate(zip(default_file, file), start=6):
|
||||
for line_num, (line_default, line_file) in enumerate(
|
||||
zip(default_file, file), start=6
|
||||
):
|
||||
try:
|
||||
# Ignoring empty lines and lines start with #
|
||||
if line_default.strip() == "" or line_default.startswith("#"):
|
||||
continue
|
||||
|
||||
default_key, default_value = line_default.split("=", 1)
|
||||
file_key, file_value = line_file.split("=", 1)
|
||||
if (
|
||||
default_value.strip() == file_value.strip()
|
||||
and default_key.strip() not in sort_translation_status[language]["ignore"]
|
||||
and default_key.strip()
|
||||
not in sort_ignore_translation[language]["ignore"]
|
||||
):
|
||||
print(f"{language}: Line {line_num} is missing the translation.")
|
||||
# if default_key.strip() not in sort_translation_status[language]["missing"]:
|
||||
print(
|
||||
f"{language}: Line {line_num} is missing the translation."
|
||||
)
|
||||
# if default_key.strip() not in sort_ignore_translation[language]["missing"]:
|
||||
# missing_array = tomlkit.array()
|
||||
# missing_array.append(default_key.strip())
|
||||
# missing_array.multiline(True)
|
||||
# sort_translation_status[language]["missing"].extend(missing_array)
|
||||
# sort_ignore_translation[language]["missing"].extend(missing_array)
|
||||
fails += 1
|
||||
# elif default_key.strip() in sort_translation_status[language]["ignore"]:
|
||||
# if default_key.strip() in sort_translation_status[language]["missing"]:
|
||||
# sort_translation_status[language]["missing"].remove(default_key.strip())
|
||||
# elif default_key.strip() in sort_ignore_translation[language]["ignore"]:
|
||||
# if default_key.strip() in sort_ignore_translation[language]["missing"]:
|
||||
# sort_ignore_translation[language]["missing"].remove(default_key.strip())
|
||||
if default_value.strip() != file_value.strip():
|
||||
# if default_key.strip() in sort_translation_status[language]["missing"]:
|
||||
# sort_translation_status[language]["missing"].remove(default_key.strip())
|
||||
if default_key.strip() in sort_translation_status[language]["ignore"]:
|
||||
sort_translation_status[language]["ignore"].remove(default_key.strip())
|
||||
|
||||
# if default_key.strip() in sort_ignore_translation[language]["missing"]:
|
||||
# sort_ignore_translation[language]["missing"].remove(default_key.strip())
|
||||
if (
|
||||
default_key.strip()
|
||||
in sort_ignore_translation[language]["ignore"]
|
||||
):
|
||||
sort_ignore_translation[language]["ignore"].remove(
|
||||
default_key.strip()
|
||||
)
|
||||
except ValueError:
|
||||
print(f"{line_default}|{line_file}")
|
||||
exit(1)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
@@ -171,9 +194,9 @@ def compare_files(default_file_path, file_paths, translation_status_file) -> lis
|
||||
int((num_lines - fails) * 100 / num_lines),
|
||||
)
|
||||
)
|
||||
translation_status = convert_to_multiline(sort_translation_status)
|
||||
with open(translation_status_file, "w", encoding="utf-8") as file:
|
||||
file.write(tomlkit.dumps(translation_status))
|
||||
ignore_translation = convert_to_multiline(sort_ignore_translation)
|
||||
with open(ignore_translation_file, "w", encoding="utf-8") as file:
|
||||
file.write(tomlkit.dumps(ignore_translation))
|
||||
|
||||
unique_data = list(set(result_list))
|
||||
unique_data.sort(key=lambda x: x[1], reverse=True)
|
||||
@@ -187,6 +210,8 @@ if __name__ == "__main__":
|
||||
reference_file = os.path.join(directory, "messages_en_GB.properties")
|
||||
|
||||
scripts_directory = os.path.join(os.getcwd(), "scripts")
|
||||
translation_state_file = os.path.join(scripts_directory, "translation_status.toml")
|
||||
translation_state_file = os.path.join(scripts_directory, "ignore_translation.toml")
|
||||
|
||||
write_readme(compare_files(reference_file, messages_file_paths, translation_state_file))
|
||||
write_readme(
|
||||
compare_files(reference_file, messages_file_paths, translation_state_file)
|
||||
)
|
||||
|
||||
@@ -10,7 +10,11 @@ ignore = [
|
||||
|
||||
[ca_CA]
|
||||
ignore = [
|
||||
'PDFToText.tags',
|
||||
'adminUserSettings.admin',
|
||||
'language.direction',
|
||||
'survey.button',
|
||||
'watermark.type.1',
|
||||
]
|
||||
|
||||
[cs_CZ]
|
||||
@@ -21,6 +25,11 @@ ignore = [
|
||||
'text',
|
||||
]
|
||||
|
||||
[da_DK]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[de_DE]
|
||||
ignore = [
|
||||
'AddStampRequest.alphabet',
|
||||
@@ -48,6 +57,7 @@ ignore = [
|
||||
ignore = [
|
||||
'adminUserSettings.roles',
|
||||
'color',
|
||||
'error',
|
||||
'language.direction',
|
||||
'no',
|
||||
'showJS.tags',
|
||||
@@ -60,8 +70,31 @@ ignore = [
|
||||
|
||||
[fr_FR]
|
||||
ignore = [
|
||||
'AddStampRequest.alphabet',
|
||||
'AddStampRequest.position',
|
||||
'AddStampRequest.rotation',
|
||||
'PDFToBook.selectText.1',
|
||||
'addPageNumbers.selectText.3',
|
||||
'adminUserSettings.actions',
|
||||
'alphabet',
|
||||
'compare.document.1',
|
||||
'compare.document.2',
|
||||
'info',
|
||||
'language.direction',
|
||||
'licenses.license',
|
||||
'licenses.module',
|
||||
'licenses.nav',
|
||||
'licenses.version',
|
||||
'pdfOrganiser.mode',
|
||||
'pipeline.title',
|
||||
'pipelineOptions.pipelineHeader',
|
||||
'sponsor',
|
||||
'watermark.type.2',
|
||||
]
|
||||
|
||||
[ga_IE]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[hi_IN]
|
||||
@@ -71,6 +104,7 @@ ignore = [
|
||||
|
||||
[hr_HR]
|
||||
ignore = [
|
||||
'PDFToBook.selectText.1',
|
||||
'font',
|
||||
'home.pipeline.title',
|
||||
'info',
|
||||
@@ -114,16 +148,34 @@ ignore = [
|
||||
|
||||
[nl_NL]
|
||||
ignore = [
|
||||
'HTMLToPDF.print',
|
||||
'adjustContrast.contrast',
|
||||
'compare.document.1',
|
||||
'compare.document.2',
|
||||
'error',
|
||||
'getPdfInfo.downloadJson',
|
||||
'help',
|
||||
'info',
|
||||
'language.direction',
|
||||
'navbar.allTools',
|
||||
'printFile.submit',
|
||||
'showJS.downloadJS',
|
||||
'sponsor',
|
||||
]
|
||||
|
||||
[no_NB]
|
||||
ignore = [
|
||||
'PDFToBook.selectText.1',
|
||||
'adminUserSettings.admin',
|
||||
'info',
|
||||
'language.direction',
|
||||
'oops',
|
||||
'sponsor',
|
||||
]
|
||||
|
||||
[pl_PL]
|
||||
ignore = [
|
||||
'PDFToBook.selectText.1',
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
@@ -149,12 +201,20 @@ ignore = [
|
||||
|
||||
[sk_SK]
|
||||
ignore = [
|
||||
'adminUserSettings.admin',
|
||||
'home.multiTool.title',
|
||||
'info',
|
||||
'language.direction',
|
||||
'navbar.sections.security',
|
||||
'text',
|
||||
'watermark.type.1',
|
||||
]
|
||||
|
||||
[sr_LATN_RS]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
'licenses.version',
|
||||
'poweredBy',
|
||||
]
|
||||
|
||||
[sv_SE]
|
||||
@@ -162,6 +222,14 @@ ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[th_TH]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
'pipeline.title',
|
||||
'pipelineOptions.pipelineHeader',
|
||||
'showJS.tags',
|
||||
]
|
||||
|
||||
[tr_TR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
@@ -172,6 +240,14 @@ ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[vi_VN]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
'pipeline.title',
|
||||
'pipelineOptions.pipelineHeader',
|
||||
'showJS.tags',
|
||||
]
|
||||
|
||||
[zh_CN]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
@@ -12,7 +12,8 @@ fi
|
||||
umask "$UMASK" || true
|
||||
|
||||
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" && "$FAT_DOCKER" != "true" ]]; then
|
||||
apk add --no-cache calibre@testing
|
||||
echo "issue with calibre in current version, feature currently disabled on Stirling-PDF"
|
||||
#apk add --no-cache calibre@testing
|
||||
fi
|
||||
|
||||
if [[ "$FAT_DOCKER" != "true" ]]; then
|
||||
|
||||
@@ -45,7 +45,6 @@ public class SPdfApplication {
|
||||
// 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 = "http://localhost:" + getNonStaticPort();
|
||||
@@ -79,13 +78,14 @@ public class SPdfApplication {
|
||||
|
||||
// custom javs settings file
|
||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||
String existing = propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||
if (!existing.isEmpty()) {
|
||||
existing += ",";
|
||||
String existingLocation =
|
||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||
if (!existingLocation.isEmpty()) {
|
||||
existingLocation += ",";
|
||||
}
|
||||
propertyFiles.put(
|
||||
"spring.config.additional-location",
|
||||
existing + "file:configs/custom_settings.yml");
|
||||
existingLocation + "file:configs/custom_settings.yml");
|
||||
} else {
|
||||
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
"error",
|
||||
"erroroauth",
|
||||
"file",
|
||||
"messageType");
|
||||
"messageType",
|
||||
"infoMessage");
|
||||
|
||||
@Override
|
||||
public boolean preHandle(
|
||||
@@ -31,25 +32,25 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null && !queryString.isEmpty()) {
|
||||
String requestURI = request.getRequestURI();
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
Map<String, String> allowedParameters = new HashMap<>();
|
||||
|
||||
// Keep only the allowed parameters
|
||||
String[] queryParameters = queryString.split("&");
|
||||
for (String param : queryParameters) {
|
||||
String[] keyValue = param.split("=");
|
||||
if (keyValue.length != 2) {
|
||||
String[] keyValuePair = param.split("=");
|
||||
if (keyValuePair.length != 2) {
|
||||
continue;
|
||||
}
|
||||
if (ALLOWED_PARAMS.contains(keyValue[0])) {
|
||||
parameters.put(keyValue[0], keyValue[1]);
|
||||
if (ALLOWED_PARAMS.contains(keyValuePair[0])) {
|
||||
allowedParameters.put(keyValuePair[0], keyValuePair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any parameters that are not allowed
|
||||
if (parameters.size() != queryParameters.length) {
|
||||
if (allowedParameters.size() != queryParameters.length) {
|
||||
// Construct new query string
|
||||
StringBuilder newQueryString = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
for (Map.Entry<String, String> entry : allowedParameters.entrySet()) {
|
||||
if (newQueryString.length() > 0) {
|
||||
newQueryString.append("&");
|
||||
}
|
||||
|
||||
@@ -4,17 +4,26 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.simpleyaml.configuration.comments.CommentType;
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
public class ConfigInitializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigInitializer.class);
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
try {
|
||||
@@ -44,95 +53,89 @@ public class ConfigInitializer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Path templatePath =
|
||||
// Paths.get(
|
||||
// getClass()
|
||||
// .getClassLoader()
|
||||
// .getResource("settings.yml.template")
|
||||
// .toURI());
|
||||
// Path userPath = Paths.get("configs", "settings.yml");
|
||||
//
|
||||
// List<String> templateLines = Files.readAllLines(templatePath);
|
||||
// List<String> userLines =
|
||||
// Files.exists(userPath) ? Files.readAllLines(userPath) : new
|
||||
// ArrayList<>();
|
||||
//
|
||||
// List<String> resultLines = new ArrayList<>();
|
||||
// int position = 0;
|
||||
// for (String templateLine : templateLines) {
|
||||
// // Check if the line is a comment
|
||||
// if (templateLine.trim().startsWith("#")) {
|
||||
// String entry = templateLine.trim().substring(1).trim();
|
||||
// if (!entry.isEmpty()) {
|
||||
// // Check if this comment has been uncommented in userLines
|
||||
// String key = entry.split(":")[0].trim();
|
||||
// addLine(resultLines, userLines, templateLine, key, position);
|
||||
// } else {
|
||||
// resultLines.add(templateLine);
|
||||
// }
|
||||
// }
|
||||
// // Check if the line is a key-value pair
|
||||
// else if (templateLine.contains(":")) {
|
||||
// String key = templateLine.split(":")[0].trim();
|
||||
// addLine(resultLines, userLines, templateLine, key, position);
|
||||
// }
|
||||
// // Handle empty lines
|
||||
// else if (templateLine.trim().length() == 0) {
|
||||
// resultLines.add("");
|
||||
// }
|
||||
// position++;
|
||||
// }
|
||||
//
|
||||
// // Write the result to the user settings file
|
||||
// Files.write(userPath, resultLines);
|
||||
|
||||
// Define the path to the config settings file
|
||||
Path settingsPath = Paths.get("configs", "settings.yml");
|
||||
// Load the template resource
|
||||
URL settingsTemplateResource =
|
||||
getClass().getClassLoader().getResource("settings.yml.template");
|
||||
if (settingsTemplateResource == null) {
|
||||
throw new IOException("Resource not found: settings.yml.template");
|
||||
}
|
||||
|
||||
// Create a temporary file to copy the resource content
|
||||
Path tempTemplatePath = Files.createTempFile("settings.yml", ".template");
|
||||
|
||||
try (InputStream in = settingsTemplateResource.openStream()) {
|
||||
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile());
|
||||
settingsTemplateFile.loadWithComments();
|
||||
|
||||
final YamlFile settingsFile = new YamlFile(settingsPath.toFile());
|
||||
settingsFile.loadWithComments();
|
||||
|
||||
// Load headers and comments
|
||||
String header = settingsTemplateFile.getHeader();
|
||||
|
||||
// Create a new file for temporary settings
|
||||
final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile());
|
||||
tempSettingFile.createNewFile(true);
|
||||
tempSettingFile.setHeader(header);
|
||||
|
||||
// Get all keys from the template
|
||||
List<String> keys =
|
||||
Arrays.asList(settingsTemplateFile.getKeys(true).toArray(new String[0]));
|
||||
|
||||
for (String key : keys) {
|
||||
if (!key.contains(".")) {
|
||||
// Add blank lines and comments to specific sections
|
||||
tempSettingFile
|
||||
.path(key)
|
||||
.comment(settingsTemplateFile.getComment(key))
|
||||
.blankLine();
|
||||
continue;
|
||||
}
|
||||
// Copy settings from the template to the settings.yml file
|
||||
changeConfigItemFromCommentToKeyValue(
|
||||
settingsTemplateFile, settingsFile, tempSettingFile, key);
|
||||
}
|
||||
|
||||
// Save the settings.yml file
|
||||
tempSettingFile.save();
|
||||
}
|
||||
|
||||
// Create custom settings file if it doesn't exist
|
||||
Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
|
||||
if (!Files.exists(customSettingsPath)) {
|
||||
Files.createFile(customSettingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
|
||||
private static void addLine(
|
||||
List<String> resultLines,
|
||||
List<String> userLines,
|
||||
String templateLine,
|
||||
String key,
|
||||
int position) {
|
||||
boolean added = false;
|
||||
int templateIndentationLevel = getIndentationLevel(templateLine);
|
||||
int pos = 0;
|
||||
for (String settingsLine : userLines) {
|
||||
if (settingsLine.trim().startsWith(key + ":") && position == pos) {
|
||||
int settingsIndentationLevel = getIndentationLevel(settingsLine);
|
||||
// Check if it is correct settingsLine and has the same parent as templateLine
|
||||
if (settingsIndentationLevel == templateIndentationLevel) {
|
||||
resultLines.add(settingsLine);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
private void changeConfigItemFromCommentToKeyValue(
|
||||
final YamlFile settingsTemplateFile,
|
||||
final YamlFile settingsFile,
|
||||
final YamlFile tempSettingFile,
|
||||
String path) {
|
||||
if (settingsFile.get(path) == null && settingsTemplateFile.get(path) != null) {
|
||||
// If the key is only in the template, add it to the temporary settings with comments
|
||||
tempSettingFile
|
||||
.path(path)
|
||||
.set(settingsTemplateFile.get(path))
|
||||
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
|
||||
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||
} else if (settingsFile.get(path) != null && settingsTemplateFile.get(path) != null) {
|
||||
// If the key is in both, update the temporary settings with the main settings' value
|
||||
// and comments
|
||||
tempSettingFile
|
||||
.path(path)
|
||||
.set(settingsFile.get(path))
|
||||
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
|
||||
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
|
||||
} else {
|
||||
// Log if the key is not found in both YAML files
|
||||
logger.info("Key not found in both YAML files: " + path);
|
||||
}
|
||||
if (!added) {
|
||||
resultLines.add(templateLine);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getIndentationLevel(String line) {
|
||||
int indentationLevel = 0;
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith("#")) {
|
||||
line = trimmedLine.substring(1);
|
||||
}
|
||||
for (char c : line.toCharArray()) {
|
||||
if (c == ' ') {
|
||||
indentationLevel++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return indentationLevel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import stirling.software.SPDF.utils.FileInfo;
|
||||
|
||||
public interface DatabaseBackupInterface {
|
||||
void exportDatabase() throws IOException;
|
||||
|
||||
boolean importDatabase();
|
||||
|
||||
boolean hasBackup();
|
||||
|
||||
List<FileInfo> getBackupList();
|
||||
}
|
||||
@@ -137,6 +137,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Other", "auto-rename");
|
||||
addEndpointToGroup("Other", "get-info-on-pdf");
|
||||
addEndpointToGroup("Other", "show-javascript");
|
||||
addEndpointToGroup("Other", "remove-image-pdf");
|
||||
|
||||
// CLI
|
||||
addEndpointToGroup("CLI", "compress-pdf");
|
||||
@@ -221,6 +222,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "split-pdf-by-sections");
|
||||
addEndpointToGroup("Java", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Java", "pdf-to-text");
|
||||
addEndpointToGroup("Java", "remove-image-pdf");
|
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class InitialSecuritySetup {
|
||||
|
||||
@Autowired private UserService userService;
|
||||
|
||||
@Autowired private ApplicationProperties applicationProperties;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InitialSecuritySetup.class);
|
||||
@Autowired private DatabaseBackupInterface databaseBackupHelper;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (!userService.hasUsers()) {
|
||||
public void init() throws IllegalArgumentException, IOException {
|
||||
if (databaseBackupHelper.hasBackup() && !userService.hasUsers()) {
|
||||
databaseBackupHelper.importDatabase();
|
||||
} else if (!userService.hasUsers()) {
|
||||
initializeAdminUser();
|
||||
} else {
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
initializeInternalApiUser();
|
||||
}
|
||||
@@ -42,12 +46,11 @@ public class InitialSecuritySetup {
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeAdminUser() {
|
||||
private void initializeAdminUser() throws IOException {
|
||||
String initialUsername =
|
||||
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||
String initialPassword =
|
||||
applicationProperties.getSecurity().getInitialLogin().getPassword();
|
||||
|
||||
if (initialUsername != null
|
||||
&& !initialUsername.isEmpty()
|
||||
&& initialPassword != null
|
||||
@@ -55,9 +58,9 @@ public class InitialSecuritySetup {
|
||||
&& !userService.findByUsernameIgnoreCase(initialUsername).isPresent()) {
|
||||
try {
|
||||
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
||||
logger.info("Admin user created: " + initialUsername);
|
||||
log.info("Admin user created: " + initialUsername);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Failed to initialize security setup", e);
|
||||
log.error("Failed to initialize security setup", e);
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
@@ -65,54 +68,38 @@ public class InitialSecuritySetup {
|
||||
}
|
||||
}
|
||||
|
||||
private void createDefaultAdminUser() {
|
||||
private void createDefaultAdminUser() throws IllegalArgumentException, IOException {
|
||||
String defaultUsername = "admin";
|
||||
String defaultPassword = "stirling";
|
||||
if (!userService.findByUsernameIgnoreCase(defaultUsername).isPresent()) {
|
||||
userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true);
|
||||
logger.info("Default admin user created: " + defaultUsername);
|
||||
log.info("Default admin user created: " + defaultUsername);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeInternalApiUser() {
|
||||
private void initializeInternalApiUser() throws IllegalArgumentException, IOException {
|
||||
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
|
||||
userService.saveUser(
|
||||
Role.INTERNAL_API_USER.getRoleId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Role.INTERNAL_API_USER.getRoleId());
|
||||
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||
logger.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeyToConfig(String key) throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
boolean keyFound = false;
|
||||
|
||||
// Search for the existing key to replace it or place to add it
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (lines.get(i).startsWith("AutomaticallyGenerated:")) {
|
||||
keyFound = true;
|
||||
if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) {
|
||||
lines.set(i + 1, " key: " + key);
|
||||
break;
|
||||
} else {
|
||||
lines.add(i + 1, " key: " + key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||
|
||||
// If the section doesn't exist, append it
|
||||
if (!keyFound) {
|
||||
lines.add("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
lines.add("AutomaticallyGenerated:");
|
||||
lines.add(" key: " + key);
|
||||
}
|
||||
settingsYml.loadWithComments();
|
||||
|
||||
// Write back to the file
|
||||
Files.write(path, lines);
|
||||
settingsYml
|
||||
.path("AutomaticallyGenerated.key")
|
||||
.set(key)
|
||||
.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
settingsYml.save();
|
||||
}
|
||||
|
||||
private boolean isValidUUID(String uuid) {
|
||||
|
||||
@@ -38,12 +38,12 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationS
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
@Configuration
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket;
|
||||
import io.github.bucket4j.ConsumptionProbe;
|
||||
import io.github.bucket4j.Refill;
|
||||
import io.github.pixee.security.Newlines;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
@@ -142,7 +141,10 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||
|
||||
private Bucket createUserBucket(int limitPerDay) {
|
||||
Bandwidth limit =
|
||||
Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
|
||||
Bandwidth.builder()
|
||||
.capacity(limitPerDay)
|
||||
.refillIntervally(limitPerDay, Duration.ofDays(1))
|
||||
.build();
|
||||
return Bucket.builder().addLimit(limit).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -19,6 +20,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
@@ -38,8 +40,11 @@ public class UserService implements UserServiceInterface {
|
||||
|
||||
@Autowired private MessageSource messageSource;
|
||||
|
||||
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
||||
|
||||
// Handle OAUTH2 login and user auto creation.
|
||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) {
|
||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (!isUsernameValid(username)) {
|
||||
return false;
|
||||
}
|
||||
@@ -131,7 +136,7 @@ public class UserService implements UserServiceInterface {
|
||||
}
|
||||
|
||||
public void saveUser(String username, AuthenticationType authenticationType)
|
||||
throws IllegalArgumentException {
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (!isUsernameValid(username)) {
|
||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||
}
|
||||
@@ -142,9 +147,11 @@ public class UserService implements UserServiceInterface {
|
||||
user.addAuthority(new Authority(Role.USER.getRoleId(), user));
|
||||
user.setAuthenticationType(authenticationType);
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
|
||||
public void saveUser(String username, String password) throws IllegalArgumentException {
|
||||
public void saveUser(String username, String password)
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (!isUsernameValid(username)) {
|
||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||
}
|
||||
@@ -154,10 +161,11 @@ public class UserService implements UserServiceInterface {
|
||||
user.setEnabled(true);
|
||||
user.setAuthenticationType(AuthenticationType.WEB);
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
|
||||
public void saveUser(String username, String password, String role, boolean firstLogin)
|
||||
throws IllegalArgumentException {
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (!isUsernameValid(username)) {
|
||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||
}
|
||||
@@ -169,10 +177,11 @@ public class UserService implements UserServiceInterface {
|
||||
user.setAuthenticationType(AuthenticationType.WEB);
|
||||
user.setFirstLogin(firstLogin);
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
|
||||
public void saveUser(String username, String password, String role)
|
||||
throws IllegalArgumentException {
|
||||
throws IllegalArgumentException, IOException {
|
||||
saveUser(username, password, role, false);
|
||||
}
|
||||
|
||||
@@ -206,7 +215,8 @@ public class UserService implements UserServiceInterface {
|
||||
return userCount > 0;
|
||||
}
|
||||
|
||||
public void updateUserSettings(String username, Map<String, String> updates) {
|
||||
public void updateUserSettings(String username, Map<String, String> updates)
|
||||
throws IOException {
|
||||
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
@@ -220,6 +230,7 @@ public class UserService implements UserServiceInterface {
|
||||
user.setSettings(settingsMap);
|
||||
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,22 +246,26 @@ public class UserService implements UserServiceInterface {
|
||||
return authorityRepository.findByUserId(user.getId());
|
||||
}
|
||||
|
||||
public void changeUsername(User user, String newUsername) throws IllegalArgumentException {
|
||||
public void changeUsername(User user, String newUsername)
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (!isUsernameValid(newUsername)) {
|
||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||
}
|
||||
user.setUsername(newUsername);
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
|
||||
public void changePassword(User user, String newPassword) {
|
||||
public void changePassword(User user, String newPassword) throws IOException {
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
|
||||
public void changeFirstUse(User user, boolean firstUse) {
|
||||
public void changeFirstUse(User user, boolean firstUse) throws IOException {
|
||||
user.setFirstLogin(firstUse);
|
||||
userRepository.save(user);
|
||||
databaseBackupHelper.exportDatabase();
|
||||
}
|
||||
|
||||
public void changeRole(User user, String newRole) {
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
package stirling.software.SPDF.config.security.database;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.utils.FileInfo;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class DatabaseBackupHelper implements DatabaseBackupInterface {
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String url;
|
||||
|
||||
private Path backupPath = Paths.get("configs/db/backup/");
|
||||
|
||||
@Override
|
||||
public boolean hasBackup() {
|
||||
// Check if there is at least one backup
|
||||
return !getBackupList().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileInfo> getBackupList() {
|
||||
// Check if the backup directory exists, and create it if it does not
|
||||
ensureBackupDirectoryExists();
|
||||
|
||||
List<FileInfo> backupFiles = new ArrayList<>();
|
||||
|
||||
// Read the backup directory and filter for files with the prefix "backup_" and suffix
|
||||
// ".sql"
|
||||
try (DirectoryStream<Path> stream =
|
||||
Files.newDirectoryStream(
|
||||
backupPath,
|
||||
path ->
|
||||
path.getFileName().toString().startsWith("backup_")
|
||||
&& path.getFileName().toString().endsWith(".sql"))) {
|
||||
for (Path entry : stream) {
|
||||
BasicFileAttributes attrs = Files.readAttributes(entry, BasicFileAttributes.class);
|
||||
LocalDateTime modificationDate =
|
||||
LocalDateTime.ofInstant(
|
||||
attrs.lastModifiedTime().toInstant(), ZoneId.systemDefault());
|
||||
LocalDateTime creationDate =
|
||||
LocalDateTime.ofInstant(
|
||||
attrs.creationTime().toInstant(), ZoneId.systemDefault());
|
||||
long fileSize = attrs.size();
|
||||
backupFiles.add(
|
||||
new FileInfo(
|
||||
entry.getFileName().toString(),
|
||||
entry.toString(),
|
||||
modificationDate,
|
||||
fileSize,
|
||||
creationDate));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading backup directory: {}", e.getMessage(), e);
|
||||
}
|
||||
return backupFiles;
|
||||
}
|
||||
|
||||
// Imports a database backup from the specified file.
|
||||
public boolean importDatabaseFromUI(String fileName) throws IOException {
|
||||
return this.importDatabaseFromUI(getBackupFilePath(fileName));
|
||||
}
|
||||
|
||||
// Imports a database backup from the specified path.
|
||||
public boolean importDatabaseFromUI(Path tempTemplatePath) throws IOException {
|
||||
boolean success = executeDatabaseScript(tempTemplatePath);
|
||||
if (success) {
|
||||
LocalDateTime dateNow = LocalDateTime.now();
|
||||
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
||||
Path insertOutputFilePath =
|
||||
this.getBackupFilePath("backup_user_" + dateNow.format(myFormatObj) + ".sql");
|
||||
Files.copy(tempTemplatePath, insertOutputFilePath);
|
||||
Files.deleteIfExists(tempTemplatePath);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importDatabase() {
|
||||
if (!this.hasBackup()) return false;
|
||||
|
||||
List<FileInfo> backupList = this.getBackupList();
|
||||
backupList.sort(Comparator.comparing(FileInfo::getModificationDate).reversed());
|
||||
|
||||
return executeDatabaseScript(Paths.get(backupList.get(0).getFilePath()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportDatabase() throws IOException {
|
||||
// Check if the backup directory exists, and create it if it does not
|
||||
ensureBackupDirectoryExists();
|
||||
|
||||
// Filter and delete old backups if there are more than 5
|
||||
List<FileInfo> filteredBackupList =
|
||||
this.getBackupList().stream()
|
||||
.filter(backup -> !backup.getFileName().startsWith("backup_user_"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (filteredBackupList.size() > 5) {
|
||||
filteredBackupList.sort(
|
||||
Comparator.comparing(
|
||||
p -> p.getFileName().substring(7, p.getFileName().length() - 4)));
|
||||
Files.deleteIfExists(Paths.get(filteredBackupList.get(0).getFilePath()));
|
||||
log.info("Deleted oldest backup: {}", filteredBackupList.get(0).getFileName());
|
||||
}
|
||||
|
||||
LocalDateTime dateNow = LocalDateTime.now();
|
||||
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
||||
Path insertOutputFilePath =
|
||||
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
|
||||
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "");
|
||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
stmt.setString(1, insertOutputFilePath.toString());
|
||||
stmt.execute();
|
||||
log.info("Database export completed: {}", insertOutputFilePath);
|
||||
} catch (SQLException e) {
|
||||
log.error("Error during database export: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves the H2 database version.
|
||||
public String getH2Version() {
|
||||
String version = "Unknown";
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
|
||||
try (Statement stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
|
||||
if (rs.next()) {
|
||||
version = rs.getString("version");
|
||||
log.info("H2 Database Version: {}", version);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Error retrieving H2 version: {}", e.getMessage(), e);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
// Deletes a backup file.
|
||||
public boolean deleteBackupFile(String fileName) throws IOException {
|
||||
Path filePath = this.getBackupFilePath(fileName);
|
||||
if (Files.deleteIfExists(filePath)) {
|
||||
log.info("Deleted backup file: {}", fileName);
|
||||
return true;
|
||||
} else {
|
||||
log.error("File not found or could not be deleted: {}", fileName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the Path object for a given backup file name.
|
||||
public Path getBackupFilePath(String fileName) {
|
||||
return Paths.get(backupPath.toString(), fileName);
|
||||
}
|
||||
|
||||
private boolean executeDatabaseScript(Path scriptPath) {
|
||||
String query = "RUNSCRIPT from ?;";
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, "sa", "");
|
||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
stmt.setString(1, scriptPath.toString());
|
||||
stmt.execute();
|
||||
log.info("Database import completed: {}", scriptPath);
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
log.error("Error during database import: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureBackupDirectoryExists() {
|
||||
if (Files.notExists(backupPath)) {
|
||||
try {
|
||||
Files.createDirectories(backupPath);
|
||||
} catch (IOException e) {
|
||||
log.error("Error creating directories: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package stirling.software.SPDF.config.security.database;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ScheduledTasks {
|
||||
|
||||
@Autowired private DatabaseBackupHelper databaseBackupService;
|
||||
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
public void performBackup() throws IOException {
|
||||
databaseBackupService.exportDatabase();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.Provider;
|
||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||
import stirling.software.SPDF.utils.UrlUtils;
|
||||
|
||||
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
@@ -51,8 +52,8 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
||||
Provider provider = oauth.getClient().get(registrationId);
|
||||
issuer = provider.getIssuer();
|
||||
clientId = provider.getClientId();
|
||||
} catch (Exception e) {
|
||||
logger.error("exception", e);
|
||||
} catch (UnsupportedProviderException e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.database.DatabaseBackupHelper;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping("/api/v1/database")
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@Tag(name = "Database", description = "Database APIs")
|
||||
public class DatabaseController {
|
||||
|
||||
@Autowired DatabaseBackupHelper databaseBackupHelper;
|
||||
|
||||
@Hidden
|
||||
@PostMapping(consumes = "multipart/form-data", value = "import-database")
|
||||
@Operation(
|
||||
summary = "Import database backup",
|
||||
description = "This endpoint imports a database backup from a SQL file.")
|
||||
public String importDatabase(
|
||||
@RequestParam("fileInput") MultipartFile file, RedirectAttributes redirectAttributes)
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (file == null || file.isEmpty()) {
|
||||
redirectAttributes.addAttribute("error", "fileNullOrEmpty");
|
||||
return "redirect:/database";
|
||||
}
|
||||
log.info("Received file: {}", file.getOriginalFilename());
|
||||
Path tempTemplatePath = Files.createTempFile("backup_", ".sql");
|
||||
try (InputStream in = file.getInputStream()) {
|
||||
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
boolean importSuccess = databaseBackupHelper.importDatabaseFromUI(tempTemplatePath);
|
||||
if (importSuccess) {
|
||||
redirectAttributes.addAttribute("infoMessage", "importIntoDatabaseSuccessed");
|
||||
} else {
|
||||
redirectAttributes.addAttribute("error", "failedImportFile");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error importing database: {}", e.getMessage());
|
||||
redirectAttributes.addAttribute("error", "failedImportFile");
|
||||
}
|
||||
return "redirect:/database";
|
||||
}
|
||||
|
||||
@Hidden
|
||||
@GetMapping("/import-database-file/{fileName}")
|
||||
public String importDatabaseFromBackupUI(@PathVariable String fileName)
|
||||
throws IllegalArgumentException, IOException {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return "redirect:/database?error=fileNullOrEmpty";
|
||||
}
|
||||
|
||||
// Check if the file exists in the backup list
|
||||
boolean fileExists =
|
||||
databaseBackupHelper.getBackupList().stream()
|
||||
.anyMatch(backup -> backup.getFileName().equals(fileName));
|
||||
if (!fileExists) {
|
||||
log.error("File {} not found in backup list", fileName);
|
||||
return "redirect:/database?error=fileNotFound";
|
||||
}
|
||||
log.info("Received file: {}", fileName);
|
||||
if (databaseBackupHelper.importDatabaseFromUI(fileName)) {
|
||||
log.info("File {} imported to database", fileName);
|
||||
return "redirect:/database?infoMessage=importIntoDatabaseSuccessed";
|
||||
}
|
||||
return "redirect:/database?error=failedImportFile";
|
||||
}
|
||||
|
||||
@Hidden
|
||||
@GetMapping("/delete/{fileName}")
|
||||
@Operation(
|
||||
summary = "Delete a database backup file",
|
||||
description =
|
||||
"This endpoint deletes a database backup file with the specified file name.")
|
||||
public String deleteFile(@PathVariable String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
throw new IllegalArgumentException("File must not be null or empty");
|
||||
}
|
||||
try {
|
||||
if (databaseBackupHelper.deleteBackupFile(fileName)) {
|
||||
log.info("Deleted file: {}", fileName);
|
||||
} else {
|
||||
log.error("Failed to delete file: {}", fileName);
|
||||
return "redirect:/database?error=failedToDeleteFile";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting file: {}", e.getMessage());
|
||||
return "redirect:/database?error=" + e.getMessage();
|
||||
}
|
||||
return "redirect:/database";
|
||||
}
|
||||
|
||||
@Hidden
|
||||
@GetMapping("/download/{fileName}")
|
||||
@Operation(
|
||||
summary = "Download a database backup file",
|
||||
description =
|
||||
"This endpoint downloads a database backup file with the specified file name.")
|
||||
public ResponseEntity<?> downloadFile(@PathVariable String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
throw new IllegalArgumentException("File must not be null or empty");
|
||||
}
|
||||
try {
|
||||
Path filePath = databaseBackupHelper.getBackupFilePath(fileName);
|
||||
InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(Files.size(filePath))
|
||||
.body(resource);
|
||||
} catch (IOException e) {
|
||||
log.error("Error downloading file: {}", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.SEE_OTHER_303)
|
||||
.location(URI.create("/database?error=downloadFailed"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.service.PdfImageRemovalService;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
/**
|
||||
* Controller class for handling PDF image removal requests. Provides an endpoint to remove images
|
||||
* from a PDF file to reduce its size.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/general")
|
||||
public class PdfImageRemovalController {
|
||||
|
||||
// Service for removing images from PDFs
|
||||
@Autowired private PdfImageRemovalService pdfImageRemovalService;
|
||||
|
||||
/**
|
||||
* Constructor for dependency injection of PdfImageRemovalService.
|
||||
*
|
||||
* @param pdfImageRemovalService The service used for removing images from PDFs.
|
||||
*/
|
||||
public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) {
|
||||
this.pdfImageRemovalService = pdfImageRemovalService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to remove images from a PDF file.
|
||||
*
|
||||
* <p>This method processes the uploaded PDF file, removes all images, and returns the modified
|
||||
* PDF file with a new name indicating that images were removed.
|
||||
*
|
||||
* @param file The PDF file with images to be removed.
|
||||
* @return ResponseEntity containing the modified PDF file as byte array with appropriate
|
||||
* content type and filename.
|
||||
* @throws IOException If an error occurs while processing the PDF file.
|
||||
*/
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
|
||||
@Operation(
|
||||
summary = "Remove images from file to reduce the file size.",
|
||||
description =
|
||||
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
|
||||
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
|
||||
|
||||
MultipartFile pdf = file.getFileInput();
|
||||
|
||||
// Convert the MultipartFile to a byte array
|
||||
byte[] pdfBytes = pdf.getBytes();
|
||||
|
||||
// Load the PDF document from the byte array
|
||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||
|
||||
// Remove images from the PDF document using the service
|
||||
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
|
||||
|
||||
// Create a ByteArrayOutputStream to hold the modified PDF data
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
// Save the modified PDF document to the output stream
|
||||
modifiedDocument.save(outputStream);
|
||||
modifiedDocument.close();
|
||||
|
||||
// Generate a new filename for the modified PDF
|
||||
String mergedFileName =
|
||||
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_images.pdf";
|
||||
|
||||
// Convert the byte array to a web response and return it
|
||||
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -42,7 +43,8 @@ public class UserController {
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/register")
|
||||
public String register(@ModelAttribute UsernameAndPass requestModel, Model model) {
|
||||
public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
|
||||
throws IOException {
|
||||
if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) {
|
||||
model.addAttribute("error", "Username already exists");
|
||||
return "register";
|
||||
@@ -63,7 +65,8 @@ public class UserController {
|
||||
@RequestParam(name = "newUsername") String newUsername,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
RedirectAttributes redirectAttributes)
|
||||
throws IOException {
|
||||
|
||||
if (!userService.isUsernameValid(newUsername)) {
|
||||
return new RedirectView("/account?messageType=invalidUsername", true);
|
||||
@@ -116,7 +119,8 @@ public class UserController {
|
||||
@RequestParam(name = "newPassword") String newPassword,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
RedirectAttributes redirectAttributes)
|
||||
throws IOException {
|
||||
if (principal == null) {
|
||||
return new RedirectView("/change-creds?messageType=notAuthenticated", true);
|
||||
}
|
||||
@@ -149,7 +153,8 @@ public class UserController {
|
||||
@RequestParam(name = "newPassword") String newPassword,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
RedirectAttributes redirectAttributes)
|
||||
throws IOException {
|
||||
if (principal == null) {
|
||||
return new RedirectView("/account?messageType=notAuthenticated", true);
|
||||
}
|
||||
@@ -176,7 +181,8 @@ public class UserController {
|
||||
|
||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/updateUserSettings")
|
||||
public String updateUserSettings(HttpServletRequest request, Principal principal) {
|
||||
public String updateUserSettings(HttpServletRequest request, Principal principal)
|
||||
throws IOException {
|
||||
Map<String, String[]> paramMap = request.getParameterMap();
|
||||
Map<String, String> updates = new HashMap<>();
|
||||
|
||||
@@ -201,7 +207,8 @@ public class UserController {
|
||||
@RequestParam(name = "password") String password,
|
||||
@RequestParam(name = "role") String role,
|
||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||
boolean forceChange) {
|
||||
boolean forceChange)
|
||||
throws IllegalArgumentException, IOException {
|
||||
|
||||
if (!userService.isUsernameValid(username)) {
|
||||
return new RedirectView("/addUsers?messageType=invalidUsername", true);
|
||||
|
||||
@@ -39,6 +39,12 @@ public class ConvertWebsiteToPDF {
|
||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
||||
throw new IllegalArgumentException("Invalid URL format provided.");
|
||||
}
|
||||
|
||||
// validate the URL is reachable
|
||||
if (!GeneralUtils.isURLReachable(URL)) {
|
||||
throw new IllegalArgumentException("URL is not reachable, please provide a valid URL.");
|
||||
}
|
||||
|
||||
Path tempOutputFile = null;
|
||||
byte[] pdfBytes;
|
||||
try {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -17,6 +17,7 @@ import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -50,31 +51,31 @@ public class BlankPageController {
|
||||
int threshold = request.getThreshold();
|
||||
float whitePercent = request.getWhitePercent();
|
||||
|
||||
PDDocument document = null;
|
||||
try {
|
||||
document = Loader.loadPDF(inputFile.getBytes());
|
||||
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
|
||||
PDPageTree pages = document.getDocumentCatalog().getPages();
|
||||
PDFTextStripper textStripper = new PDFTextStripper();
|
||||
|
||||
List<Integer> pagesToKeepIndex = new ArrayList<>();
|
||||
List<PDPage> nonBlankPages = new ArrayList<>();
|
||||
List<PDPage> blankPages = new ArrayList<>();
|
||||
int pageIndex = 0;
|
||||
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
for (PDPage page : pages) {
|
||||
logger.info("checking page " + pageIndex);
|
||||
logger.info("checking page {}", pageIndex);
|
||||
textStripper.setStartPage(pageIndex + 1);
|
||||
textStripper.setEndPage(pageIndex + 1);
|
||||
String pageText = textStripper.getText(document);
|
||||
boolean hasText = !pageText.trim().isEmpty();
|
||||
|
||||
Boolean blank = true;
|
||||
boolean blank = true;
|
||||
if (hasText) {
|
||||
logger.info("page " + pageIndex + " has text, not blank");
|
||||
logger.info("page {} has text, not blank", pageIndex);
|
||||
blank = false;
|
||||
} else {
|
||||
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
||||
if (hasImages) {
|
||||
logger.info("page " + pageIndex + " has image, running blank detection");
|
||||
logger.info("page {} has image, running blank detection", pageIndex);
|
||||
// Render image and save as temp file
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);
|
||||
blank = isBlankImage(image, threshold, whitePercent, threshold);
|
||||
@@ -82,34 +83,57 @@ public class BlankPageController {
|
||||
}
|
||||
|
||||
if (blank) {
|
||||
logger.info("Skipping, Image was blank for page #" + pageIndex);
|
||||
logger.info("Skipping, Image was blank for page #{}", pageIndex);
|
||||
blankPages.add(page);
|
||||
} else {
|
||||
logger.info("page " + pageIndex + " has image which is not blank");
|
||||
pagesToKeepIndex.add(pageIndex);
|
||||
logger.info("page {} has image which is not blank", pageIndex);
|
||||
nonBlankPages.add(page);
|
||||
}
|
||||
|
||||
pageIndex++;
|
||||
}
|
||||
// Remove pages not present in pagesToKeepIndex
|
||||
List<Integer> pageIndices =
|
||||
IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
|
||||
Collections.reverse(pageIndices); // Reverse to prevent index shifting during removal
|
||||
for (Integer i : pageIndices) {
|
||||
if (!pagesToKeepIndex.contains(i)) {
|
||||
pages.remove(i);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ZipOutputStream zos = new ZipOutputStream(baos);
|
||||
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
if (!nonBlankPages.isEmpty()) {
|
||||
createZipEntry(zos, nonBlankPages, filename + "_nonBlankPages.pdf");
|
||||
} else {
|
||||
createZipEntry(zos, blankPages, filename + "_allBlankPages.pdf");
|
||||
}
|
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_blanksRemoved.pdf");
|
||||
if (!nonBlankPages.isEmpty() && !blankPages.isEmpty()) {
|
||||
createZipEntry(zos, blankPages, filename + "_blankPages.pdf");
|
||||
}
|
||||
|
||||
zos.close();
|
||||
|
||||
logger.info("Returning ZIP file: {}", filename + "_processed.zip");
|
||||
return WebResponseUtils.boasToWebResponse(
|
||||
baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("exception", e);
|
||||
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
} finally {
|
||||
if (document != null) document.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void createZipEntry(ZipOutputStream zos, List<PDPage> pages, String entryName)
|
||||
throws IOException {
|
||||
try (PDDocument document = new PDDocument()) {
|
||||
|
||||
for (PDPage page : pages) {
|
||||
document.addPage(page);
|
||||
}
|
||||
|
||||
ZipEntry zipEntry = new ZipEntry(entryName);
|
||||
zos.putNextEntry(zipEntry);
|
||||
document.save(zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ public class CompressController {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("gs");
|
||||
command.add("-sDEVICE=pdfwrite");
|
||||
command.add("-dCompatibilityLevel=1.4");
|
||||
command.add("-dCompatibilityLevel=1.5");
|
||||
|
||||
switch (optimizeLevel) {
|
||||
case 1:
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
@@ -47,16 +50,19 @@ public class ExtractImagesController {
|
||||
@Operation(
|
||||
summary = "Extract images from a PDF file",
|
||||
description =
|
||||
"This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||
"This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input: PDF Output: IMAGE/ZIP Type: SIMO")
|
||||
public ResponseEntity<byte[]> extractImages(@ModelAttribute PDFWithImageFormatRequest request)
|
||||
throws IOException {
|
||||
throws IOException, InterruptedException, ExecutionException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String format = request.getFormat();
|
||||
|
||||
System.out.println(
|
||||
System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
||||
System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format);
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
|
||||
// Determine if multithreading should be used based on PDF size or number of pages
|
||||
boolean useMultithreading = shouldUseMultithreading(file, document);
|
||||
|
||||
// Create ByteArrayOutputStream to write zip file to byte array
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@@ -66,71 +72,51 @@ public class ExtractImagesController {
|
||||
// Set compression level
|
||||
zos.setLevel(Deflater.BEST_COMPRESSION);
|
||||
|
||||
int imageIndex = 1;
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
int pageNum = 0;
|
||||
Set<Integer> processedImages = new HashSet<>();
|
||||
// Iterate over each page
|
||||
for (PDPage page : document.getPages()) {
|
||||
++pageNum;
|
||||
// Extract images from page
|
||||
for (COSName name : page.getResources().getXObjectNames()) {
|
||||
if (page.getResources().isImageXObject(name)) {
|
||||
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
|
||||
int imageHash = image.hashCode();
|
||||
if (processedImages.contains(imageHash)) {
|
||||
continue; // Skip already processed images
|
||||
}
|
||||
processedImages.add(imageHash);
|
||||
|
||||
// Convert image to desired format
|
||||
RenderedImage renderedImage = image.getImage();
|
||||
BufferedImage bufferedImage = null;
|
||||
if ("png".equalsIgnoreCase(format)) {
|
||||
bufferedImage =
|
||||
new BufferedImage(
|
||||
renderedImage.getWidth(),
|
||||
renderedImage.getHeight(),
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
} else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) {
|
||||
bufferedImage =
|
||||
new BufferedImage(
|
||||
renderedImage.getWidth(),
|
||||
renderedImage.getHeight(),
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
} else if ("gif".equalsIgnoreCase(format)) {
|
||||
bufferedImage =
|
||||
new BufferedImage(
|
||||
renderedImage.getWidth(),
|
||||
renderedImage.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED);
|
||||
}
|
||||
if (useMultithreading) {
|
||||
// Executor service to handle multithreading
|
||||
ExecutorService executor =
|
||||
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
Set<Future<Void>> futures = new HashSet<>();
|
||||
|
||||
// Write image to zip file
|
||||
String imageName =
|
||||
filename + "_" + imageIndex + " (Page " + pageNum + ")." + format;
|
||||
ZipEntry zipEntry = new ZipEntry(imageName);
|
||||
zos.putNextEntry(zipEntry);
|
||||
// Iterate over each page
|
||||
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
||||
PDPage page = document.getPage(pgNum);
|
||||
int pageNum = document.getPages().indexOf(page) + 1;
|
||||
// Submit a task for processing each page
|
||||
Future<Void> future =
|
||||
executor.submit(
|
||||
() -> {
|
||||
extractImagesFromPage(
|
||||
page, format, filename, pageNum, processedImages, zos);
|
||||
return null;
|
||||
});
|
||||
|
||||
Graphics2D g = bufferedImage.createGraphics();
|
||||
g.drawImage((Image) renderedImage, 0, 0, null);
|
||||
g.dispose();
|
||||
// Write image bytes to zip file
|
||||
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, format, imageBaos);
|
||||
zos.write(imageBaos.toByteArray());
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
zos.closeEntry();
|
||||
imageIndex++;
|
||||
}
|
||||
// Wait for all tasks to complete
|
||||
for (Future<Void> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
|
||||
// Close executor service
|
||||
executor.shutdown();
|
||||
} else {
|
||||
// Single-threaded extraction
|
||||
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
||||
PDPage page = document.getPage(pgNum);
|
||||
extractImagesFromPage(page, format, filename, pgNum + 1, processedImages, zos);
|
||||
}
|
||||
}
|
||||
|
||||
// Close ZipOutputStream and PDDocument
|
||||
zos.close();
|
||||
// Close PDDocument and ZipOutputStream
|
||||
document.close();
|
||||
zos.close();
|
||||
|
||||
// Create ByteArrayResource from byte array
|
||||
byte[] zipContents = baos.toByteArray();
|
||||
@@ -138,4 +124,69 @@ public class ExtractImagesController {
|
||||
return WebResponseUtils.boasToWebResponse(
|
||||
baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
private boolean shouldUseMultithreading(MultipartFile file, PDDocument document) {
|
||||
// Criteria: Use multithreading if file size > 10MB or number of pages > 20
|
||||
long fileSizeInMB = file.getSize() / (1024 * 1024);
|
||||
int numberOfPages = document.getPages().getCount();
|
||||
return fileSizeInMB > 10 || numberOfPages > 20;
|
||||
}
|
||||
|
||||
private void extractImagesFromPage(
|
||||
PDPage page,
|
||||
String format,
|
||||
String filename,
|
||||
int pageNum,
|
||||
Set<Integer> processedImages,
|
||||
ZipOutputStream zos)
|
||||
throws IOException {
|
||||
for (COSName name : page.getResources().getXObjectNames()) {
|
||||
if (page.getResources().isImageXObject(name)) {
|
||||
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
|
||||
int imageHash = image.hashCode();
|
||||
synchronized (processedImages) {
|
||||
if (processedImages.contains(imageHash)) {
|
||||
continue; // Skip already processed images
|
||||
}
|
||||
processedImages.add(imageHash);
|
||||
}
|
||||
|
||||
RenderedImage renderedImage = image.getImage();
|
||||
|
||||
// Convert to standard RGB colorspace if needed
|
||||
BufferedImage bufferedImage = convertToRGB(renderedImage, format);
|
||||
|
||||
// Write image to zip file
|
||||
String imageName = filename + "_" + imageHash + " (Page " + pageNum + ")." + format;
|
||||
synchronized (zos) {
|
||||
zos.putNextEntry(new ZipEntry(imageName));
|
||||
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, format, imageBaos);
|
||||
zos.write(imageBaos.toByteArray());
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage convertToRGB(RenderedImage renderedImage, String format) {
|
||||
int width = renderedImage.getWidth();
|
||||
int height = renderedImage.getHeight();
|
||||
BufferedImage rgbImage;
|
||||
|
||||
if ("png".equalsIgnoreCase(format)) {
|
||||
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
} else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) {
|
||||
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
} else if ("gif".equalsIgnoreCase(format)) {
|
||||
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED);
|
||||
} else {
|
||||
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
|
||||
Graphics2D g = rgbImage.createGraphics();
|
||||
g.drawImage((Image) renderedImage, 0, 0, null);
|
||||
g.dispose();
|
||||
return rgbImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -32,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.PDFText;
|
||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||
import stirling.software.SPDF.pdf.TextFinder;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -81,22 +75,9 @@ public class RedactController {
|
||||
}
|
||||
|
||||
if (convertPDFToImage) {
|
||||
PDDocument imageDocument = new PDDocument();
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
||||
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
|
||||
imageDocument.addPage(newPage);
|
||||
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
imageDocument, newPage, AppendMode.APPEND, true, true);
|
||||
contentStream.drawImage(pdImage, 0, 0);
|
||||
contentStream.close();
|
||||
}
|
||||
PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);
|
||||
document.close();
|
||||
document = imageDocument;
|
||||
document = convertedPdf;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@@ -36,6 +36,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -60,6 +61,7 @@ public class WatermarkController {
|
||||
float opacity = request.getOpacity();
|
||||
int widthSpacer = request.getWidthSpacer();
|
||||
int heightSpacer = request.getHeightSpacer();
|
||||
boolean convertPdfToImage = request.isConvertPDFToImage();
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||
@@ -104,6 +106,12 @@ public class WatermarkController {
|
||||
contentStream.close();
|
||||
}
|
||||
|
||||
if (convertPdfToImage) {
|
||||
PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);
|
||||
document.close();
|
||||
document = convertedPdf;
|
||||
}
|
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
||||
|
||||
@@ -24,14 +24,14 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
|
||||
@Controller
|
||||
@@ -43,6 +43,7 @@ public class AccountWebController {
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
return "redirect:/";
|
||||
}
|
||||
@@ -72,8 +73,13 @@ public class AccountWebController {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove any null keys/values from the providerList
|
||||
providerList
|
||||
.entrySet()
|
||||
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
||||
model.addAttribute("providerlist", providerList);
|
||||
|
||||
model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod());
|
||||
model.addAttribute(
|
||||
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import stirling.software.SPDF.config.security.database.DatabaseBackupHelper;
|
||||
import stirling.software.SPDF.utils.FileInfo;
|
||||
|
||||
@Controller
|
||||
@Tag(name = "Database Management", description = "Database management and security APIs")
|
||||
public class DatabaseWebController {
|
||||
|
||||
@Autowired private DatabaseBackupHelper databaseBackupHelper;
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/database")
|
||||
public String database(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
String error = request.getParameter("error");
|
||||
String confirmed = request.getParameter("infoMessage");
|
||||
|
||||
if (error != null) {
|
||||
model.addAttribute("error", error);
|
||||
} else if (confirmed != null) {
|
||||
model.addAttribute("infoMessage", confirmed);
|
||||
}
|
||||
|
||||
List<FileInfo> backupList = databaseBackupHelper.getBackupList();
|
||||
model.addAttribute("systemUpdate", backupList);
|
||||
|
||||
return "database";
|
||||
}
|
||||
}
|
||||
@@ -310,4 +310,11 @@ public class GeneralWebController {
|
||||
model.addAttribute("currentPage", "auto-split-pdf");
|
||||
return "auto-split-pdf";
|
||||
}
|
||||
|
||||
@GetMapping("/remove-image-pdf")
|
||||
@Hidden
|
||||
public String removeImagePdfForm(Model model) {
|
||||
model.addAttribute("currentPage", "remove-image-pdf");
|
||||
return "remove-image-pdf";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "")
|
||||
@@ -128,6 +132,15 @@ public class ApplicationProperties {
|
||||
private OAUTH2 oauth2;
|
||||
private int loginAttemptCount;
|
||||
private long loginResetTimeMinutes;
|
||||
private String loginMethod = "all";
|
||||
|
||||
public String getLoginMethod() {
|
||||
return loginMethod;
|
||||
}
|
||||
|
||||
public void setLoginMethod(String loginMethod) {
|
||||
this.loginMethod = loginMethod;
|
||||
}
|
||||
|
||||
public int getLoginAttemptCount() {
|
||||
return loginAttemptCount;
|
||||
@@ -187,6 +200,8 @@ public class ApplicationProperties {
|
||||
+ initialLogin
|
||||
+ ", csrfDisabled="
|
||||
+ csrfDisabled
|
||||
+ ", loginMethod="
|
||||
+ loginMethod
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@@ -345,6 +360,8 @@ public class ApplicationProperties {
|
||||
+ useAsUsername
|
||||
+ ", provider="
|
||||
+ provider
|
||||
+ ", client="
|
||||
+ client
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ "]";
|
||||
@@ -355,7 +372,7 @@ public class ApplicationProperties {
|
||||
private GithubProvider github = new GithubProvider();
|
||||
private KeycloakProvider keycloak = new KeycloakProvider();
|
||||
|
||||
public Provider get(String registrationId) throws Exception {
|
||||
public Provider get(String registrationId) throws UnsupportedProviderException {
|
||||
switch (registrationId.toLowerCase()) {
|
||||
case "google":
|
||||
return getGoogle();
|
||||
@@ -366,7 +383,8 @@ public class ApplicationProperties {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new Exception("Provider not supported, use custom setting.");
|
||||
throw new UnsupportedProviderException(
|
||||
"Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||
}
|
||||
|
||||
public GoogleProvider getGoogle() {
|
||||
@@ -407,311 +425,6 @@ public class ApplicationProperties {
|
||||
}
|
||||
}
|
||||
|
||||
public static class GoogleProvider extends Provider {
|
||||
|
||||
private static final String authorizationUri =
|
||||
"https://accounts.google.com/o/oauth2/v2/auth";
|
||||
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token";
|
||||
private static final String userInfoUri =
|
||||
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes = new ArrayList<>();
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Google [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "google";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientName() {
|
||||
return "Google";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class GithubProvider extends Provider {
|
||||
private static final String authorizationUri = "https://github.com/login/oauth/authorize";
|
||||
private static final String tokenUri = "https://github.com/login/oauth/access_token";
|
||||
private static final String userInfoUri = "https://api.github.com/user";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "login";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return new String();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes = new ArrayList<>();
|
||||
scopes.add("read:user");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GitHub [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "github";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientName() {
|
||||
return "GitHub";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeycloakProvider extends Provider {
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes = new ArrayList<>();
|
||||
scopes.add("profile");
|
||||
scopes.add("email");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Keycloak [issuer="
|
||||
+ issuer
|
||||
+ ", clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "keycloak";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientName() {
|
||||
return "Keycloak";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return isValid(this.getIssuer(), "issuer")
|
||||
&& isValid(this.getClientId(), "clientId")
|
||||
&& isValid(this.getClientSecret(), "clientSecret")
|
||||
&& isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class System {
|
||||
private String defaultLocale;
|
||||
private Boolean googlevisibility;
|
||||
|
||||
@@ -16,6 +16,7 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.Lob;
|
||||
import jakarta.persistence.MapKeyColumn;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
@@ -55,7 +56,8 @@ public class User {
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name = "setting_key")
|
||||
@Column(name = "setting_value")
|
||||
@Lob
|
||||
@Column(name = "setting_value", columnDefinition = "CLOB")
|
||||
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id"))
|
||||
private Map<String, String> settings = new HashMap<>(); // Key-value pairs of settings.
|
||||
|
||||
|
||||
@@ -44,4 +44,7 @@ public class AddWatermarkRequest extends PDFFile {
|
||||
|
||||
@Schema(description = "The height spacer between watermark elements", example = "50")
|
||||
private int heightSpacer;
|
||||
|
||||
@Schema(description = "Convert the redacted PDF to an image", defaultValue = "false")
|
||||
private boolean convertPDFToImage;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package stirling.software.SPDF.model.provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import stirling.software.SPDF.model.Provider;
|
||||
|
||||
public class GithubProvider extends Provider {
|
||||
|
||||
private static final String authorizationUri = "https://github.com/login/oauth/authorize";
|
||||
private static final String tokenUri = "https://github.com/login/oauth/access_token";
|
||||
private static final String userInfoUri = "https://api.github.com/user";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "login";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return new String();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes = new ArrayList<>();
|
||||
scopes.add("read:user");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GitHub [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "github";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientName() {
|
||||
return "GitHub";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package stirling.software.SPDF.model.provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import stirling.software.SPDF.model.Provider;
|
||||
|
||||
public class GoogleProvider extends Provider {
|
||||
|
||||
private static final String authorizationUri = "https://accounts.google.com/o/oauth2/v2/auth";
|
||||
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token";
|
||||
private static final String userInfoUri =
|
||||
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes = new ArrayList<>();
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Google [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "google";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientName() {
|
||||
return "Google";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package stirling.software.SPDF.model.provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import stirling.software.SPDF.model.Provider;
|
||||
|
||||
public class KeycloakProvider extends Provider {
|
||||
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes = new ArrayList<>();
|
||||
scopes.add("profile");
|
||||
scopes.add("email");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Keycloak [issuer="
|
||||
+ issuer
|
||||
+ ", clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "keycloak";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientName() {
|
||||
return "Keycloak";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return isValid(this.getIssuer(), "issuer")
|
||||
&& isValid(this.getClientId(), "clientId")
|
||||
&& isValid(this.getClientSecret(), "clientSecret")
|
||||
&& isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package stirling.software.SPDF.model.provider;
|
||||
|
||||
public class UnsupportedProviderException extends Exception {
|
||||
public UnsupportedProviderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ package stirling.software.SPDF.repository;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
|
||||
@Repository
|
||||
public interface AuthorityRepository extends JpaRepository<Authority, Long> {
|
||||
// Set<Authority> findByUsername(String username);
|
||||
Set<Authority> findByUser_Username(String username);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package stirling.software.SPDF.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import stirling.software.SPDF.model.PersistentLogin;
|
||||
|
||||
@Repository
|
||||
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {}
|
||||
|
||||
@@ -3,10 +3,12 @@ package stirling.software.SPDF.repository;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import stirling.software.SPDF.model.User;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, String> {
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByUsernameIgnoreCase(String username);
|
||||
|
||||
Optional<User> findByUsername(String username);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDResources;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** Service class responsible for removing image objects from a PDF document. */
|
||||
@Service
|
||||
public class PdfImageRemovalService {
|
||||
|
||||
/**
|
||||
* Removes all image objects from the provided PDF document.
|
||||
*
|
||||
* This method iterates over each page in the document and removes any image XObjects found
|
||||
* in the page's resources.
|
||||
*
|
||||
* @param document The PDF document from which images will be removed.
|
||||
* @return The modified PDF document with images removed.
|
||||
* @throws IOException If an error occurs while processing the PDF document.
|
||||
*/
|
||||
public PDDocument removeImagesFromPdf(PDDocument document) throws IOException {
|
||||
// Iterate over each page in the PDF document
|
||||
for (PDPage page : document.getPages()) {
|
||||
PDResources resources = page.getResources();
|
||||
// Iterate over all XObject names in the page's resources
|
||||
for (COSName name : resources.getXObjectNames()) {
|
||||
// Check if the XObject is an image
|
||||
if (resources.isImageXObject(name)) {
|
||||
// Remove the image XObject by setting it to null
|
||||
resources.put(name, (PDXObject) null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return document;
|
||||
}
|
||||
}
|
||||
50
src/main/java/stirling/software/SPDF/utils/FileInfo.java
Normal file
50
src/main/java/stirling/software/SPDF/utils/FileInfo.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class FileInfo {
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private LocalDateTime modificationDate;
|
||||
private long fileSize;
|
||||
private LocalDateTime creationDate;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// Converts the file path string to a Path object.
|
||||
public Path getFilePathAsPath() {
|
||||
return Paths.get(filePath);
|
||||
}
|
||||
|
||||
// Formats the file size into a human-readable string.
|
||||
public String getFormattedFileSize() {
|
||||
if (fileSize >= 1024 * 1024 * 1024) {
|
||||
return String.format("%.2f GB", fileSize / (1024.0 * 1024 * 1024));
|
||||
} else if (fileSize >= 1024 * 1024) {
|
||||
return String.format("%.2f MB", fileSize / (1024.0 * 1024));
|
||||
} else if (fileSize >= 1024) {
|
||||
return String.format("%.2f KB", fileSize / 1024.0);
|
||||
} else {
|
||||
return String.format("%d Bytes", fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Formats the modification date to a string.
|
||||
public String getFormattedModificationDate() {
|
||||
return modificationDate.format(DATE_FORMATTER);
|
||||
}
|
||||
|
||||
// Formats the creation date to a string.
|
||||
public String getFormattedCreationDate() {
|
||||
return creationDate.format(DATE_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -71,6 +73,21 @@ public class GeneralUtils {
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean isURLReachable(String urlStr) {
|
||||
try {
|
||||
URL url = new URL(urlStr);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
int responseCode = connection.getResponseCode();
|
||||
return (200 <= responseCode && responseCode <= 399);
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static File multipartToFile(MultipartFile multipart) throws IOException {
|
||||
@@ -95,19 +112,16 @@ public class GeneralUtils {
|
||||
sizeStr = sizeStr.replace(",", ".").replace(" ", "");
|
||||
try {
|
||||
if (sizeStr.endsWith("KB")) {
|
||||
return (long)
|
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
||||
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
||||
} else if (sizeStr.endsWith("MB")) {
|
||||
return (long)
|
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||
* 1024
|
||||
* 1024);
|
||||
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||
* 1024
|
||||
* 1024);
|
||||
} else if (sizeStr.endsWith("GB")) {
|
||||
return (long)
|
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||
* 1024
|
||||
* 1024
|
||||
* 1024);
|
||||
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||
* 1024
|
||||
* 1024
|
||||
* 1024);
|
||||
} else if (sizeStr.endsWith("B")) {
|
||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
||||
} else {
|
||||
@@ -170,13 +184,15 @@ public class GeneralUtils {
|
||||
|
||||
int n = 0;
|
||||
while (true) {
|
||||
// Replace 'n' with the current value of n, correctly handling numbers before 'n'
|
||||
// Replace 'n' with the current value of n, correctly handling numbers before
|
||||
// 'n'
|
||||
String sanitizedExpression = insertMultiplicationBeforeN(expression, n);
|
||||
Double result = evaluator.evaluate(sanitizedExpression);
|
||||
|
||||
// Check if the result is null or not within bounds
|
||||
if (result == null || result <= 0 || result.intValue() > maxValue) {
|
||||
if (n != 0) break;
|
||||
if (n != 0)
|
||||
break;
|
||||
} else {
|
||||
results.add(result.intValue());
|
||||
}
|
||||
|
||||
@@ -341,6 +341,30 @@ public class PdfUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given Pdf file to PDF-Image.
|
||||
*
|
||||
* @param document to be converted. Note: the caller is responsible for closing the document
|
||||
* @return converted document to PDF-Image
|
||||
* @throws IOException if conversion fails
|
||||
*/
|
||||
public static PDDocument convertPdfToPdfImage(PDDocument document) throws IOException {
|
||||
PDDocument imageDocument = new PDDocument();
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
||||
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
|
||||
imageDocument.addPage(newPage);
|
||||
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(imageDocument, newPage, AppendMode.APPEND, true, true);
|
||||
contentStream.drawImage(pdImage, 0, 0);
|
||||
contentStream.close();
|
||||
}
|
||||
return imageDocument;
|
||||
}
|
||||
|
||||
private static BufferedImage prepareImageForPdfToImage(
|
||||
int maxWidth, int height, String imageType) {
|
||||
BufferedImage combined;
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=تغيير دور المستخدم
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=إزالة
|
||||
#compare
|
||||
compare.title=يقارن
|
||||
compare.header=قارن ملفات PDF
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=المستند 1
|
||||
compare.document.2=المستند 2
|
||||
compare.submit=يقارن
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائي
|
||||
watermark.selectText.7=التعتيم (0٪ - 100٪):
|
||||
watermark.selectText.8=Watermark Type:
|
||||
watermark.selectText.9=Watermark Image:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=إضافة علامة مائية
|
||||
watermark.type.1=نص
|
||||
watermark.type.2=صورة
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Съхранете потребителя
|
||||
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
||||
adminUserSettings.authenticated=Удостоверен
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Книга към PDF
|
||||
home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
|
||||
BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Премахване
|
||||
#compare
|
||||
compare.title=Сравнявай
|
||||
compare.header=Сравнявай PDF-и
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Документ 1
|
||||
compare.document.2=Документ 2
|
||||
compare.submit=Сравнявай
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Четно-нечетно разделяне
|
||||
pdfOrganiser.mode.7=Премахни първо
|
||||
pdfOrganiser.mode.8=Премахване на последния
|
||||
pdfOrganiser.mode.9=Премахване на първия и последния
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=дължинаSpacer (Разстояние между в
|
||||
watermark.selectText.7=Непрозрачност (0% - 100%):
|
||||
watermark.selectText.8=Тип воден знак:
|
||||
watermark.selectText.9=Изображение за воден знак:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Добавяне на воден знак
|
||||
watermark.type.1=Текст
|
||||
watermark.type.2=Изображение
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Копиране на проследяване на стека
|
||||
error.githubSubmit=GitHub - Изпратете запитване
|
||||
error.discordSubmit=Discord - Изпратете запитване за поддръжка
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Desar Usuari
|
||||
adminUserSettings.changeUserRole=Canvia el rol de l'usuari
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remove
|
||||
#compare
|
||||
compare.title=Comparar
|
||||
compare.header=Compara PDF
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Document 1
|
||||
compare.document.2=Document 2
|
||||
compare.submit=Comparar
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=separació d'alçada (Espai vertical entre cada Marca d'A
|
||||
watermark.selectText.7=Opacitat (0% - 100%):
|
||||
watermark.selectText.8=Watermark Type:
|
||||
watermark.selectText.9=Watermark Image:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Afegir Marca d'Aigua
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Uložit Uživatele
|
||||
adminUserSettings.changeUserRole=Zmenit Roli Uživatele
|
||||
adminUserSettings.authenticated=Ověřeno
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Kniha na PDF
|
||||
home.BookToPDF.desc=Převádí formáty knih/komiksů do PDF pomocí calibre
|
||||
BookToPDF.tags=Kniha,Komiks,Calibre,Konvertovat,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Odebrat
|
||||
#compare
|
||||
compare.title=Porovnat
|
||||
compare.header=Porovnat PDF
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Dokument 1
|
||||
compare.document.2=Dokument 2
|
||||
compare.submit=Porovnat
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Liché-Sudé rozdělení
|
||||
pdfOrganiser.mode.7=Odstranit první
|
||||
pdfOrganiser.mode.8=Odstranit poslední
|
||||
pdfOrganiser.mode.9=Odstranit první a poslední
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(např. 1,3,2 nebo 4-8,2,10-12 nebo 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Výška mezery (Mezera mezi každým vodoznakem svisle):
|
||||
watermark.selectText.7=Průhlednost (0% - 100%):
|
||||
watermark.selectText.8=Typ vodoznaku:
|
||||
watermark.selectText.9=Obrázek vodoznaku:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Přidat vodoznak
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Obrázek
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Kopírovat stopu zásobníku
|
||||
error.githubSubmit=GitHub - Odeslat požadavek
|
||||
error.discordSubmit=Discord - Odeslat příspěvek podpory
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
1138
src/main/resources/messages_da_DK.properties
Normal file
1138
src/main/resources/messages_da_DK.properties
Normal file
File diff suppressed because it is too large
Load Diff
@@ -86,7 +86,7 @@ pipeline.defaultOption=Benutzerdefiniert
|
||||
pipeline.submitButton=Speichern
|
||||
pipeline.help=Hilfe für Pipeline
|
||||
pipeline.scanHelp=Hilfe zum Ordnerscan
|
||||
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||
pipeline.deletePrompt=Möchten Sie die Pipeline wirklich löschen?
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Benutzer speichern
|
||||
adminUserSettings.changeUserRole=Benutzerrolle ändern
|
||||
adminUserSettings.authenticated=Authentifiziert
|
||||
|
||||
|
||||
database.title=Datenbank Import/Export
|
||||
database.header=Datenbank Import/Export
|
||||
database.fileName=Dateiname
|
||||
database.creationDate=Erstellungsdatum
|
||||
database.fileSize=Dateigröße
|
||||
database.deleteBackupFile=Sicherungsdatei löschen
|
||||
database.importBackupFile=Sicherungsdatei importieren
|
||||
database.downloadBackupFile=Sicherungsdatei herunterladen
|
||||
database.info_1=Beim Importieren der Daten ist es von größter Bedeutung, die korrekte Struktur zu gewährleisten. Wenn Sie nicht sicher sind, was Sie tun, suchen Sie Rat und Unterstützung von einem Fachmann. Ein Fehler in der Struktur kann zu Fehlfunktionen der Anwendung führen, bis hin zur vollständigen Nicht-Lauffähigkeit der Anwendung.
|
||||
database.info_2=Der Dateiname spielt beim Hochladen keine Rolle. Dieser wird nachträglich in das Format backup_user_yyyyMMddHHmm.sql geändert, um eine einheitliche Benennung zu gewährleisten.
|
||||
database.submit=Sicherungsdatei importieren
|
||||
database.importIntoDatabaseSuccessed=Import in die Datenbank erfolgreich
|
||||
database.fileNotFound=Datei nicht gefunden
|
||||
database.fileNullOrEmpty=Datei darf nicht null oder leer sein
|
||||
database.failedImportFile=Dateiimport fehlgeschlagen
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Buch als PDF
|
||||
home.BookToPDF.desc=Konvertiert Buch-/Comic-Formate mithilfe von Calibre in PDF
|
||||
BookToPDF.tags=buch,comic,calibre,convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Entfernen
|
||||
#compare
|
||||
compare.title=Vergleichen
|
||||
compare.header=PDFs vergleichen
|
||||
compare.highlightColor.1=Highlight-Farbe 1:
|
||||
compare.highlightColor.2=Highlight-Farbe 2:
|
||||
compare.document.1=Dokument 1
|
||||
compare.document.2=Dokument 2
|
||||
compare.submit=Vergleichen
|
||||
@@ -926,6 +949,7 @@ watermark.selectText.6=höheSpacer (vertikaler Abstand zwischen den einzelnen Wa
|
||||
watermark.selectText.7=Deckkraft (0% - 100 %):
|
||||
watermark.selectText.8=Wasserzeichen Typ:
|
||||
watermark.selectText.9=Wasserzeichen-Bild:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Wasserzeichen hinzufügen
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Bild
|
||||
@@ -1084,13 +1108,13 @@ licenses.version=Version
|
||||
licenses.license=Lizenz
|
||||
|
||||
#survey
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
survey.dontShowAgain=Don't show again
|
||||
survey.nav=Umfrage
|
||||
survey.title=Stirling-PDF-Umfrage
|
||||
survey.description=Stirling-PDF hat kein Tracking, daher möchten wir von unseren Benutzern hören, wie wir Stirling-PDF verbessern können!
|
||||
survey.please=Bitte nehmen Sie an unserer Umfrage teil!
|
||||
survey.disabled=(Das Umfrage-Popup wird in folgenden Updates deaktiviert, ist aber am Fuß der Seite verfügbar.)
|
||||
survey.button=Umfrage durchführen
|
||||
survey.dontShowAgain=Nicht mehr anzeigen
|
||||
|
||||
|
||||
#error
|
||||
@@ -1106,3 +1130,9 @@ error.copyStack=Stack-Trace kopieren
|
||||
error.githubSubmit=GitHub - Ein Ticket einreichen
|
||||
error.discordSubmit=Discord - Unterstützungsbeitrag einreichen
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Αποθήκευση Χρήστη
|
||||
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book σε PDF
|
||||
home.BookToPDF.desc=Μετατρέπει τις μορφές Books/Comics σε PDF χρησιμοποιώντας calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Κατάργηση
|
||||
#compare
|
||||
compare.title=Σύγκριση
|
||||
compare.header=Σύγκριση PDFs
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Έγγραφο 1
|
||||
compare.document.2=Έγγραφο 2
|
||||
compare.submit=Σύγκριση
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Διαίρεση Μονού-Ζυγού
|
||||
pdfOrganiser.mode.7=Αφαίρεση Πρώτου
|
||||
pdfOrganiser.mode.8=Αφαίρεση Τελευταίου
|
||||
pdfOrganiser.mode.9=Αφαίρεση Πρώτου και Τελευταίου
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(π.χ. 1,3,2 ή 4-8,2,10-12 ή 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=heightSpacer (Κενό μεταξύ κάθε υδατογ
|
||||
watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
|
||||
watermark.selectText.8=Τύπος Υδατογραφήματος:
|
||||
watermark.selectText.9=Εικόνα Υδατογραφήματος:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Προσθήκη Υδατογραφήματος
|
||||
watermark.type.1=Κείμενο
|
||||
watermark.type.2=Εικόνα
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Αντιγραφή Stack Trace
|
||||
error.githubSubmit=GitHub - Υποβάλετε ένα ticket
|
||||
error.discordSubmit=Discord - Υποβάλετε ένα Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Change User's Role
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed to import file
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remove
|
||||
#compare
|
||||
compare.title=Compare
|
||||
compare.header=Compare PDFs
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Document 1
|
||||
compare.document.2=Document 2
|
||||
compare.submit=Compare
|
||||
@@ -926,6 +949,7 @@ watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
||||
watermark.selectText.7=Opacity (0% - 100%):
|
||||
watermark.selectText.8=Watermark Type:
|
||||
watermark.selectText.9=Watermark Image:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Add Watermark
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1106,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Change User's Role
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remove
|
||||
#compare
|
||||
compare.title=Compare
|
||||
compare.header=Compare PDFs
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Document 1
|
||||
compare.document.2=Document 2
|
||||
compare.submit=Compare
|
||||
@@ -926,6 +949,7 @@ watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
||||
watermark.selectText.7=Opacity (0% - 100%):
|
||||
watermark.selectText.8=Watermark Type:
|
||||
watermark.selectText.9=Watermark Image:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Add Watermark
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1106,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -60,11 +60,11 @@ deleteCurrentUserMessage=No puede eliminar el usuario que tiene la sesión actua
|
||||
deleteUsernameExistsMessage=El usuario no existe y no puede eliminarse.
|
||||
downgradeCurrentUserMessage=No se puede degradar el rol del usuario actual
|
||||
downgradeCurrentUserLongMessage=No se puede degradar el rol del usuario actual. Por lo tanto, el usuario actual no se mostrará.
|
||||
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
||||
userAlreadyExistsWebMessage=The user already exists as an web user.
|
||||
userAlreadyExistsOAuthMessage=La usuario ya existe como usuario de OAuth2.
|
||||
userAlreadyExistsWebMessage=El usuario ya existe como usuario web.
|
||||
error=Error
|
||||
oops=Ups!
|
||||
help=Help
|
||||
help=Ayuda
|
||||
goHomepage=Ir a la página principal
|
||||
joinDiscord=Únase a nuestro servidor Discord
|
||||
seeDockerHub=Ver Docker Hub
|
||||
@@ -86,7 +86,7 @@ pipeline.defaultOption=Personalizar
|
||||
pipeline.submitButton=Enviar
|
||||
pipeline.help=Ayuda de Canalización
|
||||
pipeline.scanHelp=Ayuda de escaneado de carpetas
|
||||
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||
pipeline.deletePrompt=¿Estás segura de que quieres eliminar la canalización?
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
@@ -107,18 +107,18 @@ pipelineOptions.validateButton=Validar
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.favorite=Favoritos
|
||||
navbar.darkmode=Modo oscuro
|
||||
navbar.language=Languages
|
||||
navbar.language=Idiomas
|
||||
navbar.settings=Configuración
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.allTools=Herramientas
|
||||
navbar.multiTool=Multi herramientas
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.sections.convertTo=Convertir a PDF
|
||||
navbar.sections.convertFrom=Convertir desde PDF
|
||||
navbar.sections.security=Señalización y seguridad
|
||||
navbar.sections.advance=Avanzado
|
||||
navbar.sections.edit=Ver y Editar
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -175,8 +175,8 @@ adminUserSettings.header=Configuración de control de usuario administrador
|
||||
adminUserSettings.admin=Administrador
|
||||
adminUserSettings.user=Usuario
|
||||
adminUserSettings.addUser=Añadir Nuevo Usuario
|
||||
adminUserSettings.deleteUser=Delete User
|
||||
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||
adminUserSettings.deleteUser=Eliminar Usuario
|
||||
adminUserSettings.confirmDeleteUser=¿Se debe eliminar al usuario?
|
||||
adminUserSettings.usernameInfo=El nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
|
||||
adminUserSettings.roles=Roles
|
||||
adminUserSettings.role=Rol
|
||||
@@ -189,7 +189,24 @@ adminUserSettings.internalApiUser=Usuario interno de API
|
||||
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
|
||||
adminUserSettings.submit=Guardar Usuario
|
||||
adminUserSettings.changeUserRole=Cambiar rol de usuario
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
adminUserSettings.authenticated=Autenticado
|
||||
|
||||
|
||||
database.title=Base de Datos Importar/Exportar
|
||||
database.header=Base de Datos Importar/Exportar
|
||||
database.fileName=Nombre de Archivo
|
||||
database.creationDate=Fecha de creación
|
||||
database.fileSize=Tamaño de archivo
|
||||
database.deleteBackupFile=Eliminar archivo de copia de seguridad
|
||||
database.importBackupFile=Importar archivo de copia de seguridad
|
||||
database.downloadBackupFile=Descargar archivo de copia de seguridad
|
||||
database.info_1=Al importar datos, es fundamental garantizar la estructura correcta. Si no está seguro de lo que está haciendo, busque consejo y apoyo de un profesional. Un error en la estructura puede causar un mal funcionamiento de la aplicación, incluyendo la imposibilidad total de ejecutar la aplicación.
|
||||
database.info_2=El nombre del archivo no importa al cargarlo. Posteriormente se le cambiará el nombre para que siga el formato backup_user_yyyyMMddHHmm.sql, lo que garantiza una convención de nomenclatura coherente.
|
||||
database.submit=Importar Backup
|
||||
database.importIntoDatabaseSuccessed=Importación a la base de datos ha sido exitosa
|
||||
database.fileNotFound=Archivo no encontrado
|
||||
database.fileNullOrEmpty=El archivo no debe ser nulo o vacío.
|
||||
database.failedImportFile=Archivo de importación fallido
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -336,8 +353,8 @@ home.certSign.title=Firmar con certificado
|
||||
home.certSign.desc=Firmar un PDF con un Certificado/Clave (PEM/P12)
|
||||
certSign.tags=autentificar,PEM,P12,oficial,encriptar
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
home.removeCertSign.title=Quitar signo de certificado
|
||||
home.removeCertSign.desc=Eliminar firma de certificado de PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Diseño de varias páginas
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Libro a PDF
|
||||
home.BookToPDF.desc=Convierte formatos de Libro/Cómic a PDF usando Calibre
|
||||
BookToPDF.tags=Libro,Cómic,Calibre,Convertir,manga,Amazon,Kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -459,13 +480,13 @@ login.invalid=Nombre de usuario o contraseña erróneos.
|
||||
login.locked=Su cuenta se ha bloqueado.
|
||||
login.signinTitle=Por favor, inicie sesión
|
||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
||||
login.oauth2AutoCreateDisabled=Usuario DE creación automática de OAUTH2 DESACTIVADO
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO
|
||||
login.oauth2RequestNotFound=Solicitud de autorización no encontrada
|
||||
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.oauth2InvalidTokenResponse=Respuesta de token no válida
|
||||
login.oauth2InvalidIdToken=Token de identificación no válido
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -664,10 +685,10 @@ certSign.submit=Firmar PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
removeCertSign.title=Eliminar firma del certificado
|
||||
removeCertSign.header=Quitar el certificado digital del PDF
|
||||
removeCertSign.selectPDF=Seleccione un archivo PDF:
|
||||
removeCertSign.submit=Eliminar firma
|
||||
|
||||
|
||||
#removeBlanks
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Eliminar
|
||||
#compare
|
||||
compare.title=Comparar
|
||||
compare.header=Comparar archivos PDF
|
||||
compare.highlightColor.1=Color resaltado 1:
|
||||
compare.highlightColor.2=Color resaltado 2:
|
||||
compare.document.1=Documento 1
|
||||
compare.document.2=Documento 2
|
||||
compare.submit=Comparar
|
||||
@@ -725,7 +748,7 @@ repair.submit=Reparar
|
||||
#flatten
|
||||
flatten.title=Aplanar
|
||||
flatten.header=Acoplar archivos PDF
|
||||
flatten.flattenOnlyForms=Flatten only forms
|
||||
flatten.flattenOnlyForms=Aplanar sólo formularios
|
||||
flatten.submit=Aplanar
|
||||
|
||||
|
||||
@@ -773,7 +796,7 @@ extractImages.submit=Extraer
|
||||
fileToPDF.title=Archivo a PDF
|
||||
fileToPDF.header=Convertir cualquier archivo a PDF
|
||||
fileToPDF.credit=Este servicio usa LibreOffice y Unoconv para la conversión de archivos
|
||||
fileToPDF.supportedFileTypesInfo=Supported File types
|
||||
fileToPDF.supportedFileTypesInfo=Tipos de archivos admitidos
|
||||
fileToPDF.supportedFileTypes=Los tipos de archivo soportados deben incluir los indicados a continuación; sin embargo, para una completa y acutualizada lista de formatos soportados, por favor consulte la documentación de LibreOffice
|
||||
fileToPDF.submit=Convertir a PDF
|
||||
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=División par-impar
|
||||
pdfOrganiser.mode.7=Quitar primera
|
||||
pdfOrganiser.mode.8=Quitar última
|
||||
pdfOrganiser.mode.9=Quitar primera y última
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(por ej., 1,3,2 o 4-8,2,10-12 o 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Alto (Espacio entre cada marca de agua verticalmente):
|
||||
watermark.selectText.7=Opacidad (0% - 100%):
|
||||
watermark.selectText.8=Tipo de marca de agua:
|
||||
watermark.selectText.9=Imagen de marca de agua:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Añadir marca de agua
|
||||
watermark.type.1=Texto
|
||||
watermark.type.2=Imagen
|
||||
@@ -980,8 +1005,8 @@ pdfToPDFA.header=PDF a PDF/A
|
||||
pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
pdfToPDFA.outputFormat=Formato de salida
|
||||
pdfToPDFA.pdfWithDigitalSignature=El PDF contiene una firma digital. Esto se eliminará en el siguiente paso.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
@@ -1083,13 +1108,13 @@ licenses.version=Versión
|
||||
licenses.license=Licencia
|
||||
|
||||
#survey
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
survey.dontShowAgain=Don't show again
|
||||
survey.nav=Encuesta
|
||||
survey.title=Encuesta Stirling-PDF
|
||||
survey.description=Stirling-PDF no tiene seguimiento, por lo que queremos escuchar a nuestros usuarios para mejorar Stirling-PDF.
|
||||
survey.please=¡Considere realizar nuestra encuesta!
|
||||
survey.disabled=(La ventana emergente de la encuesta se desactivará en las siguientes actualizaciones, pero estará disponible al pie de la página.)
|
||||
survey.button=Realizar encuesta
|
||||
survey.dontShowAgain=No volver a mostrar
|
||||
|
||||
|
||||
#error
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Mostrar seguimiento de pila
|
||||
error.githubSubmit=GitHub - Enviar un ticket
|
||||
error.discordSubmit=Discord - Enviar mensaje de soporte
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Gorde Erabiltzailea
|
||||
adminUserSettings.changeUserRole=Erabiltzailearen rola aldatu
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remove
|
||||
#compare
|
||||
compare.title=Konparatu
|
||||
compare.header=Konparatu PDF fitxategiak
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=1. dokumentua
|
||||
compare.document.2=2. dokumentua
|
||||
compare.submit=Konparatu
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Altuera (ur-marka bakoitzaren arteko espazioa bertikalean
|
||||
watermark.selectText.7=Opakutasuna (0% - 100%):
|
||||
watermark.selectText.8=Watermark Type:
|
||||
watermark.selectText.9=Watermark Image:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Gehitu ur-marka
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Ajouter
|
||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
||||
adminUserSettings.authenticated=Authentifié
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=eBook vers PDF
|
||||
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Supprimer
|
||||
#compare
|
||||
compare.title=Comparer
|
||||
compare.header=Comparer
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Document 1
|
||||
compare.document.2=Document 2
|
||||
compare.submit=Comparer
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Partage impair-pair
|
||||
pdfOrganiser.mode.7=Supprimer le premier
|
||||
pdfOrganiser.mode.8=Supprimer le dernier
|
||||
pdfOrganiser.mode.9=Supprimer le premier et le dernier
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=heightSpacer (espace entre chaque filigrane verticalement
|
||||
watermark.selectText.7=Opacité (de 0% à 100%)
|
||||
watermark.selectText.8=Type de filigrane
|
||||
watermark.selectText.9=Image du filigrane
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Ajouter un filigrane
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copier la Stack Trace
|
||||
error.githubSubmit=GitHub - Créer un ticket
|
||||
error.discordSubmit=Discord - Poster un message de demande d’assistance
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
1138
src/main/resources/messages_ga_IE.properties
Normal file
1138
src/main/resources/messages_ga_IE.properties
Normal file
File diff suppressed because it is too large
Load Diff
@@ -191,6 +191,23 @@ adminUserSettings.submit=उपयोगकर्ता को सहेजे
|
||||
adminUserSettings.changeUserRole=यूज़र की भूमिका बदलें
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=हटाएं
|
||||
#compare
|
||||
compare.title=तुलना करें
|
||||
compare.header=पीडीएफ़ तुलना करें
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=दस्तावेज़ 1
|
||||
compare.document.2=दस्तावेज़ 2
|
||||
compare.submit=तुलना करें
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=ऊंचाई स्पेसर (प्रत्ये
|
||||
watermark.selectText.7=अपारदर्शिता (0% - 100%):
|
||||
watermark.selectText.8=वॉटरमार्क प्रकार:
|
||||
watermark.selectText.9=वॉटरमार्क छवि:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=वॉटरमार्क जोड़ें
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Spremi korisnika
|
||||
adminUserSettings.changeUserRole=Promijenite korisničku ulogu
|
||||
adminUserSettings.authenticated=Autentificirano
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book u PDF
|
||||
home.BookToPDF.desc=Pretvara format knjige/stripa u PDF format pomoću calibre
|
||||
BookToPDF.tags=Knjiga,Strip,Calibre,Pretvori,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Ukloni
|
||||
#compare
|
||||
compare.title=Uporedite
|
||||
compare.header=Usporedite PDF-ove
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Dokument 1
|
||||
compare.document.2=Dokument 2
|
||||
compare.submit=Uporedi
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Par-Nepar Podjela
|
||||
pdfOrganiser.mode.7=Ukloni Prvu
|
||||
pdfOrganiser.mode.8=Ukloni Zadnju
|
||||
pdfOrganiser.mode.9=Ukloni Prvu i Zadnju
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(npr. 1,3,2 ili 4-8,2,10-12 ili 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Visina razmaka (Razmak između svakog vodenog žiga okomi
|
||||
watermark.selectText.7=Neprozirnost (0% - 100%):
|
||||
watermark.selectText.8=Vrsta vodenog žiga:
|
||||
watermark.selectText.9=Slika vodenog žiga:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Dodaj vodeni žig
|
||||
watermark.type.1=Tekst
|
||||
watermark.type.2=Slika
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Kopiraj Stack Trace
|
||||
error.githubSubmit=GitHub - Pošaljite ticket
|
||||
error.discordSubmit=Discord - Pošalji objavu podrške
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Felhasználó mentése
|
||||
adminUserSettings.changeUserRole=Felhasználó szerepkörének módosítása
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remove
|
||||
#compare
|
||||
compare.title=Összehasonlítás
|
||||
compare.header=PDF-ek összehasonlítása
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Dokumentum 1
|
||||
compare.document.2=Dokumentum 2
|
||||
compare.submit=Összehasonlítás
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=heightSpacer (Hely a vízjelek között függőlegesen):
|
||||
watermark.selectText.7=Átlátszóság (0% - 100%):
|
||||
watermark.selectText.8=Vízjel típusa:
|
||||
watermark.selectText.9=Vízjel képe:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Vízjel hozzáadása
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Simpan Pengguna
|
||||
adminUserSettings.changeUserRole=Ubah Peran Pengguna
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Hapus
|
||||
#compare
|
||||
compare.title=Bandingkan
|
||||
compare.header=Bandingkan PDF
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Dokumen 1
|
||||
compare.document.2=Dokumen 2
|
||||
compare.submit=Bandingkan
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=heightSpacer (Spasi diantara setiap watermark vertikal):
|
||||
watermark.selectText.7=Opacity (0% - 100%):
|
||||
watermark.selectText.8=Tipe Watermark:
|
||||
watermark.selectText.9=Gambar Watermark:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Tambahkan Watermark
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -35,7 +35,7 @@ sizes.large=Largo
|
||||
sizes.x-large=Extra-Large
|
||||
error.pdfPassword=Il documento PDF è protetto da password e la password non è stata fornita oppure non era corretta
|
||||
delete=Elimina
|
||||
username=Username
|
||||
username=Nome utente
|
||||
password=Password
|
||||
welcome=Benvenuto
|
||||
property=Proprietà
|
||||
@@ -87,6 +87,7 @@ pipeline.submitButton=Invia
|
||||
pipeline.help=Aiuto sulla pipeline
|
||||
pipeline.scanHelp=Aiuto per la scansione delle cartelle
|
||||
pipeline.deletePrompt=Sei sicuro di voler eliminare la pipeline?
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
######################
|
||||
@@ -115,7 +116,7 @@ navbar.multiTool=Strumenti multipli
|
||||
navbar.sections.organize=Organizza
|
||||
navbar.sections.convertTo=Converti in PDF
|
||||
navbar.sections.convertFrom=Converti da PDF
|
||||
navbar.sections.security=Firma Firma & Sicurezza
|
||||
navbar.sections.security=Firma & Sicurezza
|
||||
navbar.sections.advance=Avanzate
|
||||
navbar.sections.edit=Visualizza & Modifica
|
||||
|
||||
@@ -190,6 +191,23 @@ adminUserSettings.submit=Salva utente
|
||||
adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
|
||||
adminUserSettings.authenticated=Autenticato
|
||||
|
||||
|
||||
database.title=Importazione/Esportazione database
|
||||
database.header=Importazione/esportazione database
|
||||
database.fileName=Nome file
|
||||
database.creationDate=Data di creazione
|
||||
database.fileSize=Dimensione
|
||||
database.deleteBackupFile=Elimina file di backup
|
||||
database.importBackupFile=Importa file di backup
|
||||
database.downloadBackupFile=Scarica il file di backup
|
||||
database.info_1=Quando si importano i dati, è fondamentale garantire la struttura corretta. Se non sei sicuro di quello che stai facendo, chiedi consiglio e supporto a un professionista. Un errore nella struttura può causare malfunzionamenti dell'applicazione, fino alla completa impossibilità di eseguire l'applicazione.
|
||||
database.info_2=Il nome del file non ha importanza durante il caricamento. Verrà rinominato in seguito per seguire il formato backup_user__yyyyMMddHHmm.sql,garantendo una convenzione di denominazione coerente.
|
||||
database.submit=Importa Backup
|
||||
database.importIntoDatabaseSuccessed=L'importazione nel database è avvenuta con successo
|
||||
database.fileNotFound=File non trovato
|
||||
database.fileNullOrEmpty=Il file non deve essere nullo o vuoto
|
||||
database.failedImportFile=Importazione file non riuscita
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -233,7 +251,7 @@ pdfOrganiser.tags=duplex,pari,dispari,ordinamento,spostamento
|
||||
|
||||
home.addImage.title=Aggiungi Immagine
|
||||
home.addImage.desc=Aggiungi un'immagine in un punto specifico del PDF (Lavori in corso)
|
||||
addImage.tags=img,jpg,immagine,photo
|
||||
addImage.tags=img,jpg,immagine,foto
|
||||
|
||||
home.watermark.title=Aggiungi Filigrana
|
||||
home.watermark.desc=Aggiungi una filigrana al tuo PDF.
|
||||
@@ -443,6 +461,10 @@ home.BookToPDF.title=Libro in PDF
|
||||
home.BookToPDF.desc=Converte i formati di libri/fumetti in PDF utilizzando Calibre
|
||||
BookToPDF.tags=Libro,fumetto,calibre,conversione,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -688,6 +710,8 @@ removeAnnotations.submit=Rimuovi
|
||||
#compare
|
||||
compare.title=Compara
|
||||
compare.header=Compara PDF
|
||||
compare.highlightColor.1=Evidenzia colore 1:
|
||||
compare.highlightColor.2=Evidenzia colore 2:
|
||||
compare.document.1=Documento 1
|
||||
compare.document.2=Documento 2
|
||||
compare.submit=Compara
|
||||
@@ -802,7 +826,7 @@ merge.title=Unisci
|
||||
merge.header=Unisci 2 o più PDF
|
||||
merge.sortByName=Ordina per nome
|
||||
merge.sortByDate=Ordina per data
|
||||
merge.removeCertSign=Remove digital signature in the merged file?
|
||||
merge.removeCertSign=Rimuovere la firma digitale nel file unito?
|
||||
merge.submit=Unisci
|
||||
|
||||
|
||||
@@ -820,6 +844,7 @@ pdfOrganiser.mode.6=Divisione pari-dispari
|
||||
pdfOrganiser.mode.7=Rimuovi prima
|
||||
pdfOrganiser.mode.8=Rimuovi ultima
|
||||
pdfOrganiser.mode.9=Rimuovi la prima e l'ultima
|
||||
pdfOrganiser.mode.10=Unione pari-dispari
|
||||
pdfOrganiser.placeholder=(ad es. 1,3,2 o 4-8,2,10-12 o 2n-1)
|
||||
|
||||
|
||||
@@ -924,6 +949,7 @@ watermark.selectText.6=spazio verticale (tra ogni filigrana):
|
||||
watermark.selectText.7=Opacità (0% - 100%):
|
||||
watermark.selectText.8=Tipo di filigrana:
|
||||
watermark.selectText.9=Immagine filigrana:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Aggiungi Filigrana
|
||||
watermark.type.1=Testo
|
||||
watermark.type.2=Immagine
|
||||
@@ -1104,3 +1130,9 @@ error.copyStack=Copia traccia dello stack
|
||||
error.githubSubmit=GitHub: invia un ticket
|
||||
error.discordSubmit=Discord: invia post di supporto
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Rimuovere immagine
|
||||
removeImage.header=Rimuovi immagine
|
||||
removeImage.removeImage=Rimuovi immagine
|
||||
removeImage.submit=Rimuovi immagine
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=ユーザーの保存
|
||||
adminUserSettings.changeUserRole=ユーザーの役割を変更する
|
||||
adminUserSettings.authenticated=認証済
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=PDFを書籍に変換
|
||||
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=削除
|
||||
#compare
|
||||
compare.title=比較
|
||||
compare.header=PDFの比較
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=ドキュメント 1
|
||||
compare.document.2=ドキュメント 2
|
||||
compare.submit=比較
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=奇数-偶数分割
|
||||
pdfOrganiser.mode.7=最初に削除
|
||||
pdfOrganiser.mode.8=最後を削除
|
||||
pdfOrganiser.mode.9=最初と最後を削除
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=高さスペース (各透かし間の垂直方向のス
|
||||
watermark.selectText.7=不透明度 (0% - 100%):
|
||||
watermark.selectText.8=透かしの種類:
|
||||
watermark.selectText.9=透かしの画像:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=透かしを追加
|
||||
watermark.type.1=テキスト
|
||||
watermark.type.2=画像
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=スタックトレースをコピー
|
||||
error.githubSubmit=GitHub - チケットを提出
|
||||
error.discordSubmit=Discord - サポート投稿を提出
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=사용자 저장
|
||||
adminUserSettings.changeUserRole=사용자의 역할 변경
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=책을 PDF로
|
||||
home.BookToPDF.desc=구경을 사용하여 책/만화 형식을 PDF로 변환
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=제거하다
|
||||
#compare
|
||||
compare.title=비교
|
||||
compare.header=PDF 문서 비교
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=문서 1
|
||||
compare.document.2=문서 2
|
||||
compare.submit=비교
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=홀수-짝수 분할
|
||||
pdfOrganiser.mode.7=첫 번째 항목 삭제
|
||||
pdfOrganiser.mode.8=마지막 항목 제거
|
||||
pdfOrganiser.mode.9=첫 번째와 마지막 제거
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(예: 1,3,2 또는 4-8,2,10-12 또는 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=세로 간격 (각 워터마크 사이의 세로 공간):
|
||||
watermark.selectText.7=투명도 (0% - 100%):
|
||||
watermark.selectText.8=워터마크 유형:
|
||||
watermark.selectText.9=워터마크 이미지:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=워터마크 추가
|
||||
watermark.type.1=텍스트
|
||||
watermark.type.2=이미지
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=스택 추적 복사
|
||||
error.githubSubmit=GitHub - 티켓 제출
|
||||
error.discordSubmit=Discord - 문의 게시
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -11,17 +11,17 @@ imgPrompt=Selecteer afbeelding(en)
|
||||
genericSubmit=Indienen
|
||||
processTimeWarning=Waarschuwing: Dit proces kan tot een minuut duren afhankelijk van de bestandsgrootte
|
||||
pageOrderPrompt=Aangepaste pagina volgorde (Voer een komma-gescheiden lijst van paginanummers of functies in, zoals 2n+1) :
|
||||
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
|
||||
pageSelectionPrompt=Aangepaste pagina selectie (Voer een komma-gescheiden lijst van paginanummer 1,5,6 of functies zoals 2n+1 in) :
|
||||
goToPage=Ga
|
||||
true=Waar
|
||||
false=Onwaar
|
||||
unknown=Onbekend
|
||||
save=Opslaan
|
||||
saveToBrowser=Save to Browser
|
||||
saveToBrowser=Opslaan in browser
|
||||
close=Sluiten
|
||||
filesSelected=Bestanden geselecteerd
|
||||
noFavourites=Geen favorieten toegevoegd
|
||||
downloadComplete=Download Complete
|
||||
downloadComplete=Download klaar
|
||||
bored=Verveeld met wachten?
|
||||
alphabet=Alfabet
|
||||
downloadPdf=Download PDF
|
||||
@@ -54,23 +54,23 @@ notAuthenticatedMessage=Gebruiker niet ingelogd.
|
||||
userNotFoundMessage=Gebruiker niet gevonden.
|
||||
incorrectPasswordMessage=Huidige wachtwoord is onjuist.
|
||||
usernameExistsMessage=Nieuwe gebruikersnaam bestaat al.
|
||||
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
|
||||
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
invalidUsernameMessage=Ongeldige gebruikersnaam, gebruikersnaam kan alleen letters, nummers en de volgende speciale tekens @._+- bevatten of moet een geldig emailadres zijn.
|
||||
confirmPasswordErrorMessage=Nieuw wachtwoord en bevestig wachtwoord moeten overeenkomen.
|
||||
deleteCurrentUserMessage=Kan niet een momenteel ingelogde gebruiker verwijderen.
|
||||
deleteUsernameExistsMessage=De gebruikersnaam bestaat niet en kan niet verwijderd worden.
|
||||
downgradeCurrentUserMessage=Kan de rol van de huidige gebruiker niet downgraden
|
||||
downgradeCurrentUserLongMessage=Kan de rol van de huidige gebruiker niet downgraden. Huidige gebruiker wordt dus niet weergegeven.
|
||||
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
||||
userAlreadyExistsWebMessage=The user already exists as an web user.
|
||||
userAlreadyExistsOAuthMessage=De gebruiker bestaat al als een OAuth2 gebruiker.
|
||||
userAlreadyExistsWebMessage=De gebruiker bestaat al als een web gebruiker.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
oops=Oeps!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
goHomepage=Ga naar de startpagina
|
||||
joinDiscord=Word lid van onze Discord server
|
||||
seeDockerHub=Zie Docker Hub
|
||||
visitGithub=Ga naar de Github Repository
|
||||
donate=Doneer
|
||||
color=Kleur
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
|
||||
@@ -79,14 +79,14 @@ info=Info
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
pipeline.header=Pijplijn menu (Alpha)
|
||||
pipeline.header=Pijplijn menu (Beta)
|
||||
pipeline.uploadButton=Aangepast uploaden
|
||||
pipeline.configureButton=Configureren
|
||||
pipeline.defaultOption=Aangepast
|
||||
pipeline.submitButton=Opslaan
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp=Folder Scanning Help
|
||||
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||
pipeline.help=Pijplijn help
|
||||
pipeline.scanHelp=Map scannen help
|
||||
pipeline.deletePrompt=Weet je zeker dat je deze pijplijn wil verwijderen?
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
@@ -107,25 +107,25 @@ pipelineOptions.validateButton=Valideren
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.favorite=Favorieten
|
||||
navbar.darkmode=Donkere modus
|
||||
navbar.language=Languages
|
||||
navbar.language=Talen
|
||||
navbar.settings=Instellingen
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.multiTool=Multitools
|
||||
navbar.sections.organize=Organizeren
|
||||
navbar.sections.convertTo=Converteren naar PDF
|
||||
navbar.sections.convertFrom=Converteren van PDF
|
||||
navbar.sections.security=Ondertekenen & beveiliging
|
||||
navbar.sections.advance=Geavanceerd
|
||||
navbar.sections.edit=Bekijken & wijzigen
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
#############
|
||||
settings.title=Instellingen
|
||||
settings.update=Update beschikbaar
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.updateAvailable={0} is de huidig geïnstalleerde versie. Een nieuwe versie ({1}) is beschikbaar.
|
||||
settings.appVersion=App versie:
|
||||
settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip):
|
||||
settings.downloadOption.1=Open in hetzelfde venster
|
||||
@@ -134,13 +134,13 @@ settings.downloadOption.3=Download bestand
|
||||
settings.zipThreshold=Bestanden zippen wanneer het aantal gedownloade bestanden meer is dan
|
||||
settings.signOut=Uitloggen
|
||||
settings.accountSettings=Account instellingen
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
settings.bored.help=Schakelt geheim spelletje in
|
||||
settings.cacheInputs.name=Sla invoer in formulieren op
|
||||
settings.cacheInputs.help=Schakel in om eerdere invoeren op te slaan voor toekomstige uitvoeren
|
||||
|
||||
changeCreds.title=Inloggegevens wijzigen
|
||||
changeCreds.header=Werk je accountgegevens bij
|
||||
changeCreds.changePassword=You are using default login credentials. Please enter a new password
|
||||
changeCreds.changePassword=Je gebruikt de standaard inloggegevens. Voer alstublieft een nieuw wachtwoord in
|
||||
changeCreds.newUsername=Nieuwe gebruikersnaam
|
||||
changeCreds.oldPassword=Huidige wachtwoord
|
||||
changeCreds.newPassword=Nieuw wachtwoord
|
||||
@@ -175,27 +175,44 @@ adminUserSettings.header=Beheer gebruikers
|
||||
adminUserSettings.admin=Beheerder
|
||||
adminUserSettings.user=Gebruiker
|
||||
adminUserSettings.addUser=Voeg nieuwe gebruiker toe
|
||||
adminUserSettings.deleteUser=Delete User
|
||||
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
|
||||
adminUserSettings.deleteUser=Verwijder gebruiker
|
||||
adminUserSettings.confirmDeleteUser=Moet deze gebruiker verwijderd worden?
|
||||
adminUserSettings.usernameInfo=Gebruikersnaam kan alleen letters, nummers en de volgende speciale tekens @._+- bevatten of moet een geldig emailadres zijn.
|
||||
adminUserSettings.roles=Rollen
|
||||
adminUserSettings.role=Rol
|
||||
adminUserSettings.actions=Acties
|
||||
adminUserSettings.apiUser=Beperkte API gebruiker
|
||||
adminUserSettings.extraApiUser=Additional Limited API User
|
||||
adminUserSettings.extraApiUser=Extra beperkte API gebruiker
|
||||
adminUserSettings.webOnlyUser=Alleen web gebruiker
|
||||
adminUserSettings.demoUser=Demogebruiker (geen aangepaste instellingen)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.internalApiUser=Interne API gebruiker
|
||||
adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen
|
||||
adminUserSettings.submit=Gebruiker opslaan
|
||||
adminUserSettings.changeUserRole=De rol van de gebruiker wijzigen
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
adminUserSettings.authenticated=Geauthenticeerd
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
home.desc=Jouw lokaal gehoste one-stop-shop voor al je PDF-behoeften.
|
||||
home.searchBar=Zoeken naar functies...
|
||||
home.searchBar=Zoek naar functies...
|
||||
|
||||
|
||||
home.viewPdf.title=PDF bekijken
|
||||
@@ -204,11 +221,11 @@ viewPdf.tags=bekijken,lezen,annoteren,tekst,afbeelding
|
||||
|
||||
home.multiTool.title=PDF multitool
|
||||
home.multiTool.desc=Pagina's samenvoegen, draaien, herschikken en verwijderen
|
||||
multiTool.tags=Multitool,Multi bewerking,UI,klik sleep,voorkant,clientzijde,interactief,beweegbaar,verplaats
|
||||
multiTool.tags=Multitool,meerdere bewerkingen,UI,klik sleep,voorkant,clientzijde,interactief,beweegbaar,verplaats
|
||||
|
||||
home.merge.title=Samenvoegen
|
||||
home.merge.desc=Voeg eenvoudig meerdere PDF's samen tot één.
|
||||
merge.tags=samenvoegen,Pagina bewerkingen,Serverkant
|
||||
merge.tags=samenvoegen,pagina bewerkingen,serverzijde
|
||||
|
||||
home.split.title=Splitsen
|
||||
home.split.desc=Splits PDF's in meerdere documenten
|
||||
@@ -255,7 +272,7 @@ addPassword.tags=veilig,beveiliging
|
||||
|
||||
home.removePassword.title=Wachtwoord verwijderen
|
||||
home.removePassword.desc=Verwijder wachtwoordbeveiliging van je PDF-document.
|
||||
removePassword.tags=veilig,Decrypteren,beveiliging,wachtwoord verwijderen
|
||||
removePassword.tags=veilig,ontsleutelen,beveiliging,wachtwoord verwijderen
|
||||
|
||||
home.compressPdfs.title=Comprimeren
|
||||
home.compressPdfs.desc=Comprimeer PDF's om hun bestandsgrootte te verkleinen.
|
||||
@@ -336,9 +353,9 @@ home.certSign.title=Ondertekenen met certificaat
|
||||
home.certSign.desc=Ondertekent een PDF met een certificaat/sleutel (PEM/P12)
|
||||
certSign.tags=authenticeren,PEM,P12,officieel,versleutelen
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
home.removeCertSign.title=Verwijder certificaat
|
||||
home.removeCertSign.desc=Verwijder certificaat van PDF
|
||||
removeCertSign.tags=authenticeren,PEM,P12,officieel,ontsleutelen
|
||||
|
||||
home.pageLayout.title=Multi-pagina indeling
|
||||
home.pageLayout.desc=Voeg meerdere pagina's van een PDF-document samen op één pagina
|
||||
@@ -436,13 +453,17 @@ home.AddStampRequest.desc=Voeg tekst of afbeeldingsstempels toe op vaste locatie
|
||||
AddStampRequest.tags=Stempel, Afbeelding toevoegen, afbeelding centreren, watermerk, PDF, Insluiten, Aanpassen
|
||||
|
||||
|
||||
home.PDFToBook.title=PDF to Book
|
||||
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
home.PDFToBook.title=PDF naar Boek
|
||||
home.PDFToBook.desc=Converteert PDF naar boek-/stripformaat met gebruik van Calibre
|
||||
PDFToBook.tags=Boek,Strip,Comic,Calibre,Converteren,manga,amazon,kindle
|
||||
|
||||
home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
home.BookToPDF.title=Boek naar PDF
|
||||
home.BookToPDF.desc=Converteert boek-/stripformaat naar PDF met gebruik van Calibre
|
||||
BookToPDF.tags=Boek,Strip,Comic,Calibre,Converteren,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
@@ -460,12 +481,12 @@ login.locked=Je account is geblokkeerd.
|
||||
login.signinTitle=Gelieve in te loggen
|
||||
login.ssoSignIn=Inloggen via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.oauth2RequestNotFound=Autorisatieverzoek niet gevonden
|
||||
login.oauth2InvalidUserInfoResponse=Ongeldige reactie op gebruikersinfo
|
||||
login.oauth2invalidRequest=Ongeldig verzoek
|
||||
login.oauth2AccessDenied=Toegang geweigerd
|
||||
login.oauth2InvalidTokenResponse=Ongeldige tokenreactie
|
||||
login.oauth2InvalidIdToken=Ongeldige ID token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -512,7 +533,7 @@ getPdfInfo.downloadJson=Download JSON
|
||||
MarkdownToPDF.title=Markdown naar PDF
|
||||
MarkdownToPDF.header=Markdown naar PDF
|
||||
MarkdownToPDF.submit=Converteren
|
||||
MarkdownToPDF.help=in ontwikkeling
|
||||
MarkdownToPDF.help=In ontwikkeling
|
||||
MarkdownToPDF.credit=Gebruikt WeasyPrint
|
||||
|
||||
|
||||
@@ -542,7 +563,7 @@ HTMLToPDF.defaultHeader=Standaard koptekst weergeven (naam en paginanummer)
|
||||
HTMLToPDF.cssMediaType=Wijzig het CSS-mediatype van de pagina.
|
||||
HTMLToPDF.none=Geen
|
||||
HTMLToPDF.print=Print
|
||||
HTMLToPDF.screen=Screen
|
||||
HTMLToPDF.screen=Scherm
|
||||
|
||||
|
||||
#AddStampRequest
|
||||
@@ -664,10 +685,10 @@ certSign.submit=PDF ondertekenen
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
removeCertSign.title=Verwijder certificaat
|
||||
removeCertSign.header=Verwijder het digitale certificaat van de PDF
|
||||
removeCertSign.selectPDF=Selecteer een PDF bestand:
|
||||
removeCertSign.submit=Verwijder certificaat
|
||||
|
||||
|
||||
#removeBlanks
|
||||
@@ -689,22 +710,24 @@ removeAnnotations.submit=Verwijderen
|
||||
#compare
|
||||
compare.title=Vergelijken
|
||||
compare.header=PDF's vergelijken
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Document 1
|
||||
compare.document.2=Document 2
|
||||
compare.submit=Vergelijken
|
||||
|
||||
#BookToPDF
|
||||
BookToPDF.title=Books and Comics to PDF
|
||||
BookToPDF.header=Book to PDF
|
||||
BookToPDF.credit=Uses Calibre
|
||||
BookToPDF.submit=Convert
|
||||
BookToPDF.title=Boeken en strips naar PDF
|
||||
BookToPDF.header=Boek naar PDF
|
||||
BookToPDF.credit=Gebruikt Calibre
|
||||
BookToPDF.submit=Converteer
|
||||
|
||||
#PDFToBook
|
||||
PDFToBook.title=PDF to Book
|
||||
PDFToBook.header=PDF to Book
|
||||
PDFToBook.selectText.1=Format
|
||||
PDFToBook.credit=Uses Calibre
|
||||
PDFToBook.submit=Convert
|
||||
PDFToBook.title=PDF naar boek
|
||||
PDFToBook.header=PDF naar boek
|
||||
PDFToBook.selectText.1=Formaat
|
||||
PDFToBook.credit=Gebruikt Calibre
|
||||
PDFToBook.submit=Converteer
|
||||
|
||||
#sign
|
||||
sign.title=Ondertekenen
|
||||
@@ -725,7 +748,7 @@ repair.submit=Repareren
|
||||
#flatten
|
||||
flatten.title=Afvlakken
|
||||
flatten.header=PDF's afvlakken
|
||||
flatten.flattenOnlyForms=Flatten only forms
|
||||
flatten.flattenOnlyForms=Alleen formulieren afvlakken
|
||||
flatten.submit=Afvlakken
|
||||
|
||||
|
||||
@@ -756,7 +779,7 @@ ocr.selectText.8=Normaal (Zal een fout geven als de PDF tekst bevat)
|
||||
ocr.selectText.9=Aanvullende instellingen
|
||||
ocr.selectText.10=OCR-modus
|
||||
ocr.selectText.11=Verwijder afbeeldingen na OCR (Verwijdert ALLE afbeeldingen, alleen nuttig als onderdeel van conversiestap)
|
||||
ocr.selectText.12=Render 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.credit=Deze dienst maakt gebruik van OCRmyPDF en Tesseract voor OCR.
|
||||
ocr.submit=Verwerk PDF met OCR
|
||||
@@ -803,7 +826,7 @@ merge.title=Samenvoegen
|
||||
merge.header=Meerdere PDF's samenvoegen (2+)
|
||||
merge.sortByName=Sorteer op naam
|
||||
merge.sortByDate=Sorteer op datum
|
||||
merge.removeCertSign=Remove digital signature in the merged file?
|
||||
merge.removeCertSign=Verwijder digitale handtekening in het samengevoegde bestand?
|
||||
merge.submit=Samenvoegen
|
||||
|
||||
|
||||
@@ -811,23 +834,24 @@ merge.submit=Samenvoegen
|
||||
pdfOrganiser.title=Pagina organisator
|
||||
pdfOrganiser.header=PDF pagina organisator
|
||||
pdfOrganiser.submit=Pagina's herschikken
|
||||
pdfOrganiser.mode=Mode
|
||||
pdfOrganiser.mode.1=Custom Page Order
|
||||
pdfOrganiser.mode.2=Reverse Order
|
||||
pdfOrganiser.mode.3=Duplex Sort
|
||||
pdfOrganiser.mode.4=Booklet Sort
|
||||
pdfOrganiser.mode.5=Side Stitch Booklet Sort
|
||||
pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
pdfOrganiser.mode=Modus
|
||||
pdfOrganiser.mode.1=Aangepaste paginavolgorde
|
||||
pdfOrganiser.mode.2=Omgekeerde volgorde
|
||||
pdfOrganiser.mode.3=Duplex sorteren
|
||||
pdfOrganiser.mode.4=Boekje sorteren
|
||||
pdfOrganiser.mode.5=Zijsteek boekje sorteren
|
||||
pdfOrganiser.mode.6=Oneven-even splitsen
|
||||
pdfOrganiser.mode.7=Eerste verwijderen
|
||||
pdfOrganiser.mode.8=Laatste verwijderen
|
||||
pdfOrganiser.mode.9=Eerste en laaste verwijderen
|
||||
pdfOrganiser.mode.10=Oneven-even samenvoeken
|
||||
pdfOrganiser.placeholder=(bijv. 1,3,2 of 4-8,2,10-12 of 2n-1)
|
||||
|
||||
|
||||
#multiTool
|
||||
multiTool.title=PDF Multitool
|
||||
multiTool.header=PDF Multitool
|
||||
multiTool.uploadPrompts=File Name
|
||||
multiTool.uploadPrompts=Bestandsnaam
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=PDF bekijken
|
||||
@@ -838,7 +862,7 @@ pageRemover.title=Pagina verwijderaar
|
||||
pageRemover.header=PDF pagina verwijderaar
|
||||
pageRemover.pagesToDelete=Te verwijderen pagina's (Voer een door komma's gescheiden lijst met paginanummers in):
|
||||
pageRemover.submit=Pagina's verwijderen
|
||||
pageRemover.placeholder=(e.g. 1,2,6 or 1-10,15-30)
|
||||
pageRemover.placeholder=(bijv. 1,2,6 of 1-10,15-30)
|
||||
|
||||
|
||||
#rotate
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=hoogteSpacer (Ruimte tussen elk watermerk verticaal):
|
||||
watermark.selectText.7=Transparantie (0% - 100%):
|
||||
watermark.selectText.8=Type watermerk:
|
||||
watermark.selectText.9=Watermerk afbeelding:
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Watermerk toevoegen
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -979,9 +1004,9 @@ pdfToPDFA.title=PDF naar PDF/A
|
||||
pdfToPDFA.header=PDF naar PDF/A
|
||||
pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie
|
||||
pdfToPDFA.submit=Converteren
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd.
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
pdfToPDFA.pdfWithDigitalSignature=Dit PDF bestand bevat een digitale handtekening. Deze wordt in de volgende stap verwijderd.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
@@ -1063,14 +1088,14 @@ split-by-sections.vertical.label=Verticale secties
|
||||
split-by-sections.horizontal.placeholder=Voer het aantal horizontale secties in
|
||||
split-by-sections.vertical.placeholder=Voer het aantal verticale secties in
|
||||
split-by-sections.submit=PDF splitsen
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
split-by-sections.merge=Samenvoegen in één PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.title=Print bestand
|
||||
printFile.header=Print bestand naar printer
|
||||
printFile.selectText.1=Selecteer bestand om te printen
|
||||
printFile.selectText.2=Voer printernaam in
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
@@ -1083,25 +1108,31 @@ licenses.version=Versie
|
||||
licenses.license=Licentie
|
||||
|
||||
#survey
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
survey.dontShowAgain=Don't show again
|
||||
survey.nav=Enquête
|
||||
survey.title=Stirling-PDF Enquête
|
||||
survey.description=Stirling-PDF heeft geen tracking, dus we willen van onze gebruikers horen om Stirling-PDF te verbeteren.
|
||||
survey.please=Overweeg alstublieft om onze enquête in te vullen!
|
||||
survey.disabled=(Enquête popup wordt in een toekomstige update weggehaald, maar is beschikbaar aan de onderkant van de pagina.)
|
||||
survey.button=Vul enquête in.
|
||||
survey.dontShowAgain=Niet weer tonen
|
||||
|
||||
|
||||
#error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=Excuses voor het probleem!
|
||||
error.needHelp=Hulp nodig / probleem gevonden?
|
||||
error.contactTip=Als je nog steeds problemen hebt, schroom niet om contact met ons op te nemen voor hulp. Je kan een ticket op onze Github pagina indienen of ons via Discord bereiken:
|
||||
error.404.head=404 - Pagina niet gevonden | Oeps, we struikelden over de code!
|
||||
error.404.1=We kunnen de pagina die je zoek niet vinden.
|
||||
error.404.2=Er ging iets mis.
|
||||
error.github=Dien een ticket op Github in.
|
||||
error.showStack=Geeft tracering weer
|
||||
error.copyStack=Kopieer tracering
|
||||
error.githubSubmit=GitHub - Dien een ticket in
|
||||
error.discordSubmit=Discord - Maak een support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -55,7 +55,7 @@ userNotFoundMessage=Bruker ikke funnet.
|
||||
incorrectPasswordMessage=Nåværende passord er feil.
|
||||
usernameExistsMessage=Det nye brukernavnet eksisterer allerede.
|
||||
invalidUsernameMessage=Ugyldig brukernavn, brukernavnet kan bare inneholde bokstaver, tall og følgende spesialtegn @._+- eller må være en gyldig e-postadresse.
|
||||
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
|
||||
confirmPasswordErrorMessage=Nytt passord og Bekreft nytt passord må være like.
|
||||
deleteCurrentUserMessage=Kan ikke slette den innloggede brukeren.
|
||||
deleteUsernameExistsMessage=Brukernavnet eksisterer ikke og kan ikke slettes.
|
||||
downgradeCurrentUserMessage=Kan ikke nedgradere den innloggede brukerens rolle.
|
||||
@@ -175,8 +175,8 @@ adminUserSettings.header=Admin Brukerkontroll Innstillinger
|
||||
adminUserSettings.admin=Admin
|
||||
adminUserSettings.user=Bruker
|
||||
adminUserSettings.addUser=Legg til Ny Bruker
|
||||
adminUserSettings.deleteUser=Delete User
|
||||
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||
adminUserSettings.deleteUser=Slett Bruker
|
||||
adminUserSettings.confirmDeleteUser=Skal brukeren slettes?
|
||||
adminUserSettings.usernameInfo=Brukernavn kan bare inneholde bokstaver, tall og følgende spesialtegn @._+- eller må være en gyldig e-postadresse.
|
||||
adminUserSettings.roles=Roller
|
||||
adminUserSettings.role=Rolle
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Lagre Bruker
|
||||
adminUserSettings.changeUserRole=Endre Brukerens Rolle
|
||||
adminUserSettings.authenticated=Autentisert
|
||||
|
||||
|
||||
database.title=Database Import/Eksport
|
||||
database.header=Database Import/Eksport
|
||||
database.fileName=Fil navn
|
||||
database.creationDate=Opprettelsesdato
|
||||
database.fileSize=Filstørrelse
|
||||
database.deleteBackupFile=Slett sikkerhetskopifil
|
||||
database.importBackupFile=Importer sikkerhetskopifil
|
||||
database.downloadBackupFile=Last ned sikkerhetskopifil
|
||||
database.info_1=Når du importerer data, er det avgjørende å sikre riktig struktur. Hvis du er usikker på hva du gjør, bør du søke råd og støtte fra en profesjonell. En feil i strukturen kan føre til applikasjonsfeil, inkludert fullstendig manglende evne til å kjøre applikasjonen.
|
||||
database.info_2=Filnavnet spiller ingen rolle ved opplasting. Det vil bli omdøpt etterpå for å følge formatet backup_user_yyyyMMddHHmm.sql, for å sikre en konsekvent navnekonvensjon.
|
||||
database.submit=Importer sikkerhetskopi
|
||||
database.importIntoDatabaseSuccessed=Import til database vellykket
|
||||
database.fileNotFound=Fil ikke funnet
|
||||
database.fileNullOrEmpty=Fil må ikke være tom eller null
|
||||
database.failedImportFile=Import av fil mislyktes
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Bok til PDF
|
||||
home.BookToPDF.desc=Konverter bøker/tegneserier til PDF ved hjelp av calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -540,8 +561,8 @@ HTMLToPDF.marginRight=Høyre margin på siden i millimeter. (Blank for standard)
|
||||
HTMLToPDF.printBackground=Vis bakgrunnen til nettsider.
|
||||
HTMLToPDF.defaultHeader=Aktiver standardtopp (Navn og sidenummer)
|
||||
HTMLToPDF.cssMediaType=Endre CSS-mediatypen til siden.
|
||||
HTMLToPDF.none=None
|
||||
HTMLToPDF.print=Print
|
||||
HTMLToPDF.none=Ingen
|
||||
HTMLToPDF.print=Utskrift
|
||||
HTMLToPDF.screen=Skjerm
|
||||
|
||||
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Fjern
|
||||
#compare
|
||||
compare.title=Sammenlign
|
||||
compare.header=Sammenlign PDF-er
|
||||
compare.highlightColor.1=Uthevingsfarge 1:
|
||||
compare.highlightColor.2=Uthevingsfarge 2:
|
||||
compare.document.1=Dokument 1
|
||||
compare.document.2=Dokument 2
|
||||
compare.submit=Sammenlign
|
||||
@@ -803,7 +826,7 @@ merge.title=Slå sammen
|
||||
merge.header=Slå sammen flere PDF-er (2+)
|
||||
merge.sortByName=Sorter etter navn
|
||||
merge.sortByDate=Sorter etter dato
|
||||
merge.removeCertSign=Remove digital signature in the merged file?
|
||||
merge.removeCertSign=Fjern digital signatur i den sammenslåtte filen?
|
||||
merge.submit=Slå sammen
|
||||
|
||||
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Oddetall-jevntall splitt
|
||||
pdfOrganiser.mode.7=Fjern først
|
||||
pdfOrganiser.mode.8=Fjern sist
|
||||
pdfOrganiser.mode.9=Fjern først og sist
|
||||
pdfOrganiser.mode.10=Partall-Oddetall Sammenslåing
|
||||
pdfOrganiser.placeholder=(f.eks. 1,3,2 eller 4-8,2,10-12 eller 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Høydeavstand (Avstand mellom hvert vannmerke vertikalt):
|
||||
watermark.selectText.7=Opasitet (0% - 100%):
|
||||
watermark.selectText.8=Vannmerketype:
|
||||
watermark.selectText.9=Vannmerkebilde:
|
||||
watermark.selectText.10=Konverter PDF til PDF-Bilde
|
||||
watermark.submit=Legg til vannmerke
|
||||
watermark.type.1=Tekst
|
||||
watermark.type.2=Bilde
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Kopier stakksporing
|
||||
error.githubSubmit=GitHub - Send inn en billett
|
||||
error.discordSubmit=Discord - Send inn støtteinnlegg
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
867
src/main/resources/messages_pl_PL.properties
Normal file → Executable file
867
src/main/resources/messages_pl_PL.properties
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -191,6 +191,23 @@ adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Alterar Função de Usuário
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remove
|
||||
#compare
|
||||
compare.title=Comparar
|
||||
compare.header=Comparar PDFs
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Documento 1
|
||||
compare.document.2=Documento 2
|
||||
compare.submit=Comparar
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Espaçamento Vertical (heightSpacer)
|
||||
watermark.selectText.7=Opacidade (0% - 100%)
|
||||
watermark.selectText.8=Tipo de Marca d'Água
|
||||
watermark.selectText.9=Imagem da Marca d'Água
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Adicionar Marca d'Água
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
@@ -191,6 +191,23 @@ adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Alterar usuário
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
|
||||
|
||||
database.title=Database Import/Export
|
||||
database.header=Database Import/Export
|
||||
database.fileName=File Name
|
||||
database.creationDate=Creation Date
|
||||
database.fileSize=File Size
|
||||
database.deleteBackupFile=Delete Backup File
|
||||
database.importBackupFile=Import Backup File
|
||||
database.downloadBackupFile=Download Backup File
|
||||
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||
database.submit=Import Backup
|
||||
database.importIntoDatabaseSuccessed=Import into database successed
|
||||
database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -444,6 +461,10 @@ home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.removeImagePdf.title=Remove image
|
||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
@@ -689,6 +710,8 @@ removeAnnotations.submit=Remover
|
||||
#compare
|
||||
compare.title=Comparar
|
||||
compare.header=Comparar PDFs
|
||||
compare.highlightColor.1=Highlight Color 1:
|
||||
compare.highlightColor.2=Highlight Color 2:
|
||||
compare.document.1=Documento 1
|
||||
compare.document.2=Documento 2
|
||||
compare.submit=Comparar
|
||||
@@ -821,6 +844,7 @@ pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.mode.10=Odd-Even Merge
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
|
||||
|
||||
@@ -925,6 +949,7 @@ watermark.selectText.6=Espaçamento Vertical (heightSpacer)
|
||||
watermark.selectText.7=Opacidade (0% - 100%)
|
||||
watermark.selectText.8=Tipo de Marca d'Água
|
||||
watermark.selectText.9=Imagem da Marca d'Água
|
||||
watermark.selectText.10=Convert PDF to PDF-Image
|
||||
watermark.submit=Adicionar Marca d'Água
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
@@ -1105,3 +1130,9 @@ error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
|
||||
|
||||
#remove-image
|
||||
removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user