Compare commits
31 Commits
v0.33.0
...
Frooodle/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8709c25c0 | ||
|
|
261e69f3ca | ||
|
|
addb999301 | ||
|
|
496fcad698 | ||
|
|
daf4f49050 | ||
|
|
2cdad2f9ff | ||
|
|
4c4613cfec | ||
|
|
8aa0e85c20 | ||
|
|
1129487aaf | ||
|
|
378d0f8afb | ||
|
|
a12de218c0 | ||
|
|
1ba271b0f4 | ||
|
|
6ccc29d0a7 | ||
|
|
eee2f5c666 | ||
|
|
e660237e28 | ||
|
|
83e93688ee | ||
|
|
dedfabd630 | ||
|
|
3b5b7772a9 | ||
|
|
c59d3ff3e0 | ||
|
|
5832147b30 | ||
|
|
20dc2f60cd | ||
|
|
b46ccdde44 | ||
|
|
4fa1b4adb0 | ||
|
|
03158b05e4 | ||
|
|
28c55ca80c | ||
|
|
6ca14edaf1 | ||
|
|
f9677b1fe8 | ||
|
|
04a6ebf515 | ||
|
|
262e2ed47a | ||
|
|
4436759e12 | ||
|
|
87925ac618 |
17
.github/labeler-config.yml
vendored
17
.github/labeler-config.yml
vendored
@@ -16,27 +16,21 @@ Java:
|
|||||||
|
|
||||||
Back End:
|
Back End:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/**/*'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/**/*'
|
- 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/settings.yml.template'
|
||||||
- any-glob-to-any-file: 'src/main/resources/application.properties'
|
|
||||||
- any-glob-to-any-file: 'src/main/resources/banner.txt'
|
- any-glob-to-any-file: 'src/main/resources/banner.txt'
|
||||||
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
|
||||||
- any-glob-to-any-file: 'split_photos.py'
|
|
||||||
|
|
||||||
Security:
|
Security:
|
||||||
- changed-files:
|
- 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/security/**/*'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*'
|
- 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/model/AuthenticationType.java'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/model/AuthenticationType.java'
|
||||||
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
|
||||||
|
|
||||||
API:
|
API:
|
||||||
- changed-files:
|
- 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/web/MetricsController.java'
|
||||||
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*'
|
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*'
|
||||||
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
|
||||||
- any-glob-to-any-file: 'split_photos.py'
|
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
@@ -49,9 +43,6 @@ Docker:
|
|||||||
- any-glob-to-any-file: 'Dockerfile'
|
- any-glob-to-any-file: 'Dockerfile'
|
||||||
- any-glob-to-any-file: 'Dockerfile-*'
|
- any-glob-to-any-file: 'Dockerfile-*'
|
||||||
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
|
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
|
||||||
- any-glob-to-any-file: 'scripts/init.sh'
|
|
||||||
- any-glob-to-any-file: 'scripts/init-without-ocr.sh'
|
|
||||||
- any-glob-to-any-file: 'scripts/installFonts.sh'
|
|
||||||
|
|
||||||
Test:
|
Test:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
|||||||
21
.github/labels.yml
vendored
21
.github/labels.yml
vendored
@@ -3,12 +3,9 @@
|
|||||||
#
|
#
|
||||||
# The repository labels will be automatically configured using this file and
|
# The repository labels will be automatically configured using this file and
|
||||||
# the GitHub Action https://github.com/marketplace/actions/github-labeler.
|
# the GitHub Action https://github.com/marketplace/actions/github-labeler.
|
||||||
- name: "Licenses"
|
|
||||||
color: "EDEDED"
|
|
||||||
from_name: "licenses"
|
|
||||||
- name: "Back End"
|
- name: "Back End"
|
||||||
color: "20CE6C"
|
color: "20CE6C"
|
||||||
description: "Issues or pull requests related to back-end development"
|
description: "Issues related to back-end development"
|
||||||
from_name: "Back end"
|
from_name: "Back end"
|
||||||
- name: "Bug"
|
- name: "Bug"
|
||||||
description: "Something isn't working"
|
description: "Something isn't working"
|
||||||
@@ -27,7 +24,6 @@
|
|||||||
from_name: "documentation"
|
from_name: "documentation"
|
||||||
- name: "Done for next release"
|
- name: "Done for next release"
|
||||||
color: "0CDBD1"
|
color: "0CDBD1"
|
||||||
description: "Items that are completed and will be included in the next release"
|
|
||||||
- name: "Done"
|
- name: "Done"
|
||||||
color: "60F13B"
|
color: "60F13B"
|
||||||
- name: "duplicate"
|
- name: "duplicate"
|
||||||
@@ -41,7 +37,7 @@
|
|||||||
description: "Fix needs to be confirmed"
|
description: "Fix needs to be confirmed"
|
||||||
- name: "Front End"
|
- name: "Front End"
|
||||||
color: "BBD2F1"
|
color: "BBD2F1"
|
||||||
description: "Issues or pull requests related to front-end development"
|
description: "Issues related to front-end development"
|
||||||
- name: "github-actions"
|
- name: "github-actions"
|
||||||
description: "Pull requests that update GitHub Actions code"
|
description: "Pull requests that update GitHub Actions code"
|
||||||
color: "999999"
|
color: "999999"
|
||||||
@@ -95,16 +91,3 @@
|
|||||||
description: "Testing-related issues or pull requests"
|
description: "Testing-related issues or pull requests"
|
||||||
- name: "Stale"
|
- name: "Stale"
|
||||||
color: "000000"
|
color: "000000"
|
||||||
description: "Issues or pull requests that have become inactive"
|
|
||||||
- name: "Priority: Critical"
|
|
||||||
color: "000000"
|
|
||||||
description: "Issues or pull requests with the highest priority"
|
|
||||||
- name: "Priority: High"
|
|
||||||
color: "FF0000"
|
|
||||||
description: "Issues or pull requests with high priority"
|
|
||||||
- name: "Priority: Medium"
|
|
||||||
color: "FFFF00"
|
|
||||||
description: "Issues or pull requests with medium priority"
|
|
||||||
- name: "Priority: Low"
|
|
||||||
color: "00FF00"
|
|
||||||
description: "Issues or pull requests with low priority"
|
|
||||||
|
|||||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -8,8 +8,6 @@ Closes #(issue_number)
|
|||||||
|
|
||||||
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
||||||
- [ ] I have performed a self-review of my own code
|
- [ ] I have performed a self-review of my own code
|
||||||
- [ ] I have attached images of the change if it is UI based
|
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||||
- [ ] If my code has heavily changed functionality I have updated relevant docs on [Stirling-PDFs doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
|
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
||||||
|
|||||||
67
.github/scripts/gradle_to_chart.py
vendored
Normal file
67
.github/scripts/gradle_to_chart.py
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import re
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# Paths to the files
|
||||||
|
chart_yaml_path = "chart/stirling-pdf/Chart.yaml"
|
||||||
|
gradle_path = "build.gradle"
|
||||||
|
|
||||||
|
|
||||||
|
def get_chart_version(path):
|
||||||
|
"""
|
||||||
|
Reads the appVersion from Chart.yaml.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): The file path to the Chart.yaml.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The appVersion if found, otherwise an empty string.
|
||||||
|
"""
|
||||||
|
with open(path, encoding="utf-8") as file:
|
||||||
|
chart_yaml = yaml.safe_load(file)
|
||||||
|
return chart_yaml.get("appVersion", "")
|
||||||
|
|
||||||
|
|
||||||
|
def get_gradle_version(path):
|
||||||
|
"""
|
||||||
|
Extracts the version from build.gradle.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): The file path to the build.gradle.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The version if found, otherwise an empty string.
|
||||||
|
"""
|
||||||
|
with open(path, encoding="utf-8") as file:
|
||||||
|
for line in file:
|
||||||
|
if "version =" in line:
|
||||||
|
# Extracts the value after 'version ='
|
||||||
|
return re.search(r'version\s*=\s*[\'"](.+?)[\'"]', line).group(1)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def update_chart_version(path, new_version):
|
||||||
|
"""
|
||||||
|
Updates the appVersion in Chart.yaml with a new version.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): The file path to the Chart.yaml.
|
||||||
|
new_version (str): The new version to update to.
|
||||||
|
"""
|
||||||
|
with open(path, encoding="utf-8") as file:
|
||||||
|
chart_yaml = yaml.safe_load(file)
|
||||||
|
chart_yaml["appVersion"] = new_version
|
||||||
|
with open(path, "w", encoding="utf-8") as file:
|
||||||
|
yaml.safe_dump(chart_yaml, file)
|
||||||
|
|
||||||
|
|
||||||
|
# Main logic
|
||||||
|
chart_version = get_chart_version(chart_yaml_path)
|
||||||
|
gradle_version = get_gradle_version(gradle_path)
|
||||||
|
|
||||||
|
if chart_version != gradle_version:
|
||||||
|
print(
|
||||||
|
f"Versions do not match. Updating Chart.yaml from {chart_version} to {gradle_version}."
|
||||||
|
)
|
||||||
|
update_chart_version(chart_yaml_path, gradle_version)
|
||||||
|
else:
|
||||||
|
print("Versions match. No update required.")
|
||||||
4
.github/workflows/push-docker.yml
vendored
4
.github/workflows/push-docker.yml
vendored
@@ -10,7 +10,6 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -67,7 +66,6 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||||
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
|
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
@@ -95,7 +93,6 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||||
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
|
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
@@ -122,7 +119,6 @@ jobs:
|
|||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||||
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
|
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
|||||||
38
.github/workflows/sync_files.yml
vendored
38
.github/workflows/sync_files.yml
vendored
@@ -14,6 +14,44 @@ permissions:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
sync-versions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install pyyaml
|
||||||
|
- name: Sync versions
|
||||||
|
run: python .github/scripts/gradle_to_chart.py
|
||||||
|
- name: Set up git config
|
||||||
|
run: |
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
- name: Run git add
|
||||||
|
run: |
|
||||||
|
git add .
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
commit-message: Update files
|
||||||
|
committer: GitHub Action <action@github.com>
|
||||||
|
author: GitHub Action <action@github.com>
|
||||||
|
signoff: true
|
||||||
|
branch: sync_version
|
||||||
|
title: ":floppy_disk: Update Version"
|
||||||
|
body: |
|
||||||
|
Auto-generated by [create-pull-request][1]
|
||||||
|
|
||||||
|
[1]: https://github.com/peter-evans/create-pull-request
|
||||||
|
draft: false
|
||||||
|
delete-branch: true
|
||||||
|
labels: github-actions
|
||||||
sync-readme:
|
sync-readme:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,7 +4,6 @@ bin/
|
|||||||
tmp/
|
tmp/
|
||||||
*.tmp
|
*.tmp
|
||||||
*.bak
|
*.bak
|
||||||
*.exe
|
|
||||||
*.swp
|
*.swp
|
||||||
*~.nib
|
*~.nib
|
||||||
local.properties
|
local.properties
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ If you would like to add or modify a translation, please see [How to add new lan
|
|||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/).
|
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||||
|
|
||||||
## Fixing Bugs or Adding a New Feature
|
## Fixing Bugs or Adding a New Feature
|
||||||
|
|
||||||
@@ -41,4 +41,4 @@ If, at any point of time, you have a question, please feel free to ask in the sa
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
By contributing to this project, you agree that your contributions will be licensed under the [GPL 3 License](LICENSE). You also acknowledge and agree that your contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# New Database Backup and Import Functionality
|
# New Database Backup and Import Functionality
|
||||||
|
|
||||||
> [!IMPORTANT]
|
**Full activation will take place on approximately January 5th, 2025!**
|
||||||
> **Full activation will take place on approximately January 5th, 2025!**
|
|
||||||
|
|
||||||
Why is the waiting time six months?
|
Why is the waiting time six months?
|
||||||
|
|
||||||
|
|||||||
@@ -1,575 +0,0 @@
|
|||||||
# Stirling-PDF Developer Guide
|
|
||||||
|
|
||||||
## 1. Introduction
|
|
||||||
|
|
||||||
Stirling-PDF is a robust, locally hosted web-based PDF manipulation tool. This guide focuses on Docker-based development and testing, which is the recommended approach for working with the full version of Stirling-PDF.
|
|
||||||
|
|
||||||
## 2. Project Overview
|
|
||||||
|
|
||||||
Stirling-PDF is built using:
|
|
||||||
|
|
||||||
- Spring Boot + Thymeleaf
|
|
||||||
- PDFBox
|
|
||||||
- LibreOffice
|
|
||||||
- OcrMyPdf
|
|
||||||
- HTML, CSS, JavaScript
|
|
||||||
- Docker
|
|
||||||
- PDF.js
|
|
||||||
- PDF-LIB.js
|
|
||||||
- Lombok
|
|
||||||
|
|
||||||
## 3. Development Environment Setup
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Docker
|
|
||||||
- Git
|
|
||||||
- Java JDK 17 or later
|
|
||||||
- Gradle 7.0 or later (Included within repo)
|
|
||||||
|
|
||||||
### Setup Steps
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/Stirling-Tools/Stirling-PDF.git
|
|
||||||
cd Stirling-PDF
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install Docker and JDK17 if not already installed.
|
|
||||||
|
|
||||||
3. Install a recommended Java IDE such as Eclipse, IntelliJ or VSCode
|
|
||||||
|
|
||||||
4. Lombok Setup
|
|
||||||
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
|
||||||
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
|
|
||||||
|
|
||||||
5. Add environment variable
|
|
||||||
For local testing you should generally be testing the full 'Security' version of Stirling-PDF to do this you must add the environment flag DOCKER_ENABLE_SECURITY=true to your system and/or IDE build/run step
|
|
||||||
|
|
||||||
## 4. Project Structure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Stirling-PDF/
|
|
||||||
├── .github/ # GitHub-specific files (workflows, issue templates)
|
|
||||||
├── configs/ # Configuration files used by stirling at runtime (generated at runtime)
|
|
||||||
├── cucumber/ # Cucumber test files
|
|
||||||
│ ├── features/
|
|
||||||
├── customFiles/ # Custom static files and templates (generated at runtime used to replace existing files)
|
|
||||||
├── docs/ # Documentation files
|
|
||||||
├── exampleYmlFiles/ # Example YAML configuration files
|
|
||||||
├── images/ # Image assets
|
|
||||||
├── pipeline/ # Pipeline-related files (generated at runtime)
|
|
||||||
├── scripts/ # Utility scripts
|
|
||||||
├── src/ # Source code
|
|
||||||
│ ├── main/
|
|
||||||
│ │ ├── java/
|
|
||||||
│ │ │ └── stirling/
|
|
||||||
│ │ │ └── software/
|
|
||||||
│ │ │ └── SPDF/
|
|
||||||
│ │ │ ├── config/
|
|
||||||
│ │ │ ├── controller/
|
|
||||||
│ │ │ ├── model/
|
|
||||||
│ │ │ ├── repository/
|
|
||||||
│ │ │ ├── service/
|
|
||||||
│ │ │ └── utils/
|
|
||||||
│ │ └── resources/
|
|
||||||
│ │ ├── static/
|
|
||||||
│ │ │ ├── css/
|
|
||||||
│ │ │ ├── js/
|
|
||||||
│ │ │ └── pdfjs/
|
|
||||||
│ │ └── templates/
|
|
||||||
│ └── test/
|
|
||||||
│ └── java/
|
|
||||||
│ └── stirling/
|
|
||||||
│ └── software/
|
|
||||||
│ └── SPDF/
|
|
||||||
├── build.gradle # Gradle build configuration
|
|
||||||
├── Dockerfile # Main Dockerfile
|
|
||||||
├── Dockerfile-ultra-lite # Dockerfile for ultra-lite version
|
|
||||||
├── Dockerfile-fat # Dockerfile for fat version
|
|
||||||
├── docker-compose.yml # Docker Compose configuration
|
|
||||||
└── test.sh # Test script to deploy all docker versions and run cuke tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Docker-based Development
|
|
||||||
|
|
||||||
Stirling-PDF offers several Docker versions:
|
|
||||||
|
|
||||||
- Full: All features included
|
|
||||||
- Ultra-Lite: Basic PDF operations only
|
|
||||||
- Fat: Includes additional libraries and fonts predownloaded
|
|
||||||
|
|
||||||
### Example Docker Compose Files
|
|
||||||
|
|
||||||
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory such as :
|
|
||||||
|
|
||||||
- `docker-compose-latest.yml`: Latest version without security features
|
|
||||||
- `docker-compose-latest-security.yml`: Latest version with security features enabled
|
|
||||||
- `docker-compose-latest-fat-security.yml`: Fat version with security features enabled
|
|
||||||
|
|
||||||
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Security
|
|
||||||
image: frooodle/s-pdf:latest
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- /stirling/latest/config:/configs:rw
|
|
||||||
- /stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
|
||||||
SECURITY_ENABLELOGIN: "true"
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
restart: on-failure:5
|
|
||||||
```
|
|
||||||
|
|
||||||
To use these example files, copy the desired file to your project root and rename it to `docker-compose.yml`, or specify the file explicitly when running Docker Compose:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose -f exampleYmlFiles/docker-compose-latest-security.yml up
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Docker Images
|
|
||||||
|
|
||||||
Stirling-PDF uses different Docker images for various configurations. The build process is controlled by environment variables and uses specific Dockerfile variants. Here's how to build the Docker images:
|
|
||||||
|
|
||||||
1. Set the security environment variable:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export DOCKER_ENABLE_SECURITY=false # or true for security-enabled builds
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Build the project with Gradle:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew clean build
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Build the Docker images:
|
|
||||||
|
|
||||||
For the latest version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest -f ./Dockerfile .
|
|
||||||
```
|
|
||||||
|
|
||||||
For the ultra-lite version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite .
|
|
||||||
```
|
|
||||||
|
|
||||||
For the fat version (with security enabled):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export DOCKER_ENABLE_SECURITY=true
|
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-fat -f ./Dockerfile-fat .
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase
|
|
||||||
|
|
||||||
## 6. Testing
|
|
||||||
|
|
||||||
### Comprehensive Testing Script
|
|
||||||
|
|
||||||
Stirling-PDF provides a `test.sh` script in the root directory. This script builds all versions of Stirling-PDF, checks that each version works, and runs Cucumber tests. It's recommended to run this script before submitting a final pull request.
|
|
||||||
|
|
||||||
To run the test script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./test.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This script performs the following actions:
|
|
||||||
|
|
||||||
1. Builds all Docker images (full, ultra-lite, fat)
|
|
||||||
2. Runs each version to ensure it starts correctly
|
|
||||||
3. Executes Cucumber tests against main version and ensures feature compatibility, in the event these tests fail your PR will not be merged
|
|
||||||
|
|
||||||
Note: The `test.sh` script will run automatically when you raise a PR. However, it's recommended to run it locally first to save resources and catch any issues early.
|
|
||||||
|
|
||||||
### Full Testing with Docker
|
|
||||||
|
|
||||||
1. Build and run the Docker container per the above instructions:
|
|
||||||
|
|
||||||
2. Access the application at `http://localhost:8080` and manually test all features developed.
|
|
||||||
|
|
||||||
### Local Testing (Java and UI Components)
|
|
||||||
|
|
||||||
For quick iterations and development of Java backend, JavaScript, and UI components, you can run and test Stirling-PDF locally without Docker. This approach allows you to work on and verify changes to:
|
|
||||||
|
|
||||||
- Java backend logic
|
|
||||||
- RESTful API endpoints
|
|
||||||
- JavaScript functionality
|
|
||||||
- User interface components and styling
|
|
||||||
- Thymeleaf templates
|
|
||||||
|
|
||||||
To run Stirling-PDF locally:
|
|
||||||
|
|
||||||
1. Compile and run the project using built in IDE methods or by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew bootRun
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Access the application at `http://localhost:8080` in your web browser.
|
|
||||||
|
|
||||||
3. Manually test the features you're working on through the UI.
|
|
||||||
|
|
||||||
4. For API changes, use tools like Postman or curl to test endpoints directly.
|
|
||||||
|
|
||||||
Important notes:
|
|
||||||
|
|
||||||
- Local testing doesn't include features that depend on external tools like OCRmyPDF, LibreOffice, or Python scripts.
|
|
||||||
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
|
|
||||||
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
|
|
||||||
|
|
||||||
## 7. Contributing
|
|
||||||
|
|
||||||
1. Fork the repository on GitHub.
|
|
||||||
2. Create a new branch for your feature or bug fix.
|
|
||||||
3. Make your changes and commit them with clear, descriptive messages and ensure any documentation is updated related to your changes.
|
|
||||||
4. Test your changes thoroughly in the Docker environment.
|
|
||||||
5. Run the `test.sh` script to ensure all versions build correctly and pass the Cucumber tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./test.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Push your changes to your fork.
|
|
||||||
7. Submit a pull request to the main repository.
|
|
||||||
8. See additional [contributing guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
When you raise a PR:
|
|
||||||
|
|
||||||
- The `test.sh` script will run automatically against your PR.
|
|
||||||
- The PR checks will verify versioning and dependency updates.
|
|
||||||
- Documentation will be automatically updated for dependency changes.
|
|
||||||
- Security issues will be checked using Snyk and PixeeBot.
|
|
||||||
|
|
||||||
Address any issues that arise from these checks before finalizing your pull request.
|
|
||||||
|
|
||||||
## 8. API Documentation
|
|
||||||
|
|
||||||
API documentation is available at `/swagger-ui/index.html` when running the application. You can also view the latest API documentation [here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/).
|
|
||||||
|
|
||||||
## 9. Customization
|
|
||||||
|
|
||||||
Stirling-PDF can be customized through environment variables or a `settings.yml` file. Key customization options include:
|
|
||||||
|
|
||||||
- Application name and branding
|
|
||||||
- Security settings
|
|
||||||
- UI customization
|
|
||||||
- Endpoint management
|
|
||||||
|
|
||||||
When using Docker, pass environment variables using the `-e` flag or in your `docker-compose.yml` file.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -p 8080:8080 -e APP_NAME="My PDF Tool" stirling-pdf:full
|
|
||||||
```
|
|
||||||
|
|
||||||
Refer to the main README for a full list of customization options.
|
|
||||||
|
|
||||||
## 10. Language Translations
|
|
||||||
|
|
||||||
For managing language translations that affect multiple files, Stirling-PDF provides a helper script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/scripts/replace_translation_line.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This script helps you make consistent replacements across language files.
|
|
||||||
|
|
||||||
When contributing translations:
|
|
||||||
|
|
||||||
1. Use the helper script for multi-file changes.
|
|
||||||
2. Ensure all language files are updated consistently.
|
|
||||||
3. The PR checks will verify consistency in language file updates.
|
|
||||||
|
|
||||||
Remember to test your changes thoroughly to ensure they don't break any existing functionality.
|
|
||||||
|
|
||||||
## Code examples
|
|
||||||
|
|
||||||
### Overview of Thymeleaf
|
|
||||||
|
|
||||||
Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot
|
|
||||||
|
|
||||||
### Thymeleaf overview
|
|
||||||
|
|
||||||
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
|
|
||||||
|
|
||||||
Some examples of this are:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
|
||||||
or
|
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
|
||||||
```
|
|
||||||
|
|
||||||
Where it uses the th:block, th: indicating its a special thymeleaf element to be used serverside in generating the html, and block being the actual element type.
|
|
||||||
In this case we are inserting the ``navbar`` entry within the ``fragments/navbar.html`` fragment into the ``th:block`` element.
|
|
||||||
|
|
||||||
They can be more complex such as:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
|
|
||||||
```
|
|
||||||
|
|
||||||
Which is the same as above but passes the parameters title and header into the fragment common.html to be used in its HTML generation
|
|
||||||
|
|
||||||
Thymeleaf can also be used to loop through objects or pass things from java side into html side.
|
|
||||||
|
|
||||||
```java
|
|
||||||
@GetMapping
|
|
||||||
public String newFeaturePage(Model model) {
|
|
||||||
model.addAttribute("exampleData", exampleData);
|
|
||||||
return "new-feature";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
in above example if exampleData is a list of plain java objects of class Person and within it you had id, name, age etc. You can reference it like so
|
|
||||||
|
|
||||||
```html
|
|
||||||
<tbody>
|
|
||||||
<!-- Use th:each to iterate over the list -->
|
|
||||||
<tr th:each="person : ${exampleData}">
|
|
||||||
<td th:text="${person.id}"></td>
|
|
||||||
<td th:text="${person.name}"></td>
|
|
||||||
<td th:text="${person.age}"></td>
|
|
||||||
<td th:text="${person.email}"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
```
|
|
||||||
|
|
||||||
This would generate n entries of tr for each person in exampleData
|
|
||||||
|
|
||||||
### Adding a New Feature to the Backend (API)
|
|
||||||
|
|
||||||
1. **Create a New Controller:**
|
|
||||||
- Create a new Java class in the `src/main/java/stirling/software/SPDF/controller/api` directory.
|
|
||||||
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
|
|
||||||
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.controller.api;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/new-feature")
|
|
||||||
@Tag(name = "General", description = "General APIs")
|
|
||||||
public class NewFeatureController {
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
|
||||||
public String newFeature() {
|
|
||||||
return "NewFeatureResponse"; // This refers to the NewFeatureResponse.html template presenting the user with the generated html from that file when they navigate to /api/v1/new-feature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Define the Service Layer:** (Not required but often useful)
|
|
||||||
- Create a new service class in the `src/main/java/stirling/software/SPDF/service` directory.
|
|
||||||
- Implement the business logic for the new feature.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.service;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class NewFeatureService {
|
|
||||||
|
|
||||||
public String getNewFeatureData() {
|
|
||||||
// Implement business logic here
|
|
||||||
return "New Feature Data";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2b. **Integrate the Service with the Controller:**
|
|
||||||
|
|
||||||
- Autowire the service class in the controller and use it to handle the API request.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.controller.api;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import stirling.software.SPDF.service.NewFeatureService;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/new-feature")
|
|
||||||
@Tag(name = "General", description = "General APIs")
|
|
||||||
public class NewFeatureController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private NewFeatureService newFeatureService;
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
|
||||||
public String newFeature() {
|
|
||||||
return newFeatureService.getNewFeatureData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding a New Feature to the Frontend (UI)
|
|
||||||
|
|
||||||
1. **Create a New Thymeleaf Template:**
|
|
||||||
- Create a new HTML file in the `src/main/resources/templates` directory.
|
|
||||||
- Use Thymeleaf attributes to dynamically generate content.
|
|
||||||
- Use `extract-page.html` as a base example for the HTML template, useful to ensure importing of the general layout, navbar and footer.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
|
||||||
<head>
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{newFeature.title}, header=#{newFeature.header})}"></th:block>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="page-container">
|
|
||||||
<div id="content-wrap">
|
|
||||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
|
||||||
<br><br>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-6 bg-card">
|
|
||||||
<div class="tool-header">
|
|
||||||
<span class="material-symbols-rounded tool-header-icon organize">upload</span>
|
|
||||||
<span class="tool-header-text" th:text="#{newFeature.header}"></span>
|
|
||||||
</div>
|
|
||||||
<form th:action="@{'/api/v1/new-feature'}" method="post" enctype="multipart/form-data">
|
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
|
||||||
<input type="hidden" id="customMode" name="customMode" value="">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="featureInput" th:text="#{newFeature.prompt}"></label>
|
|
||||||
<input type="text" class="form-control" id="featureInput" name="featureInput" th:placeholder="#{newFeature.placeholder}" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{newFeature.submit}"></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create a New Controller for the UI:**
|
|
||||||
- Create a new Java class in the `src/main/java/stirling/software/SPDF/controller/ui` directory.
|
|
||||||
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.controller.ui;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.ui.Model;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import stirling.software.SPDF.service.NewFeatureService;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/new-feature")
|
|
||||||
public class NewFeatureUIController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private NewFeatureService newFeatureService;
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public String newFeaturePage(Model model) {
|
|
||||||
model.addAttribute("newFeatureData", newFeatureService.getNewFeatureData());
|
|
||||||
return "new-feature";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Update the Navigation Bar:**
|
|
||||||
- Add a link to the new feature page in the navigation bar.
|
|
||||||
- Update the `src/main/resources/templates/fragments/navbar.html` file.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" th:href="@{/new-feature}">New Feature</a>
|
|
||||||
</li>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding New Translations to Existing Language Files in Stirling-PDF
|
|
||||||
|
|
||||||
When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide:
|
|
||||||
|
|
||||||
### 1. Locate Existing Language Files
|
|
||||||
|
|
||||||
Find the existing `messages.properties` files in the `src/main/resources` directory. You'll see files like:
|
|
||||||
|
|
||||||
- `messages.properties` (default, usually English)
|
|
||||||
- `messages_en_GB.properties`
|
|
||||||
- `messages_fr_FR.properties`
|
|
||||||
- `messages_de_DE.properties`
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
### 2. Add New Translation Entries
|
|
||||||
|
|
||||||
Open each of these files and add your new translation entries. For example, if you're adding a new feature called "PDF Splitter",
|
|
||||||
Use descriptive, hierarchical keys (e.g., `feature.element.description`)
|
|
||||||
you might add:
|
|
||||||
|
|
||||||
```properties
|
|
||||||
pdfSplitter.title=PDF Splitter
|
|
||||||
pdfSplitter.description=Split your PDF into multiple documents
|
|
||||||
pdfSplitter.button.split=Split PDF
|
|
||||||
pdfSplitter.input.pages=Enter page numbers to split
|
|
||||||
```
|
|
||||||
|
|
||||||
Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language.
|
|
||||||
|
|
||||||
### 3. Use Translations in Thymeleaf Templates
|
|
||||||
|
|
||||||
In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<h1 th:text="#{pdfSplitter.title}">PDF Splitter</h1>
|
|
||||||
<p th:text="#{pdfSplitter.description}">Split your PDF into multiple documents</p>
|
|
||||||
<input type="text" th:placeholder="#{pdfSplitter.input.pages}">
|
|
||||||
<button th:text="#{pdfSplitter.button.split}">Split PDF</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
|
||||||
@@ -1,46 +1,47 @@
|
|||||||
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript | Unoconv | Ghostscript |
|
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript |
|
||||||
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- |
|
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- |
|
||||||
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | |
|
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ |
|
||||||
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | |
|
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| crop | ✔️ | | | | | | | | | ✔️ | | | |
|
| crop | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| extract-page | ✔️ | | | | | | | | | ✔️ | | | |
|
| extract-page | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| merge-pdfs | ✔️ | | | | | | | | | ✔️ | | | |
|
| merge-pdfs | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| multi-page-layout | ✔️ | | | | | | | | | ✔️ | | | |
|
| multi-page-layout | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ | | |
|
| pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ |
|
||||||
| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | | | |
|
| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| remove-pages | ✔️ | | | | | | | | | ✔️ | | | |
|
| remove-pages | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| rotate-pdf | ✔️ | | | | | | | | | ✔️ | | | |
|
| rotate-pdf | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| scale-pages | ✔️ | | | | | | | | | ✔️ | | | |
|
| scale-pages | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| split-pdfs | ✔️ | | | | | | | | | ✔️ | | | |
|
| split-pdfs | ✔️ | | | | | | | | | ✔️ | |
|
||||||
| file-to-pdf | | ✔️ | | | ✔️ | ✔️ | | ✔️ | | | | ✔️ | |
|
| file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | |
|
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | |
|
||||||
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | | | |
|
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | |
|
||||||
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | ✔️ |
|
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | |
|
||||||
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | |
|
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | |
|
||||||
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
|
| pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| add-password | | | ✔️ | | | | | | | ✔️ | | | |
|
| xlsx-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||||
| add-watermark | | | ✔️ | | | | | | | ✔️ | | | |
|
| add-password | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| cert-sign | | | ✔️ | | | | | | | ✔️ | | | |
|
| add-watermark | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| remove-cert-sign | | | ✔️ | | | | | | | ✔️ | | | |
|
| cert-sign | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| change-permissions | | | ✔️ | | | | | | | ✔️ | | | |
|
| remove-cert-sign | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| remove-password | | | ✔️ | | | | | | | ✔️ | | | |
|
| change-permissions | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| sanitize-pdf | | | ✔️ | | | | | | | ✔️ | | | |
|
| remove-password | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| add-image | | | | ✔️ | | | | | | ✔️ | | | |
|
| sanitize-pdf | | | ✔️ | | | | | | | ✔️ | |
|
||||||
| add-page-numbers | | | | ✔️ | | | | | | ✔️ | | | |
|
| add-image | | | | ✔️ | | | | | | ✔️ | |
|
||||||
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | |
|
| add-page-numbers | | | | ✔️ | | | | | | ✔️ | |
|
||||||
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | |
|
| auto-rename | | | | ✔️ | | | | | | ✔️ | |
|
||||||
| compare | | | | ✔️ | | | | | | | ✔️ | | |
|
| change-metadata | | | | ✔️ | | | | | | ✔️ | |
|
||||||
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | ✔️ |
|
| compare | | | | ✔️ | | | | | | | ✔️ |
|
||||||
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
|
||||||
| extract-images | | | | ✔️ | | | | | | ✔️ | | | |
|
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
|
||||||
| flatten | | | | ✔️ | | | | | | | ✔️ | | |
|
| extract-images | | | | ✔️ | | | | | | ✔️ | |
|
||||||
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | |
|
| flatten | | | | ✔️ | | | | | | | ✔️ |
|
||||||
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
|
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | |
|
||||||
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
|
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
|
||||||
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | ✔️ |
|
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
|
||||||
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | |
|
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | |
|
||||||
| sign | | | | ✔️ | | | | | | | ✔️ | | |
|
| show-javascript | | | | ✔️ | | | | | | | ✔️ |
|
||||||
|
| sign | | | | ✔️ | | | | | | | ✔️ |
|
||||||
|
|||||||
@@ -1,41 +1,33 @@
|
|||||||
## User Guide for Local Directory Scanning and File Processing
|
## User Guide for Local Directory Scanning and File Processing
|
||||||
|
|
||||||
### Setting Up Watched Folders
|
### Setting Up Watched Folders:
|
||||||
|
|
||||||
- Create a folder where you want your files to be monitored. This is your 'watched folder'.
|
- Create a folder where you want your files to be monitored. This is your 'watched folder'.
|
||||||
- The default directory for this is `./pipeline/watchedFolders/`.
|
- The default directory for this is `./pipeline/watchedFolders/`
|
||||||
- Place any directories you want to be scanned into this folder. This folder should contain multiple folders, each for their own tasks and pipelines.
|
- Place any directories you want to be scanned into this folder, this folder should contain multiple folders each for their own tasks and pipelines.
|
||||||
|
|
||||||
### Configuring Processing with JSON Files
|
### Configuring Processing with JSON Files:
|
||||||
|
- In each directory you want processed (e.g `./pipeline/watchedFolders/officePrinter`), include a JSON configuration file.
|
||||||
- In each directory you want processed (e.g., `./pipeline/watchedFolders/officePrinter`), include a JSON configuration file.
|
- This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them) which can be made, configured and downloaded from Stirling-PDF Pipeline interface.r
|
||||||
- This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them). This can be made, configured, and downloaded from the Stirling-PDF Pipeline interface.
|
|
||||||
|
|
||||||
### Automatic Scanning and Processing
|
|
||||||
|
|
||||||
|
### Automatic Scanning and Processing:
|
||||||
- The system automatically checks the watched folder every minute for new directories and files to process.
|
- The system automatically checks the watched folder every minute for new directories and files to process.
|
||||||
- When a directory with a valid JSON configuration file is found, it begins processing the files inside according to the configuration.
|
- When a directory with a valid JSON configuration file is found, it begins processing the files inside as per the configuration.
|
||||||
|
|
||||||
### Processing Steps
|
|
||||||
|
|
||||||
|
### Processing Steps:
|
||||||
- Files in each directory are processed according to the instructions in the JSON file.
|
- Files in each directory are processed according to the instructions in the JSON file.
|
||||||
- This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to the next process.
|
- This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to next process.
|
||||||
|
|
||||||
### Results and Output
|
|
||||||
|
|
||||||
|
### Results and Output:
|
||||||
- After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location `./pipeline/finishedFolders/`.
|
- After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location `./pipeline/finishedFolders/`.
|
||||||
- Each processed file is named and organized according to the rules set in the JSON configuration.
|
- Each processed file is named and organized according to the rules set in the JSON configuration.
|
||||||
|
|
||||||
### Completion and Cleanup
|
### Completion and Cleanup:
|
||||||
|
|
||||||
- Once processing is complete, the original files in the watched folder's directory are removed.
|
- Once processing is complete, the original files in the watched folder's directory are removed.
|
||||||
- You can find the processed files in the designated output location.
|
- You can find the processed files in the designated output location.
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling:
|
||||||
|
|
||||||
- If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.
|
- If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.
|
||||||
|
|
||||||
### User Interaction
|
### User Interaction:
|
||||||
|
|
||||||
- As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.
|
- As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.
|
||||||
- The system handles the rest, including scanning, processing, and outputting results.
|
- The system handles the rest, including scanning, processing, and outputting results.
|
||||||
|
|||||||
@@ -1,47 +1,43 @@
|
|||||||
<p align="center">
|
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
||||||
<img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80">
|
|
||||||
<br>
|
|
||||||
<h1 align="center">Stirling-PDF</h1>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# How to add new languages to Stirling-PDF
|
# How to add new languages to Stirling-PDF
|
||||||
|
|
||||||
Fork Stirling-PDF and create a new branch out of `main`.
|
Fork Stirling-PDF and make a new branch out of Main
|
||||||
|
|
||||||
Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
|
Then add reference to the language in the navbar by adding a new language entry to the dropdown
|
||||||
|
|
||||||
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html)
|
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html
|
||||||
- Add a flag SVG file to: [flags directory](https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags)
|
and add a flag svg file to
|
||||||
|
https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags
|
||||||
|
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
|
||||||
|
If your language isn't represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
|
||||||
|
|
||||||
Any SVG flags are fine; most of the current ones were sourced from [here](https://flagicons.lipis.dev/). If your language isn't represented by a flag, choose a similar one, such as Saudi Arabia's flag for Arabic.
|
For example to add Polish you would add
|
||||||
|
|
||||||
For example, to add Polish, you would add:
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL">
|
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
|
||||||
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
||||||
</a>
|
</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
The `data-bs-language-code` is the code used to reference the file in the next step.
|
The data-language-code is the code used to reference the file in the next step.
|
||||||
|
|
||||||
### Add Language Property File
|
Start by copying the existing english property file
|
||||||
|
|
||||||
Start by copying the existing English property file:
|
[https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
|
||||||
|
|
||||||
- [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
|
Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties
|
||||||
|
|
||||||
Copy and rename it to `messages_{your data-bs-language-code here}.properties`. In the Polish example, you would set the name to `messages_pl_PL.properties`.
|
Then simply translate all property entries within that file and make a PR into main for others to use!
|
||||||
|
|
||||||
Then simply translate all property entries within that file and make a Pull Request (PR) into `main` for others to use!
|
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves)
|
||||||
|
|
||||||
If you do not have a Java IDE, I am happy to verify that the changes work once you raise the PR (but I won't be able to verify the translations themselves).
|
|
||||||
|
|
||||||
## Handling Untranslatable Strings
|
## Handling Untranslatable Strings
|
||||||
|
|
||||||
Sometimes, certain strings in the properties file may not require translation because they are the same in the target language or are universal (like names of protocols, certain terminologies, etc.). To ensure accurate statistics for language progress, these strings should be added to the `ignore_translation.toml` file located in the `scripts` directory. This will exclude them from the translation progress calculations.
|
Sometimes, certain strings in the properties file may not require translation because they are the same in the target language or are universal (like names of protocols, certain terminologies, etc.). To ensure accurate statistics for language progress, these strings should be added to the `ignore_translation.toml` file located in the `scripts` directory. This will exclude them from the translation progress calculations.
|
||||||
|
|
||||||
For example, if the English string `error=Error` does not need translation in Polish, add it to the `ignore_translation.toml` under the Polish section:
|
For example, if the English string error=Error does not need translation in Polish, add it to the ignore_translation.toml under the Polish section:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[pl_PL]
|
[pl_PL]
|
||||||
@@ -53,9 +49,7 @@ ignore = [
|
|||||||
|
|
||||||
## Add New Translation Tags
|
## Add New Translation Tags
|
||||||
|
|
||||||
> [!IMPORTANT]
|
- **Important**: If you add any new translation tags, they must first be added to the `messages_en_GB.properties` file. This ensures consistency across all language files.
|
||||||
> If you add any new translation tags, they must first be added to the `messages_en_GB.properties` file. This ensures consistency across all language files.
|
|
||||||
|
|
||||||
- New translation tags **must be added** to the `messages_en_GB.properties` file to maintain a reference for other languages.
|
- New translation tags **must be added** to the `messages_en_GB.properties` file to maintain a reference for other languages.
|
||||||
- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`).
|
- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`).
|
||||||
|
|
||||||
|
|||||||
@@ -3,37 +3,35 @@
|
|||||||
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
||||||
|
|
||||||
## My OCR used to work and now doesn't!
|
## My OCR used to work and now doesn't!
|
||||||
|
The paths have changed for the tessadata locations on new docker images, please use ``/usr/share/tessdata`` (Others should still work for backwards compatibility but might not)
|
||||||
The paths have changed for the tessdata locations on new Docker images. Please use `/usr/share/tessdata` (Others should still work for backward compatibility but might not).
|
|
||||||
|
|
||||||
## How does the OCR Work
|
## How does the OCR Work
|
||||||
|
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition.
|
||||||
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF), which in turn uses Tesseract for its text recognition. All credit goes to them for this awesome work!
|
All credit goes to them for this awesome work!
|
||||||
|
|
||||||
## Language Packs
|
## Language Packs
|
||||||
|
|
||||||
Tesseract OCR supports a variety of languages. You can find additional language packs in the Tesseract GitHub repositories:
|
Tesseract OCR supports a variety of languages. You can find additional language packs in the Tesseract GitHub repositories:
|
||||||
|
|
||||||
- [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast): These language packs are smaller and faster to load but may provide lower recognition accuracy.
|
- [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast): These language packs are smaller and faster to load, but may provide lower recognition accuracy.
|
||||||
- [tessdata](https://github.com/tesseract-ocr/tessdata): These language packs are larger and provide better recognition accuracy, but may take longer to load.
|
- [tessdata](https://github.com/tesseract-ocr/tessdata): These language packs are larger and provide better recognition accuracy, but may take longer to load.
|
||||||
|
|
||||||
Depending on your requirements, you can choose the appropriate language pack for your use case. By default, Stirling-PDF uses `tessdata_fast` for English, but this can be replaced.
|
Depending on your requirements, you can choose the appropriate language pack for your use case. By default Stirling-PDF uses the tessdata_fast eng but this can be replaced.
|
||||||
|
|
||||||
### Installing Language Packs
|
### Installing Language Packs
|
||||||
|
|
||||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
||||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
||||||
|
|
||||||
**DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.**
|
# DO NOT REMOVE EXISTING ENG.TRAINEDDATA, IT'S REQUIRED.
|
||||||
|
|
||||||
### Docker Setup
|
#### Docker
|
||||||
|
|
||||||
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
|
If you are using Docker, you need to expose the Tesseract tessdata directory as a volume in order to use the additional language packs.
|
||||||
|
|
||||||
#### Docker Compose
|
#### Docker Compose
|
||||||
|
|
||||||
Modify your `docker-compose.yml` file to include the following volume configuration:
|
Modify your `docker-compose.yml` file to include the following volume configuration:
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
your_service_name:
|
your_service_name:
|
||||||
@@ -42,19 +40,18 @@ services:
|
|||||||
- /location/of/trainingData:/usr/share/tessdata
|
- /location/of/trainingData:/usr/share/tessdata
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Docker Run
|
|
||||||
|
|
||||||
Add the following to your existing Docker run command:
|
|
||||||
|
|
||||||
|
#### Docker run
|
||||||
|
Add the following to your existing docker run command
|
||||||
```bash
|
```bash
|
||||||
-v /location/of/trainingData:/usr/share/tessdata
|
-v /location/of/trainingData:/usr/share/tessdata
|
||||||
```
|
```
|
||||||
|
|
||||||
### Non-Docker Setup
|
#### Non-Docker
|
||||||
|
If you are not using Docker, you need to install the OCR components, including the ocrmypdf app.
|
||||||
|
You can see [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html)
|
||||||
|
|
||||||
If you are not using Docker, you need to install the OCR components, including the `ocrmypdf` app. You can see the [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html).
|
Debian based systems, install languages with this command:
|
||||||
|
|
||||||
For Debian-based systems, install languages with this command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt update &&\
|
sudo apt update &&\
|
||||||
@@ -68,7 +65,7 @@ apt search tesseract-ocr-
|
|||||||
dpkg-query -W tesseract-ocr- | sed 's/tesseract-ocr-//g'
|
dpkg-query -W tesseract-ocr- | sed 's/tesseract-ocr-//g'
|
||||||
```
|
```
|
||||||
|
|
||||||
For Fedora:
|
Fedora:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All languages
|
# All languages
|
||||||
@@ -80,23 +77,3 @@ dnf search -C tesseract-langpack-
|
|||||||
# View installed languages:
|
# View installed languages:
|
||||||
rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
|
rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
|
||||||
```
|
```
|
||||||
|
|
||||||
For Windows:
|
|
||||||
|
|
||||||
Ensure ocrmypdf in installed with
|
|
||||||
``pip install ocrmypdf``
|
|
||||||
|
|
||||||
Additional languages must be downloaded manually:
|
|
||||||
Download desired .traineddata files from tessdata or tessdata_fast
|
|
||||||
Place them in the tessdata folder within your Tesseract installation directory
|
|
||||||
(e.g., C:\Program Files\Tesseract-OCR\tessdata)
|
|
||||||
|
|
||||||
Verify installation:
|
|
||||||
``tesseract --list-langs``
|
|
||||||
|
|
||||||
You must then edit your ``/configs/settings.yml`` and change the system.tessdataDir to match the directory containing lang files
|
|
||||||
```
|
|
||||||
system:
|
|
||||||
tessdataDir: C:/Program Files/Tesseract-OCR/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
147
LocalRunGuide.md
147
LocalRunGuide.md
@@ -1,35 +1,48 @@
|
|||||||
|
|
||||||
To run the application without Docker/Podman, you will need to manually install all dependencies and build the necessary components.
|
To run the application without Docker/Podman, you will need to manually install all dependencies and build the necessary components.
|
||||||
|
|
||||||
Note that some dependencies might not be available in the standard repositories of all Linux distributions, and may require additional steps to install.
|
Note that some dependencies might not be available in the standard repositories of all Linux distributions, and may require additional steps to install.
|
||||||
|
|
||||||
The following guide assumes you have a basic understanding of using a command line interface in your operating system.
|
The following guide assumes you have a basic understanding of using a command line interface in your operating system.
|
||||||
|
|
||||||
It should work on most Linux distributions and MacOS. For Windows, you might need to use Windows Subsystem for Linux (WSL) for certain steps. The amount of dependencies is to actually reduce overall size, i.e., installing LibreOffice subcomponents rather than the full LibreOffice package.
|
It should work on most Linux distributions and MacOS. For Windows, you might need to use Windows Subsystem for Linux (WSL) for certain steps.
|
||||||
|
The amount of dependencies is to actually reduce overall size, ie installing LibreOffice sub components rather than full LibreOffice package.
|
||||||
|
|
||||||
You could theoretically use a Distrobox/Toolbox if your distribution has old or not all packages. But you might just as well use the Docker container then.
|
You could theoretically use a Distrobox/Toolbox, if your Distribution has old or not all Packages. But you might just as well use the Docker Container then.
|
||||||
|
|
||||||
### Step 1: Prerequisites
|
### Step 1: Prerequisites
|
||||||
|
|
||||||
Install the following software, if not already installed:
|
Install the following software, if not already installed:
|
||||||
|
|
||||||
- Java 17 or later (21 recommended)
|
- Java 17 or later (21 recommended)
|
||||||
|
|
||||||
- Gradle 7.0 or later (included within repo so not needed on server)
|
- Gradle 7.0 or later (included within repo so not needed on server)
|
||||||
|
|
||||||
- Git
|
- Git
|
||||||
|
|
||||||
- Python 3.8 (with pip)
|
- Python 3.8 (with pip)
|
||||||
|
|
||||||
- Make
|
- Make
|
||||||
|
|
||||||
- GCC/G++
|
- GCC/G++
|
||||||
|
|
||||||
- Automake
|
- Automake
|
||||||
|
|
||||||
- Autoconf
|
- Autoconf
|
||||||
|
|
||||||
- libtool
|
- libtool
|
||||||
|
|
||||||
- pkg-config
|
- pkg-config
|
||||||
|
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
|
|
||||||
- libleptonica-dev
|
- libleptonica-dev
|
||||||
|
|
||||||
For Debian-based systems, you can use the following command:
|
For Debian-based systems, you can use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ openjdk-21-jdk python3 python3-pip
|
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ openjdk-21-jdk python3 python3-pip
|
||||||
```
|
```
|
||||||
|
|
||||||
For Fedora-based systems use this command:
|
For Fedora-based systems use this command:
|
||||||
@@ -39,7 +52,6 @@ sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zli
|
|||||||
```
|
```
|
||||||
|
|
||||||
For non-root users with Nix Package Manager, use the following command:
|
For non-root users with Nix Package Manager, use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix-channel --update
|
nix-channel --update
|
||||||
nix-env -iA nixpkgs.jdk21 nixpkgs.git nixpkgs.python38 nixpkgs.gnumake nixpkgs.libgcc nixpkgs.automake nixpkgs.autoconf nixpkgs.libtool nixpkgs.pkg-config nixpkgs.zlib nixpkgs.leptonica
|
nix-env -iA nixpkgs.jdk21 nixpkgs.git nixpkgs.python38 nixpkgs.gnumake nixpkgs.libgcc nixpkgs.automake nixpkgs.autoconf nixpkgs.libtool nixpkgs.pkg-config nixpkgs.zlib nixpkgs.leptonica
|
||||||
@@ -51,37 +63,45 @@ For Debian and Fedora, you can build it from source using the following commands
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir ~/.git
|
mkdir ~/.git
|
||||||
cd ~/.git && \
|
cd ~/.git &&\
|
||||||
git clone https://github.com/agl/jbig2enc.git && \
|
git clone https://github.com/agl/jbig2enc.git &&\
|
||||||
cd jbig2enc && \
|
cd jbig2enc &&\
|
||||||
./autogen.sh && \
|
./autogen.sh &&\
|
||||||
./configure && \
|
./configure &&\
|
||||||
make && \
|
make &&\
|
||||||
sudo make install
|
sudo make install
|
||||||
```
|
```
|
||||||
|
|
||||||
For Nix, you will face `Leptonica not detected`. Bypass this by installing it directly using the following command:
|
For Nix, you will face `Leptonica not detected`. Bypass this by installing it directly using the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix-env -iA nixpkgs.jbig2enc
|
nix-env -iA nixpkgs.jbig2enc
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Install Additional Software
|
### Step 3: Install Additional Software
|
||||||
|
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality.
|
||||||
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and OpenCV for pattern recognition functionality.
|
|
||||||
|
|
||||||
Install the following software:
|
Install the following software:
|
||||||
|
|
||||||
- libreoffice-core
|
- libreoffice-core
|
||||||
|
|
||||||
- libreoffice-common
|
- libreoffice-common
|
||||||
|
|
||||||
- libreoffice-writer
|
- libreoffice-writer
|
||||||
|
|
||||||
- libreoffice-calc
|
- libreoffice-calc
|
||||||
|
|
||||||
- libreoffice-impress
|
- libreoffice-impress
|
||||||
|
|
||||||
- python3-uno
|
- python3-uno
|
||||||
|
|
||||||
- unoconv
|
- unoconv
|
||||||
|
|
||||||
- pngquant
|
- pngquant
|
||||||
|
|
||||||
- unpaper
|
- unpaper
|
||||||
|
|
||||||
- ocrmypdf
|
- ocrmypdf
|
||||||
|
|
||||||
- opencv-python-headless
|
- opencv-python-headless
|
||||||
|
|
||||||
For Debian-based systems, you can use the following command:
|
For Debian-based systems, you can use the following command:
|
||||||
@@ -108,52 +128,51 @@ pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
|||||||
### Step 4: Clone and Build Stirling-PDF
|
### Step 4: Clone and Build Stirling-PDF
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/.git && \
|
cd ~/.git &&\
|
||||||
git clone https://github.com/Stirling-Tools/Stirling-PDF.git && \
|
git clone https://github.com/Stirling-Tools/Stirling-PDF.git &&\
|
||||||
cd Stirling-PDF && \
|
cd Stirling-PDF &&\
|
||||||
chmod +x ./gradlew && \
|
chmod +x ./gradlew &&\
|
||||||
./gradlew build
|
./gradlew build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 5: Move Jar to Desired Location
|
### Step 5: Move jar to desired location
|
||||||
|
|
||||||
After the build process, a `.jar` file will be generated in the `build/libs` directory. You can move this file to a desired location, for example, `/opt/Stirling-PDF/`. You must also move the Script folder within the Stirling-PDF repo that you have downloaded to this directory. This folder is required for the Python scripts using OpenCV.
|
After the build process, a `.jar` file will be generated in the `build/libs` directory.
|
||||||
|
You can move this file to a desired location, for example, `/opt/Stirling-PDF/`.
|
||||||
|
You must also move the Script folder within the Stirling-PDF repo that you have downloaded to this directory.
|
||||||
|
This folder is required for the python scripts using OpenCV.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir /opt/Stirling-PDF && \
|
sudo mkdir /opt/Stirling-PDF &&\
|
||||||
sudo mv ./build/libs/Stirling-PDF-*.jar /opt/Stirling-PDF/ && \
|
sudo mv ./build/libs/Stirling-PDF-*.jar /opt/Stirling-PDF/ &&\
|
||||||
sudo mv scripts /opt/Stirling-PDF/ && \
|
sudo mv scripts /opt/Stirling-PDF/ &&\
|
||||||
echo "Scripts installed."
|
echo "Scripts installed."
|
||||||
```
|
```
|
||||||
|
|
||||||
For non-root users, you can just keep the jar in the main directory of Stirling-PDF using the following command:
|
For non-root users, you can just keep the jar in the main directory of Stirling-PDF using the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mv ./build/libs/Stirling-PDF-*.jar ./Stirling-PDF-*.jar
|
mv ./build/libs/Stirling-PDF-*.jar ./Stirling-PDF-*.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 6: Other Files
|
### Step 6: Other files
|
||||||
|
|
||||||
#### OCR
|
#### OCR
|
||||||
|
If you plan to use the OCR (Optical Character Recognition) functionality, you might need to install language packs for Tesseract if running non-english scanning.
|
||||||
If you plan to use the OCR (Optical Character Recognition) functionality, you might need to install language packs for Tesseract if running non-English scanning.
|
|
||||||
|
|
||||||
##### Installing Language Packs
|
##### Installing Language Packs
|
||||||
|
Easiest is to use the langpacks provided by your repositories. Skip the other steps.
|
||||||
|
|
||||||
The easiest method is to use the language packs provided by your repositories. Skip the other steps if they are available.
|
Manual:
|
||||||
|
|
||||||
**Manual:**
|
|
||||||
|
|
||||||
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
|
||||||
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
|
||||||
3. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
|
3. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
|
||||||
|
|
||||||
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
|
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
|
||||||
|
|
||||||
**Debian-based systems**, install languages with this command:
|
Debian based systems, install languages with this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt update && \
|
sudo apt update &&\
|
||||||
# All languages
|
# All languages
|
||||||
# sudo apt install -y 'tesseract-ocr-*'
|
# sudo apt install -y 'tesseract-ocr-*'
|
||||||
|
|
||||||
@@ -164,7 +183,7 @@ apt search tesseract-ocr-
|
|||||||
dpkg-query -W tesseract-ocr- | sed 's/tesseract-ocr-//g'
|
dpkg-query -W tesseract-ocr- | sed 's/tesseract-ocr-//g'
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fedora:**
|
Fedora:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All languages
|
# All languages
|
||||||
@@ -177,13 +196,13 @@ dnf search -C tesseract-langpack-
|
|||||||
rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
|
rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
|
||||||
```
|
```
|
||||||
|
|
||||||
**Nix:**
|
Nix:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix-env -iA nixpkgs.tesseract
|
nix-env -iA nixpkgs.tesseract
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Nix Package Manager pre-installs almost all the language packs when Tesseract is installed.
|
**Note:** Nix Package Manager pre-installs almost all the language packs when tesseract is installed.
|
||||||
|
|
||||||
### Step 7: Run Stirling-PDF
|
### Step 7: Run Stirling-PDF
|
||||||
|
|
||||||
@@ -195,13 +214,11 @@ or
|
|||||||
java -jar /opt/Stirling-PDF/Stirling-PDF-*.jar
|
java -jar /opt/Stirling-PDF/Stirling-PDF-*.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
Since LibreOffice, soffice, and conversion tools have their dbus_tmp_dir set as `dbus_tmp_dir="/run/user/$(id -u)/libreoffice-dbus"`, you might get the following error when using their endpoints:
|
Since libreoffice, soffice, and conversion tools have their dbus_tmp_dir set as `dbus_tmp_dir="/run/user/$(id -u)/libreoffice-dbus"`, you might get the following error when using their endpoints:
|
||||||
|
|
||||||
```
|
```
|
||||||
[Thread-7] INFO s.s.SPDF.utils.ProcessExecutor - mkdir: cannot create directory ‘/run/user/1501’: Permission denied
|
[Thread-7] INFO s.s.SPDF.utils.ProcessExecutor - mkdir: cannot create directory ‘/run/user/1501’: Permission denied
|
||||||
```
|
```
|
||||||
|
To resolve this, before starting the Stirling-PDF, you have to set the environment variable to a directory you have write access to by using the following commands:
|
||||||
To resolve this, before starting Stirling-PDF, you have to set the environment variable to a directory you have write access to by using the following commands:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir temp
|
mkdir temp
|
||||||
@@ -211,10 +228,9 @@ or
|
|||||||
java -jar ./Stirling-PDF-*.jar
|
java -jar ./Stirling-PDF-*.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 8: Adding a Desktop Icon
|
### Step 8: Adding a Desktop icon
|
||||||
|
|
||||||
This will add a modified app starter to your app menu.
|
|
||||||
|
|
||||||
|
This will add a modified Appstarter to your Appmenu.
|
||||||
```bash
|
```bash
|
||||||
location=$(pwd)/gradlew
|
location=$(pwd)/gradlew
|
||||||
image=$(pwd)/docs/stirling-transparent.svg
|
image=$(pwd)/docs/stirling-transparent.svg
|
||||||
@@ -235,40 +251,35 @@ EOF
|
|||||||
|
|
||||||
Note: Currently the app will run in the background until manually closed.
|
Note: Currently the app will run in the background until manually closed.
|
||||||
|
|
||||||
### Optional: Changing the Host and Port of the Application
|
### Optional: Changing the host and port of the application:
|
||||||
|
|
||||||
To override the default configuration, you can add the following to `/.git/Stirling-PDF/configs/custom_settings.yml` file:
|
To override the default configuration, you can add the following to `/.git/Stirling-PDF/configs/custom_settings.yml` file:
|
||||||
|
|
||||||
```yaml
|
```bash
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0 # Not working - use instead address
|
host: 0.0.0.0 # Not working - use instead address
|
||||||
address: 0.0.0.0
|
address: 0.0.0.0
|
||||||
port: 3000
|
port: 3000
|
||||||
```
|
```
|
||||||
|
'-Djava.net.preferIPv4Stack=true' --> To force ipv4 only in the java starting command
|
||||||
`-Djava.net.preferIPv4Stack=true` --> To force IPv4 only in the Java starting command
|
|
||||||
|
|
||||||
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
||||||
|
|
||||||
### Optional: Run Stirling-PDF as a Service (requires root)
|
### Optional: Run Stirling-PDF as a service (requires root).
|
||||||
|
|
||||||
First create a `.env` file, where you can store environment variables:
|
First create a .env file, where you can store environment variables:
|
||||||
|
```
|
||||||
```bash
|
|
||||||
touch /opt/Stirling-PDF/.env
|
touch /opt/Stirling-PDF/.env
|
||||||
```
|
```
|
||||||
|
In this file you can add all variables, one variable per line, as stated in the main readme (for example SYSTEM_DEFAULTLOCALE="de-DE").
|
||||||
|
|
||||||
In this file, you can add all variables, one variable per line, as stated in the main readme (for example `SYSTEM_DEFAULTLOCALE="de-DE"`).
|
Create a new file where we store our service settings and open it with nano editor:
|
||||||
|
```
|
||||||
Create a new file where we store our service settings and open it with the nano editor:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nano /etc/systemd/system/stirlingpdf.service
|
nano /etc/systemd/system/stirlingpdf.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Paste this content, make sure to update the filename of the jar file. Press `Ctrl+S` and `Ctrl+X` to save and exit the nano editor:
|
Paste this content, make sure to update the filename of the jar-file. Press Ctrl+S and Ctrl+X to save and exit the nano editor:
|
||||||
|
```
|
||||||
```ini
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Stirling-PDF service
|
Description=Stirling-PDF service
|
||||||
After=syslog.target network.target
|
After=syslog.target network.target
|
||||||
@@ -292,25 +303,22 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
Notify systemd that it has to rebuild its internal service database (you have to run this command every time you make a change in the service file):
|
Notify systemd that it has to rebuild its internal service database (you have to run this command every time you make a change in the service file):
|
||||||
|
|
||||||
```bash
|
```
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable the service to tell it to start automatically:
|
Enable the service to tell the service to start it automatically:
|
||||||
|
```
|
||||||
```bash
|
|
||||||
sudo systemctl enable stirlingpdf.service
|
sudo systemctl enable stirlingpdf.service
|
||||||
```
|
```
|
||||||
|
|
||||||
See the status of the service:
|
See the status of the service:
|
||||||
|
```
|
||||||
```bash
|
|
||||||
sudo systemctl status stirlingpdf.service
|
sudo systemctl status stirlingpdf.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Manually start/stop/restart the service:
|
Manually start/stop/restart the service:
|
||||||
|
```
|
||||||
```bash
|
|
||||||
sudo systemctl start stirlingpdf.service
|
sudo systemctl start stirlingpdf.service
|
||||||
sudo systemctl stop stirlingpdf.service
|
sudo systemctl stop stirlingpdf.service
|
||||||
sudo systemctl restart stirlingpdf.service
|
sudo systemctl restart stirlingpdf.service
|
||||||
@@ -318,11 +326,12 @@ sudo systemctl restart stirlingpdf.service
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Remember to set the necessary environment variables before running the project if you want to customize the application. The list can be seen in the main readme.
|
Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme.
|
||||||
|
|
||||||
You can do this in the terminal by using the `export` command or `-D` argument to the Java `-jar` command:
|
You can do this in the terminal by using the `export` command or -D argument to java -jar command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export APP_HOME_NAME="Stirling PDF"
|
export APP_HOME_NAME="Stirling PDF"
|
||||||
or
|
or
|
||||||
-DAPP_HOME_NAME="Stirling PDF"
|
-DAPP_HOME_NAME="Stirling PDF"
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Pipeline Configuration and Usage Tutorial
|
# Pipeline Configuration and Usage Tutorial
|
||||||
|
- Configure the pipeline config file and input files to run files against it
|
||||||
- Configure the pipeline config file and input files to run files against it.
|
- For reuse, download the config file and re-upload it when needed, or place it in /pipeline/defaultWebUIConfigs/ to auto-load in the web UI for all users
|
||||||
- For reuse, download the config file and re-upload it when needed, or place it in `/pipeline/defaultWebUIConfigs/` to auto-load in the web UI for all users.
|
|
||||||
|
|
||||||
## Steps to Configure and Use Your Pipeline
|
## Steps to Configure and Use Your Pipeline
|
||||||
|
|
||||||
@@ -27,16 +26,19 @@
|
|||||||
- Use the **Validation** button to check your pipeline. A green indicator signifies correct setup; a pop-out error indicates issues.
|
- Use the **Validation** button to check your pipeline. A green indicator signifies correct setup; a pop-out error indicates issues.
|
||||||
|
|
||||||
8. **Download Pipeline Configuration**
|
8. **Download Pipeline Configuration**
|
||||||
- To use the configuration for folder scanning (or save it for future use and re-upload it), download a JSON file in this menu. You can also pre-load it for future use by placing it in `/pipeline/defaultWebUIConfigs/`. It will then appear in the dropdown menu for all users to use.
|
- To use the configuration for folder scanning (or save it for future use and reupload it), you can also download a JSON file in this menu. You can also pre-load this for future use by placing it in ``/pipeline/defaultWebUIConfigs/``. It will then appear in the dropdown menu for all users to use.
|
||||||
|
|
||||||
9. **Submit Files for Processing**
|
9. **Submit Files for Processing**
|
||||||
- If your pipeline is correctly set up, close the configure menu, input the files, and hit **Submit**.
|
- If your pipeline is correctly set up close the configure menu, input the files and hit **Submit**.
|
||||||
|
|
||||||
10. **Note on Web UI Limitations**
|
10. **Note on Web UI Limitations**
|
||||||
- The current web UI version does not support operations that require multiple different types of inputs, such as adding a separate image to a PDF.
|
- The current web UI version does not support operations that require multiple different types of inputs, such as adding a separate image to a PDF.
|
||||||
|
|
||||||
|
|
||||||
### Current Limitations
|
### Current Limitations
|
||||||
|
- Cannot have more than one of the same operation
|
||||||
|
- Cannot input additional files via UI
|
||||||
|
- All files and operations run in serial mode
|
||||||
|
|
||||||
- Cannot have more than one of the same operation.
|
|
||||||
- Cannot input additional files via UI.
|
|
||||||
- All files and operations run in serial mode.
|
|
||||||
440
README.md
440
README.md
@@ -1,4 +1,4 @@
|
|||||||
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80"></p>
|
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ></p>
|
||||||
<h1 align="center">Stirling-PDF</h1>
|
<h1 align="center">Stirling-PDF</h1>
|
||||||
|
|
||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
@@ -7,10 +7,11 @@
|
|||||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||||
|
|
||||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
[](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)
|
||||||
|
|
||||||
[Stirling-PDF](https://www.stirlingpdf.com) 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.
|
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.
|
||||||
|
|
||||||
Stirling-PDF does not initiate any outbound calls for record-keeping or tracking purposes.
|
Stirling PDF does not initiate any outbound calls for record-keeping or tracking purposes.
|
||||||
|
|
||||||
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
|
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
|
||||||
|
|
||||||
@@ -18,8 +19,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
- Dark mode support.
|
||||||
- Dark mode support
|
|
||||||
- Custom download options
|
- Custom download options
|
||||||
- Parallel file processing and downloads
|
- Parallel file processing and downloads
|
||||||
- Custom 'Pipelines' to run multiple features in a queue
|
- Custom 'Pipelines' to run multiple features in a queue
|
||||||
@@ -27,76 +27,68 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
|||||||
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
|
- 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)
|
- Database Backup and Import (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DATABASE.md) for documentation)
|
||||||
|
|
||||||
|
## **PDF Features**
|
||||||
|
|
||||||
## PDF Features
|
### **Page Operations**
|
||||||
|
|
||||||
### Page Operations
|
- View and modify PDFs - View multi page PDFs with custom viewing sorting and searching. Plus on page edit features like annotate, draw and adding text and images. (Using PDF.js with Joxit and Liberation.Liberation fonts)
|
||||||
|
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
|
||||||
|
- Merge multiple PDFs together into a single resultant file.
|
||||||
|
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
||||||
|
- Reorganize PDF pages into different orders.
|
||||||
|
- Rotate PDFs in 90-degree increments.
|
||||||
|
- Remove pages.
|
||||||
|
- Multi-page layout (Format PDFs into a multi-paged page).
|
||||||
|
- Scale page contents size by set %.
|
||||||
|
- Adjust Contrast.
|
||||||
|
- Crop PDF.
|
||||||
|
- Auto Split PDF (With physically scanned page dividers).
|
||||||
|
- Extract page(s).
|
||||||
|
- Convert PDF to a single page.
|
||||||
|
- Overlay PDFs ontop of each other
|
||||||
|
|
||||||
- View and modify PDFs - View multi-page PDFs with custom viewing, sorting, and searching. Plus on-page edit features like annotate, draw, and adding text and images. (Using PDF.js with Joxit and Liberation fonts)
|
### **Conversion Operations**
|
||||||
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages
|
|
||||||
- Merge multiple PDFs into a single resultant file
|
|
||||||
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files
|
|
||||||
- Reorganize PDF pages into different orders
|
|
||||||
- Rotate PDFs in 90-degree increments
|
|
||||||
- Remove pages
|
|
||||||
- Multi-page layout (format PDFs into a multi-paged page)
|
|
||||||
- Scale page contents size by set percentage
|
|
||||||
- Adjust contrast
|
|
||||||
- Crop PDF
|
|
||||||
- Auto split PDF (with physically scanned page dividers)
|
|
||||||
- Extract page(s)
|
|
||||||
- Convert PDF to a single page
|
|
||||||
- Overlay PDFs on top of each other
|
|
||||||
- PDF to single page
|
|
||||||
- Split PDF by sections
|
|
||||||
|
|
||||||
### Conversion Operations
|
- Convert PDFs to and from images.
|
||||||
|
- Convert any common file to PDF (using LibreOffice).
|
||||||
|
- Convert PDF to Word/Powerpoint/Others (using LibreOffice).
|
||||||
|
- Convert HTML to PDF.
|
||||||
|
- URL to PDF.
|
||||||
|
- Markdown to PDF.
|
||||||
|
|
||||||
- Convert PDFs to and from images
|
### **Security & Permissions**
|
||||||
- Convert any common file to PDF (using LibreOffice)
|
|
||||||
- Convert PDF to Word/PowerPoint/others (using LibreOffice)
|
|
||||||
- Convert HTML to PDF
|
|
||||||
- Convert PDF to xml
|
|
||||||
- Convert PDF to CSV
|
|
||||||
- URL to PDF
|
|
||||||
- Markdown to PDF
|
|
||||||
|
|
||||||
### Security & Permissions
|
- Add and remove passwords.
|
||||||
|
- Change/set PDF Permissions.
|
||||||
|
- Add watermark(s).
|
||||||
|
- Certify/sign PDFs.
|
||||||
|
- Sanitize PDFs.
|
||||||
|
- Auto-redact text.
|
||||||
|
|
||||||
- Add and remove passwords
|
### **Other Operations**
|
||||||
- Change/set PDF permissions
|
|
||||||
- Add watermark(s)
|
|
||||||
- Certify/sign PDFs
|
|
||||||
- Sanitize PDFs
|
|
||||||
- Auto-redact text
|
|
||||||
|
|
||||||
### Other Operations
|
- Add/Generate/Write signatures.
|
||||||
|
- Repair PDFs.
|
||||||
|
- Detect and remove blank pages.
|
||||||
|
- Compare 2 PDFs and show differences in text.
|
||||||
|
- Add images to PDFs.
|
||||||
|
- Compress PDFs to decrease their filesize (Using OCRMyPDF).
|
||||||
|
- Extract images from PDF.
|
||||||
|
- Extract images from Scans.
|
||||||
|
- Add page numbers.
|
||||||
|
- Auto rename file by detecting PDF header text.
|
||||||
|
- OCR on PDF (Using OCRMyPDF).
|
||||||
|
- PDF/A conversion (Using OCRMyPDF).
|
||||||
|
- Edit metadata.
|
||||||
|
- Flatten PDFs.
|
||||||
|
- Get all information on a PDF to view or export as JSON.
|
||||||
|
- Show/Detect embedded Javascript
|
||||||
|
|
||||||
- Add/generate/write signatures
|
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)
|
||||||
- Split by Size or PDF
|
|
||||||
- Repair PDFs
|
|
||||||
- Detect and remove blank pages
|
|
||||||
- Compare two PDFs and show differences in text
|
|
||||||
- Add images to PDFs
|
|
||||||
- Compress PDFs to decrease their filesize (using OCRMyPDF)
|
|
||||||
- Extract images from PDF
|
|
||||||
- Remove images from PDF
|
|
||||||
- Extract images from scans
|
|
||||||
- Remove annotations
|
|
||||||
- Add page numbers
|
|
||||||
- Auto rename file by detecting PDF header text
|
|
||||||
- OCR on PDF (using OCRMyPDF)
|
|
||||||
- PDF/A conversion (using OCRMyPDF)
|
|
||||||
- Edit metadata
|
|
||||||
- Flatten PDFs
|
|
||||||
- Get all information on a PDF to view or export as JSON
|
|
||||||
- Show/detect embedded JavaScript
|
|
||||||
|
|
||||||
For an 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).
|
||||||
|
|
||||||
A demo of the app is available [here](https://stirlingpdf.io).
|
## Technologies used
|
||||||
|
|
||||||
## Technologies Used
|
|
||||||
|
|
||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
|
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
|
||||||
@@ -107,28 +99,27 @@ A demo of the app is available [here](https://stirlingpdf.io).
|
|||||||
- [PDF.js](https://github.com/mozilla/pdf.js)
|
- [PDF.js](https://github.com/mozilla/pdf.js)
|
||||||
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
||||||
|
|
||||||
## How to Use
|
## How to use
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
For windows users download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe)
|
||||||
For Windows users, download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe).
|
|
||||||
|
|
||||||
### Locally
|
### Locally
|
||||||
|
|
||||||
Please view the [LocalRunGuide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md).
|
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md
|
||||||
|
|
||||||
### Docker / Podman
|
### Docker / Podman
|
||||||
|
|
||||||
> [!NOTE]
|
https://hub.docker.com/r/frooodle/s-pdf
|
||||||
> <https://hub.docker.com/r/frooodle/s-pdf>
|
|
||||||
|
|
||||||
Stirling-PDF has three different versions: a full version, an ultra-lite version, and 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 space optimization, just use the latest tag.
|
|
||||||
|
|
||||||
|
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.
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
Please note in the examples below, you may need to change the volume paths as needed, e.g., `./extraConfigs:/configs` to `/opt/stirlingpdf/extraConfigs:/configs`.
|
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
|
### Docker Run
|
||||||
|
|
||||||
@@ -138,13 +129,15 @@ docker run -d \
|
|||||||
-v ./trainingData:/usr/share/tessdata \
|
-v ./trainingData:/usr/share/tessdata \
|
||||||
-v ./extraConfigs:/configs \
|
-v ./extraConfigs:/configs \
|
||||||
-v ./logs:/logs \
|
-v ./logs:/logs \
|
||||||
# Optional customization (not required)
|
|
||||||
# -v /location/of/customFiles:/customFiles \
|
|
||||||
-e DOCKER_ENABLE_SECURITY=false \
|
-e DOCKER_ENABLE_SECURITY=false \
|
||||||
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
||||||
-e LANGS=en_GB \
|
-e LANGS=en_GB \
|
||||||
--name stirling-pdf \
|
--name stirling-pdf \
|
||||||
frooodle/s-pdf:latest
|
frooodle/s-pdf:latest
|
||||||
|
|
||||||
|
Can also add these for customisation but are not required
|
||||||
|
|
||||||
|
-v /location/of/customFiles:/customFiles \
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
@@ -157,7 +150,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
volumes:
|
volumes:
|
||||||
- ./trainingData:/usr/share/tessdata # Required for extra OCR languages
|
- ./trainingData:/usr/share/tessdata #Required for extra OCR languages
|
||||||
- ./extraConfigs:/configs
|
- ./extraConfigs:/configs
|
||||||
# - ./customFiles:/customFiles/
|
# - ./customFiles:/customFiles/
|
||||||
# - ./logs:/logs/
|
# - ./logs:/logs/
|
||||||
@@ -169,257 +162,196 @@ services:
|
|||||||
|
|
||||||
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
|
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
|
||||||
|
|
||||||
### Kubernetes
|
## Enable OCR/Compression feature
|
||||||
|
|
||||||
See the kubernetes helm chart [here](https://github.com/Stirling-Tools/Stirling-PDF-chart)
|
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md
|
||||||
|
|
||||||
## Enable OCR/Compression Feature
|
|
||||||
|
|
||||||
Please view the [HowToUseOCR.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md).
|
|
||||||
|
|
||||||
## Reuse Stored Files
|
|
||||||
|
|
||||||
Certain functionality like `Sign` supports pre-saved files stored at `/customFiles/signatures/`. Image files placed within here will be accessible to be used via the web UI. Currently, this supports two folder types:
|
|
||||||
|
|
||||||
- `/customFiles/signatures/ALL_USERS`: Accessible to all users, useful for organizations where many users use the same files or for users not using authentication
|
|
||||||
- `/customFiles/signatures/{username}`: Such as `/customFiles/signatures/froodle`, accessible only to the `froodle` username, private for all others
|
|
||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
Stirling-PDF currently supports 36 languages!
|
Stirling PDF currently supports 38!
|
||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| ------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||||
|
|
||||||
Please see our [Contributing Guide](CONTRIBUTING.md).
|
Please see our [Contributing Guide](CONTRIBUTING.md)!
|
||||||
|
|
||||||
## Stirling PDF Enterprise
|
## Customisation
|
||||||
|
|
||||||
Stirling PDF offers a Enterprise edition of its software, This is the same great software but with added features and comforts
|
Stirling PDF allows easy customization of the app.
|
||||||
|
Includes things like
|
||||||
### Whats included
|
|
||||||
|
|
||||||
- Prioritised Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from same email domain registered with us)
|
|
||||||
- Prioritised Enhancements to Stirling-PDF where applicable
|
|
||||||
- Base SSO support
|
|
||||||
- Advanced SSO such as automated login handling (Coming very soon)
|
|
||||||
- SAML SSO (Coming very soon)
|
|
||||||
- Custom automated metadata handling
|
|
||||||
- Advanced user configurations (Coming soon)
|
|
||||||
- Plus other exciting features to come
|
|
||||||
|
|
||||||
Check out of [docs](https://docs.stirlingpdf.com/Enterprise%20Edition) on it or our official [website](https://www.stirlingpdf.com)
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
Stirling-PDF allows easy customization of the app, including things like:
|
|
||||||
|
|
||||||
- Custom application name
|
- Custom application name
|
||||||
- Custom slogans, icons, HTML, images, CSS, etc. (via file overrides)
|
- Custom slogans, icons, HTML, images CSS etc (via file overrides)
|
||||||
|
|
||||||
There are two options for this, either using the generated settings file `settings.yml`, which is located in the `/configs` directory and follows standard YAML formatting, or using environment variables, which would override the settings file.
|
There are two options for this, either using the generated settings file ``settings.yml``
|
||||||
|
This file is located in the ``/configs`` directory and follows standard YAML formatting
|
||||||
|
|
||||||
For example, in `settings.yml`, you might have:
|
Environment variables are also supported and would override the settings file
|
||||||
|
For example in the settings.yml you have
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
security:
|
security:
|
||||||
enableLogin: 'true'
|
enableLogin: 'true'
|
||||||
```
|
```
|
||||||
|
|
||||||
To have this via an environment variable, you would use `SECURITY_ENABLELOGIN`.
|
To have this via an environment variable you would have ``SECURITY_ENABLELOGIN``
|
||||||
|
|
||||||
The current list of settings is:
|
The Current list of settings is
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
security:
|
security:
|
||||||
enableLogin: false # set to 'true' to enable login
|
enableLogin: false # set to 'true' to enable login
|
||||||
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production)
|
csrfDisabled: true # Set to 'true' to disable CSRF protection (not recommended for production)
|
||||||
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
||||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||||
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)
|
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:
|
initialLogin:
|
||||||
username: '' # initial username for the first login
|
username: '' # Initial username for the first login
|
||||||
password: '' # initial password for the first login
|
password: '' # Initial password for the first login
|
||||||
oauth2:
|
oauth2:
|
||||||
enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||||
client:
|
client:
|
||||||
keycloak:
|
keycloak:
|
||||||
issuer: '' # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
issuer: '' # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||||
clientId: '' # client ID for Keycloak OAuth2
|
clientId: '' # Client ID for Keycloak OAuth2
|
||||||
clientSecret: '' # client secret for Keycloak OAuth2
|
clientSecret: '' # Client Secret for Keycloak OAuth2
|
||||||
scopes: openid, profile, email # scopes for Keycloak OAuth2
|
scopes: openid, profile, email # Scopes for Keycloak OAuth2
|
||||||
useAsUsername: preferred_username # field to use as the username for Keycloak OAuth2
|
useAsUsername: preferred_username # Field to use as the username for Keycloak OAuth2
|
||||||
google:
|
google:
|
||||||
clientId: '' # client ID for Google OAuth2
|
clientId: '' # Client ID for Google OAuth2
|
||||||
clientSecret: '' # client secret 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
|
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
|
useAsUsername: email # Field to use as the username for Google OAuth2
|
||||||
github:
|
github:
|
||||||
clientId: '' # client ID for GitHub OAuth2
|
clientId: '' # Client ID for GitHub OAuth2
|
||||||
clientSecret: '' # client secret for GitHub OAuth2
|
clientSecret: '' # Client Secret for GitHub OAuth2
|
||||||
scopes: read:user # scope for GitHub OAuth2
|
scopes: read:user # Scope for GitHub OAuth2
|
||||||
useAsUsername: login # field to use as the username 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) endpoint
|
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||||
clientId: '' # client ID from your provider
|
clientId: '' # Client ID from your provider
|
||||||
clientSecret: '' # client secret from your provider
|
clientSecret: '' # Client Secret from your provider
|
||||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
||||||
useAsUsername: email # default is 'email'; custom fields can be used as the username
|
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
|
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'
|
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||||
saml2:
|
|
||||||
enabled: false # currently in alpha, not recommended for use yet, enableAlphaFunctionality must be set to true
|
|
||||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
|
||||||
registrationId: stirling
|
|
||||||
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
|
|
||||||
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml
|
|
||||||
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
|
|
||||||
idpIssuer: http://www.okta.com/externalKey
|
|
||||||
idpCert: classpath:okta.crt
|
|
||||||
privateKey: classpath:saml-private-key.key
|
|
||||||
spCert: classpath:saml-public-cert.crt
|
|
||||||
|
|
||||||
enterpriseEdition:
|
|
||||||
enabled: false # set to 'true' to enable enterprise edition
|
|
||||||
key: 00000000-0000-0000-0000-000000000000
|
|
||||||
CustomMetadata:
|
|
||||||
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
|
|
||||||
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
|
|
||||||
creator: Stirling-PDF # supports text such as 'Company-PDF'
|
|
||||||
producer: Stirling-PDF # supports text such as 'Company-PDF'
|
|
||||||
|
|
||||||
legal:
|
|
||||||
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
|
|
||||||
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
|
|
||||||
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
|
|
||||||
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
|
|
||||||
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum). Empty string to disable or filename to load from local file in static folder
|
|
||||||
|
|
||||||
system:
|
system:
|
||||||
defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||||
enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
|
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||||
showUpdate: false # see when a new update is available
|
showUpdate: true # see when a new update is available
|
||||||
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
||||||
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
|
||||||
enableAnalytics: undefined # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
|
||||||
|
|
||||||
ui:
|
ui:
|
||||||
appName: '' # application's visible name
|
appName: '' # Application's visible name
|
||||||
homeDescription: '' # short description or tagline shown on the homepage
|
homeDescription: '' # Short description or tagline shown on homepage.
|
||||||
appNameNavbar: '' # name displayed on the navigation bar
|
appNameNavbar: '' # Name displayed on the navigation bar
|
||||||
|
|
||||||
endpoints:
|
endpoints:
|
||||||
toRemove: [] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||||
groupsToRemove: [] # list groups to disable (e.g. ['LibreOffice'])
|
groupsToRemove: [] # List groups to disable (e.g. ['LibreOffice'])
|
||||||
|
|
||||||
metrics:
|
metrics:
|
||||||
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
|
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
|
||||||
|
|
||||||
# Automatically Generated Settings (Do Not Edit Directly)
|
|
||||||
AutomaticallyGenerated:
|
|
||||||
key: example
|
|
||||||
UUID: example
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There is an additional config file `/configs/custom_settings.yml` where users familiar with Java and Spring `application.properties` can input their own settings on top of Stirling-PDF's existing ones.
|
There is an additional config file ``/configs/custom_settings.yml`` were users familiar with java and spring application.properties can input their own settings on-top of Stirling-PDFs existing ones
|
||||||
|
|
||||||
|
### Extra notes
|
||||||
|
|
||||||
|
- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
|
||||||
|
- customStaticFilePath. Customise static files such as the app logo by placing files in the /customFiles/static/ directory. An example of customising app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
|
||||||
|
|
||||||
### Extra Notes
|
### Environment only parameters
|
||||||
|
|
||||||
- **Endpoints**: Currently, the `ENDPOINTS_TO_REMOVE` and `GROUPS_TO_REMOVE` endpoints can include comma-separated lists of endpoints and groups to disable. For example, `ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages` would disable both image-to-pdf and remove pages, while `GROUPS_TO_REMOVE=LibreOffice` would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md).
|
- ``SYSTEM_ROOTURIPATH`` ie set to ``/pdf-app`` to Set the application's root URI to ``localhost:8080/pdf-app``
|
||||||
- **customStaticFilePath**: Customize static files such as the app logo by placing files in the `/customFiles/static/` directory. An example of customizing the app logo is placing `/customFiles/static/favicon.svg` to override the current SVG. This can be used to change any `images/icons/css/fonts/js`, etc. in Stirling-PDF.
|
- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values
|
||||||
|
- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login)
|
||||||
### Environment-Only Parameters
|
- ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS`` to download calibre onto stirling-pdf enabling pdf to/from book and advanced html conversion
|
||||||
|
- ``LANGS`` to define custom font libraries to install for use for document conversions
|
||||||
- `SYSTEM_ROOTURIPATH` - Set the application's root URI (e.g. `/pdf-app` to set the root URI to `localhost:8080/pdf-app`)
|
|
||||||
- `SYSTEM_CONNECTIONTIMEOUTMINUTES` - Set custom connection timeout values
|
|
||||||
- `DOCKER_ENABLE_SECURITY` - Set to `true` to download security jar (required for authentication login)
|
|
||||||
- `INSTALL_BOOK_AND_ADVANCED_HTML_OPS` - Download Calibre onto Stirling-PDF to enable PDF to/from book and advanced HTML conversion
|
|
||||||
- `LANGS` - Define custom font libraries to install for document conversions
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
For those wanting to use Stirling-PDF's backend API to link with their own custom scripting to edit PDFs, you can view all existing API documentation [here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/), or navigate to `/swagger-ui/index.html` of your Stirling-PDF instance for your version's documentation (or by following the API button in the settings of Stirling-PDF).
|
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
||||||
|
[here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
|
||||||
|
|
||||||
## Login Authentication
|
## Login authentication
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- User must have the folder `./configs` volumed within Docker so that it is retained during updates.
|
- 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.
|
- Docker users must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
|
||||||
- Then either enable login via the `settings.yml` file or set `SECURITY_ENABLE_LOGIN` to `true`.
|
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
|
||||||
- Now the initial user will be generated with username `admin` and password `stirling`. On login, you will be forced to change the password to a new one. You can also use the environment variables `SECURITY_INITIALLOGIN_USERNAME` and `SECURITY_INITIALLOGIN_PASSWORD` to set your own credentials straight away (recommended to remove them after user creation).
|
- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation).
|
||||||
|
|
||||||
Once the above has been done, on restart, a new `stirling-pdf-DB.mv.db` will show if everything worked.
|
Once the above has been done, on restart, a new stirling-pdf-DB.mv.db will show if everything worked.
|
||||||
|
|
||||||
When you log in to Stirling-PDF, you will be redirected to the `/login` page to log in with those default credentials. After login, everything should function as normal.
|
When you login to Stirling PDF you will be redirected to /login page to login with those default credentials. After login everything should function as normal
|
||||||
|
|
||||||
To access your account settings, go to Account Settings in the settings cog menu (top right in the navbar). This Account Settings menu is also where you find your API key.
|
To access your account settings go to Account settings in the settings cog menu (top right in navbar) This Account settings menu is also where you find your API key.
|
||||||
|
|
||||||
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
|
To add new users go to the bottom of Account settings and hit 'Admin Settings', here you can add new users. The different roles mentioned within this are for rate limiting. This is a Work in progress which will be expanding on more in future
|
||||||
|
|
||||||
For API usage, you must provide a header with `X-API-Key` and the associated API key for that user.
|
For API usage you must provide a header with 'X-API-Key' and the associated API key for that user.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Q1: What are your planned features?
|
### Q1: What are your planned features?
|
||||||
|
|
||||||
- Progress bar/tracking
|
- Progress bar/Tracking
|
||||||
- Full custom logic pipelines to combine multiple operations together
|
- Full custom logic pipelines to combine multiple operations together.
|
||||||
- Folder support with auto-scanning to perform operations on
|
- Folder support with auto scanning to perform operations on
|
||||||
- Redact text (via UI, not just automated)
|
- Redact text (Via UI not just automated way)
|
||||||
- Add forms
|
- Add Forms
|
||||||
- Multi-page layout (stitch PDF pages together) support x rows y columns and custom page sizing
|
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
|
||||||
- Fill forms manually or automatically
|
- Fill forms manually or automatically
|
||||||
|
|
||||||
### Q2: Why is my application downloading .htm files? Why am i getting HTTP error 413?
|
### Q2: Why is my application downloading .htm files?
|
||||||
|
|
||||||
This is an issue commonly caused by your NGINX configuration. The default file upload size for NGINX is 1MB. You need to add the following in your Nginx sites-available file: `client_max_body_size SIZE;` (where "SIZE" is 50M for example for 50MB files).
|
This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files.
|
||||||
|
|
||||||
### Q3: Why is my download timing out?
|
### Q3: Why is my download timing out
|
||||||
|
|
||||||
NGINX has timeout values by default, so if you are running Stirling-PDF behind NGINX, you may need to set a timeout value, such as adding the config `proxy_read_timeout 3600;`.
|
NGINX has timeout values by default so if you are running Stirling-PDF behind NGINX you may need to set a timeout value such as adding the config ``proxy_read_timeout 3600;``
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|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``.
|
|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.
|
The 'Fat' container contains all those found in 'Full' with security jar along with this Calibre install.
|
||||||
|
|
||||||
| Technology | Ultra-Lite | Full |
|
Technology | Ultra-Lite | Full |
|
||||||
| ---------- | :--------: | :---: |
|
| ---------- | :--------: | :---: |
|
||||||
| Java | ✔️ | ✔️ |
|
| Java | ✔️ | ✔️ |
|
||||||
| JavaScript | ✔️ | ✔️ |
|
| JavaScript | ✔️ | ✔️ |
|
||||||
@@ -54,15 +54,3 @@ The 'Fat' container contains all those found in 'Full' with security jar along w
|
|||||||
| ocr-pdf | | ✔️ |
|
| ocr-pdf | | ✔️ |
|
||||||
| pdf-to-pdfa | | ✔️ |
|
| pdf-to-pdfa | | ✔️ |
|
||||||
| remove-blanks | | ✔️ |
|
| remove-blanks | | ✔️ |
|
||||||
pdf-to-text | ✔️ | ✔️
|
|
||||||
pdf-to-html | | ✔️
|
|
||||||
pdf-to-word | | ✔️
|
|
||||||
pdf-to-presentation | | ✔️
|
|
||||||
pdf-to-xml | | ✔️
|
|
||||||
remove-annotations | ✔️ | ✔️
|
|
||||||
remove-cert-sign | ✔️ | ✔️
|
|
||||||
remove-image-pdf | ✔️ | ✔️
|
|
||||||
file-to-pdf | | ✔️
|
|
||||||
html-to-pdf | | ✔️
|
|
||||||
url-to-pdf | | ✔️
|
|
||||||
repair | | ✔️
|
|
||||||
|
|||||||
45
build.gradle
45
build.gradle
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "org.springframework.boot" version "3.3.5"
|
id "org.springframework.boot" version "3.3.4"
|
||||||
id "io.spring.dependency-management" version "1.1.6"
|
id "io.spring.dependency-management" version "1.1.6"
|
||||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
@@ -13,16 +13,16 @@ plugins {
|
|||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = "3.3.5"
|
springBootVersion = "3.3.4"
|
||||||
pdfboxVersion = "3.0.3"
|
pdfboxVersion = "3.0.3"
|
||||||
logbackVersion = "1.5.7"
|
logbackVersion = "1.5.7"
|
||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.11.0"
|
||||||
lombokVersion = "1.18.34"
|
lombokVersion = "1.18.34"
|
||||||
bouncycastleVersion = "1.78.1"
|
bouncycastleVersion = "1.78.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.33.0"
|
version = "0.30.0"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@@ -32,9 +32,11 @@ java {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
|
maven {
|
||||||
|
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||||
|
}
|
||||||
maven {
|
maven {
|
||||||
url 'https://build.shibboleth.net/maven/releases'
|
url "https://build.shibboleth.net/maven/releases/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ launch4j {
|
|||||||
|
|
||||||
errTitle="Encountered error, Do you have Java 21?"
|
errTitle="Encountered error, Do you have Java 21?"
|
||||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||||
variables=["BROWSER_OPEN=true"]
|
variables=["BROWSER_OPEN=true", "ENDPOINTS_GROUPS_TO_REMOVE=CLI"]
|
||||||
jreMinVersion="17"
|
jreMinVersion="17"
|
||||||
|
|
||||||
mutexName="Stirling-PDF"
|
mutexName="Stirling-PDF"
|
||||||
@@ -119,7 +121,7 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.1.14"
|
implementation "org.springframework:spring-webmvc:6.1.13"
|
||||||
|
|
||||||
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
||||||
|
|
||||||
@@ -133,35 +135,25 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||||
implementation 'com.posthog.java:posthog:1.1.1'
|
implementation 'com.posthog.java:posthog:1.1.1'
|
||||||
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||||
|
|
||||||
|
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||||
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
|
runtimeOnly "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-data-jpa:$springBootVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.4'
|
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.3'
|
||||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||||
//2.2.x requires rebuild of DB file.. need migration path
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
runtimeOnly "com.h2database:h2:2.1.214"
|
runtimeOnly "com.h2database:h2:2.1.214"
|
||||||
// implementation "com.h2database:h2:2.2.224"
|
// implementation "com.h2database:h2:2.2.224"
|
||||||
constraints {
|
|
||||||
implementation "org.opensaml:opensaml-core"
|
|
||||||
implementation "org.opensaml:opensaml-saml-api"
|
|
||||||
implementation "org.opensaml:opensaml-saml-impl"
|
|
||||||
}
|
|
||||||
implementation "org.springframework.security:spring-security-saml2-service-provider"
|
|
||||||
|
|
||||||
implementation 'com.coveo:saml-client:5.0.0'
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||||
|
|
||||||
// Batik
|
// Batik
|
||||||
implementation "org.apache.xmlgraphics:batik-all:1.18"
|
implementation "org.apache.xmlgraphics:batik-all:1.17"
|
||||||
|
|
||||||
// TwelveMonkeys
|
// TwelveMonkeys
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
|
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
|
||||||
@@ -181,9 +173,6 @@ dependencies {
|
|||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
||||||
|
|
||||||
// Image metadata extractor
|
|
||||||
implementation "com.drewnoakes:metadata-extractor:2.19.0"
|
|
||||||
|
|
||||||
implementation "commons-io:commons-io:2.17.0"
|
implementation "commons-io:commons-io:2.17.0"
|
||||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||||
//general PDF
|
//general PDF
|
||||||
@@ -206,11 +195,11 @@ dependencies {
|
|||||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||||
implementation "io.micrometer:micrometer-core:1.13.6"
|
implementation "io.micrometer:micrometer-core:1.13.4"
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation "org.commonmark:commonmark:0.24.0"
|
implementation "org.commonmark:commonmark:0.23.0"
|
||||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.24.0"
|
implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0"
|
||||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.5"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|||||||
16
chart/stirling-pdf/Chart.yaml
Normal file
16
chart/stirling-pdf/Chart.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
appVersion: 0.29.0
|
||||||
|
description: locally hosted web application that allows you to perform various operations
|
||||||
|
on PDF files
|
||||||
|
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
|
keywords:
|
||||||
|
- stirling-pdf
|
||||||
|
- helm
|
||||||
|
- charts repo
|
||||||
|
maintainers:
|
||||||
|
- name: Stirling-Tools
|
||||||
|
url: https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
|
name: stirling-pdf-chart
|
||||||
|
sources:
|
||||||
|
- https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
|
version: 1.0.0
|
||||||
30
chart/stirling-pdf/templates/NOTES.txt
Normal file
30
chart/stirling-pdf/templates/NOTES.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
** Please be patient while the chart is being deployed **
|
||||||
|
|
||||||
|
Get the stirlingpdf URL by running:
|
||||||
|
|
||||||
|
{{- if contains "NodePort" .Values.service.type }}
|
||||||
|
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "stirlingpdf.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT/
|
||||||
|
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
|
||||||
|
** Please ensure an external IP is associated to the {{ template "stirlingpdf.fullname" . }} service before proceeding **
|
||||||
|
** Watch the status using: kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "stirlingpdf.fullname" . }} **
|
||||||
|
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "stirlingpdf.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.externalPort }}/
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
export SERVICE_HOST=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "stirlingpdf.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
|
||||||
|
echo http://$SERVICE_HOST:{{ .Values.service.externalPort }}/
|
||||||
|
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "stirlingpdf.name" . }}" -l "release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
echo http://127.0.0.1:8080/
|
||||||
|
kubectl port-forward $POD_NAME 8080:8080 --namespace {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
{{- end }}
|
||||||
129
chart/stirling-pdf/templates/_helpers.tpl
Normal file
129
chart/stirling-pdf/templates/_helpers.tpl
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- /*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
|
||||||
|
It does minimal escaping for use in Kubernetes labels.
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
stirlingpdf-0.4.5
|
||||||
|
*/ -}}
|
||||||
|
{{- define "stirlingpdf.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "stirlingpdf.chart" . }}
|
||||||
|
{{ include "stirlingpdf.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.commonLabels}}
|
||||||
|
{{ toYaml .Values.commonLabels }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "stirlingpdf.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "stirlingpdf.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Return the proper image name to change the volume permissions
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.volumePermissions.image" -}}
|
||||||
|
{{- $registryName := .Values.volumePermissions.image.registry -}}
|
||||||
|
{{- $repositoryName := .Values.volumePermissions.image.repository -}}
|
||||||
|
{{- $tag := .Values.volumePermissions.image.tag | toString -}}
|
||||||
|
{{/*
|
||||||
|
Helm 2.11 supports the assignment of a value to a variable defined in a different scope,
|
||||||
|
but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic.
|
||||||
|
Also, we can't use a single if because lazy evaluation is not an option
|
||||||
|
*/}}
|
||||||
|
{{- if .Values.global }}
|
||||||
|
{{- if .Values.global.imageRegistry }}
|
||||||
|
{{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Return the proper Docker Image Registry Secret Names
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.imagePullSecrets" -}}
|
||||||
|
{{/*
|
||||||
|
Helm 2.11 supports the assignment of a value to a variable defined in a different scope,
|
||||||
|
but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic.
|
||||||
|
Also, we can not use a single if because lazy evaluation is not an option
|
||||||
|
*/}}
|
||||||
|
{{- if .Values.global }}
|
||||||
|
{{- if .Values.global.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.global.imagePullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
131
chart/stirling-pdf/templates/deployment.yaml
Normal file
131
chart/stirling-pdf/templates/deployment.yaml
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
{{- with .Values.deployment.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- if .Values.deployment.labels }}
|
||||||
|
{{- toYaml .Values.deployment.labels | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 6 }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
strategy:
|
||||||
|
{{ toYaml .Values.strategy | indent 4 }}
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 8 }}
|
||||||
|
{{- if .Values.podLabels }}
|
||||||
|
{{- toYaml .Values.podLabels | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if .Values.priorityClassName }}
|
||||||
|
priorityClassName: "{{ .Values.priorityClassName }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.securityContext.enabled }}
|
||||||
|
securityContext:
|
||||||
|
fsGroup: {{ .Values.securityContext.fsGroup }}
|
||||||
|
{{- if .Values.securityContext.runAsNonRoot }}
|
||||||
|
runAsNonRoot: {{ .Values.securityContext.runAsNonRoot }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.securityContext.supplementalGroups }}
|
||||||
|
supplementalGroups: {{ .Values.securityContext.supplementalGroups }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if .Values.persistence.enabled }}
|
||||||
|
initContainers:
|
||||||
|
- name: volume-permissions
|
||||||
|
image: {{ template "stirlingpdf.volumePermissions.image" . }}
|
||||||
|
imagePullPolicy: "{{ .Values.volumePermissions.image.pullPolicy }}"
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.containerSecurityContext | nindent 10 }}
|
||||||
|
command: ['sh', '-c', 'chown -R {{ .Values.securityContext.fsGroup }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.path }}']
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: {{ .Values.persistence.path }}
|
||||||
|
name: storage-volume
|
||||||
|
{{- end }}
|
||||||
|
{{- include "stirlingpdf.imagePullSecrets" . | indent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.containerSecurityContext | nindent 10 }}
|
||||||
|
env:
|
||||||
|
- name: SYSTEM_ROOTURIPATH
|
||||||
|
value: {{ .Values.rootPath}}
|
||||||
|
{{- if .Values.envs }}
|
||||||
|
{{ toYaml .Values.envs | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.extraArgs }}
|
||||||
|
args:
|
||||||
|
{{ toYaml .Values.extraArgs | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: {{ .Values.rootPath}}
|
||||||
|
port: http
|
||||||
|
{{ toYaml .Values.probes.livenessHttpGetConfig | indent 12 }}
|
||||||
|
{{ toYaml .Values.probes.liveness | indent 10 }}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: {{ .Values.rootPath}}
|
||||||
|
port: http
|
||||||
|
{{ toYaml .Values.probes.readinessHttpGetConfig | indent 12 }}
|
||||||
|
{{ toYaml .Values.probes.readiness | indent 10 }}
|
||||||
|
volumeMounts:
|
||||||
|
{{- if .Values.deployment.extraVolumeMounts }}
|
||||||
|
{{- toYaml .Values.deployment.extraVolumeMounts | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.deployment.sidecarContainers }}
|
||||||
|
{{- range $name, $spec := .Values.deployment.sidecarContainers }}
|
||||||
|
- name: {{ $name }}
|
||||||
|
{{- toYaml $spec | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.resources }}
|
||||||
|
resources:
|
||||||
|
{{ toYaml . | indent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.schedulerName }}
|
||||||
|
schedulerName: {{ .Values.schedulerName }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "stirlingpdf.serviceAccountName" . }}
|
||||||
|
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||||
|
volumes:
|
||||||
|
{{- if .Values.deployment.extraVolumes }}
|
||||||
|
{{- toYaml .Values.deployment.extraVolumes | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
- name: storage-volume
|
||||||
|
{{- if .Values.persistence.enabled }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ .Values.persistence.existingClaim | default (include "stirlingpdf.fullname" .) }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
85
chart/stirling-pdf/templates/ingress.yaml
Normal file
85
chart/stirling-pdf/templates/ingress.yaml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- $servicePort := .Values.service.externalPort -}}
|
||||||
|
{{- $serviceName := include "stirlingpdf.fullname" . -}}
|
||||||
|
{{- $ingressExtraPaths := .Values.ingress.extraPaths -}}
|
||||||
|
---
|
||||||
|
{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else }}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.ingress.ingressClassName }}
|
||||||
|
ingressClassName: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .name }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range $ingressExtraPaths }}
|
||||||
|
- path: {{ default "/" .path | quote }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare "<1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
serviceName: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
servicePort: {{ default $servicePort .port }}
|
||||||
|
{{- else }}
|
||||||
|
service:
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
name: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
name: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
port:
|
||||||
|
number: {{ default $servicePort .port }}
|
||||||
|
pathType: {{ default $.Values.ingress.pathType .pathType }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
- path: {{ default "/" .path | quote }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare "<1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
serviceName: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
servicePort: {{ default $servicePort .servicePort }}
|
||||||
|
{{- else }}
|
||||||
|
service:
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
name: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
name: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
port:
|
||||||
|
number: {{ default $servicePort .port }}
|
||||||
|
pathType: {{ $.Values.ingress.pathType }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
{{- if .tls }}
|
||||||
|
- hosts:
|
||||||
|
- {{ .name }}
|
||||||
|
secretName: {{ .tlsSecret }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
16
chart/stirling-pdf/templates/pv.yaml
Normal file
16
chart/stirling-pdf/templates/pv.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{{- if .Values.persistence.pv.enabled -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.persistence.pv.pvname | default (include "stirlingpdf.fullname" .) }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: {{ .Values.persistence.pv.capacity.storage }}
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.pv.accessMode | quote }}
|
||||||
|
nfs:
|
||||||
|
server: {{ .Values.persistence.pv.nfs.server }}
|
||||||
|
path: {{ .Values.persistence.pv.nfs.path | quote }}
|
||||||
|
{{- end }}
|
||||||
27
chart/stirling-pdf/templates/pvc.yaml
Normal file
27
chart/stirling-pdf/templates/pvc.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.persistence.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode | quote }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size | quote }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
{{- if (eq "-" .Values.persistence.storageClass) }}
|
||||||
|
storageClassName: ""
|
||||||
|
{{- else }}
|
||||||
|
storageClassName: "{{ .Values.persistence.storageClass }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.persistence.volumeName }}
|
||||||
|
volumeName: "{{ .Values.persistence.volumeName }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
48
chart/stirling-pdf/templates/service.yaml
Normal file
48
chart/stirling-pdf/templates/service.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.service.servicename | default (include "stirlingpdf.fullname" .) }}
|
||||||
|
{{- with .Values.service.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.service.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
{{- if (or (eq .Values.service.type "LoadBalancer") (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort)))) }}
|
||||||
|
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP) }}
|
||||||
|
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerSourceRanges) }}
|
||||||
|
loadBalancerSourceRanges:
|
||||||
|
{{- with .Values.service.loadBalancerSourceRanges }}
|
||||||
|
{{ toYaml . | indent 2 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq .Values.service.type "ClusterIP" }}
|
||||||
|
{{- if .Values.service.clusterIP }}
|
||||||
|
clusterIP: {{ .Values.service.clusterIP }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.externalPort }}
|
||||||
|
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
|
||||||
|
nodePort: {{.Values.service.nodePort}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.service.targetPort }}
|
||||||
|
targetPort: {{ .Values.service.targetPort }}
|
||||||
|
name: {{ .Values.service.targetPort }}
|
||||||
|
{{- else }}
|
||||||
|
targetPort: http
|
||||||
|
name: http
|
||||||
|
{{- end }}
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
|
selector:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 4 }}
|
||||||
13
chart/stirling-pdf/templates/serviceaccount.yaml
Normal file
13
chart/stirling-pdf/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.serviceAccountName" . }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{ toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
31
chart/stirling-pdf/templates/servicemonitor.yaml
Normal file
31
chart/stirling-pdf/templates/servicemonitor.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) ( .Values.serviceMonitor.enabled ) }}
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceMonitor.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
endpoints:
|
||||||
|
- targetPort: 8080
|
||||||
|
{{- if .Values.serviceMonitor.interval }}
|
||||||
|
interval: {{ .Values.serviceMonitor.interval }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.serviceMonitor.metricsPath }}
|
||||||
|
path: {{ .Values.serviceMonitor.metricsPath }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.serviceMonitor.timeout }}
|
||||||
|
scrapeTimeout: {{ .Values.serviceMonitor.timeout }}
|
||||||
|
{{- end }}
|
||||||
|
jobLabel: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- {{ .Release.Namespace }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
240
chart/stirling-pdf/values.yaml
Normal file
240
chart/stirling-pdf/values.yaml
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
extraArgs: []
|
||||||
|
# - --storage-timestamp-tolerance 1s
|
||||||
|
replicaCount: 1
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
image:
|
||||||
|
repository: frooodle/s-pdf
|
||||||
|
# took Chart appVersion by default
|
||||||
|
tag: ~
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
secret:
|
||||||
|
labels: {}
|
||||||
|
## Labels to apply to all resources
|
||||||
|
##
|
||||||
|
commonLabels: {}
|
||||||
|
# team_name: dev
|
||||||
|
|
||||||
|
# rootpath for the application
|
||||||
|
rootPath: /
|
||||||
|
|
||||||
|
envs: []
|
||||||
|
# - name: UI_APP_NAME
|
||||||
|
# value: "Stirling PDF"
|
||||||
|
# - name: UI_HOME_DESCRIPTION
|
||||||
|
# value: "Your locally hosted one-stop-shop for all your PDF needs."
|
||||||
|
# - name: UI_APP_NAVBAR_NAME
|
||||||
|
# value: "Stirling PDF"
|
||||||
|
# - name: ALLOW_GOOGLE_VISIBILITY
|
||||||
|
# value: "true"
|
||||||
|
# - name: APP_LOCALE
|
||||||
|
# value: "en_GB"
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
## stirling-pdf Deployment annotations
|
||||||
|
annotations: {}
|
||||||
|
# name: value
|
||||||
|
labels: {}
|
||||||
|
# name: value
|
||||||
|
# additional volumes
|
||||||
|
extraVolumes: []
|
||||||
|
# - name: nginx-config
|
||||||
|
# secret:
|
||||||
|
# secretName: nginx-config
|
||||||
|
# additional volumes to mount
|
||||||
|
extraVolumeMounts: []
|
||||||
|
## sidecarContainers for the stirling-pdf
|
||||||
|
# Can be used to add a proxy to the pod that does
|
||||||
|
# scanning for secrets, signing, authentication, validation
|
||||||
|
# of the chart's content, send notifications...
|
||||||
|
sidecarContainers: {}
|
||||||
|
## Example sidecarContainer which uses an extraVolume from above and
|
||||||
|
## a named port that can be referenced in the service as targetPort.
|
||||||
|
# proxy:
|
||||||
|
# image: nginx:latest
|
||||||
|
# ports:
|
||||||
|
# - name: proxy
|
||||||
|
# containerPort: 8081
|
||||||
|
# volumeMounts:
|
||||||
|
# - name: nginx-config
|
||||||
|
# readOnly: true
|
||||||
|
# mountPath: /etc/nginx
|
||||||
|
|
||||||
|
## Pod annotations
|
||||||
|
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||||
|
## Read more about kube2iam to provide access to s3 https://github.com/jtblin/kube2iam
|
||||||
|
##
|
||||||
|
podAnnotations: {}
|
||||||
|
# iam.amazonaws.com/role: role-arn
|
||||||
|
|
||||||
|
## Pod labels
|
||||||
|
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||||
|
podLabels: {}
|
||||||
|
# name: value
|
||||||
|
|
||||||
|
service:
|
||||||
|
servicename:
|
||||||
|
type: ClusterIP
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
## Uses pre-assigned IP address from cloud provider
|
||||||
|
## Only valid if service.type: LoadBalancer
|
||||||
|
loadBalancerIP:
|
||||||
|
## Limits which cidr blocks can connect to service's load balancer
|
||||||
|
## Only valid if service.type: LoadBalancer
|
||||||
|
loadBalancerSourceRanges: []
|
||||||
|
# clusterIP: None
|
||||||
|
externalPort: 8080
|
||||||
|
## targetPort of the container to use. If a sidecar should handle the
|
||||||
|
## requests first, use the named port from the sidecar. See sidecar example
|
||||||
|
## from deployment above. Leave empty to use stirling-pdf directly.
|
||||||
|
targetPort:
|
||||||
|
nodePort:
|
||||||
|
annotations: {}
|
||||||
|
labels: {}
|
||||||
|
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
# namespace: prometheus
|
||||||
|
labels: {}
|
||||||
|
metricsPath: "/metrics"
|
||||||
|
# timeout: 60
|
||||||
|
# interval: 60
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 80m
|
||||||
|
# memory: 64Mi
|
||||||
|
|
||||||
|
probes:
|
||||||
|
liveness:
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 1
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessHttpGetConfig:
|
||||||
|
scheme: HTTP
|
||||||
|
readiness:
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 1
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessHttpGetConfig:
|
||||||
|
scheme: HTTP
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: ""
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
## Annotations for the Service Account
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
# UID/GID 1000 is the default user "stirling-pdf" used in
|
||||||
|
# the container image starting in v0.8.0 and above. This
|
||||||
|
# is required for local persistent storage. If your cluster
|
||||||
|
# does not allow this, try setting securityContext: {}
|
||||||
|
securityContext:
|
||||||
|
enabled: true
|
||||||
|
fsGroup: 1000
|
||||||
|
## Optionally, specify supplementalGroups and/or
|
||||||
|
## runAsNonRoot for security purposes
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# supplementalGroups: [1000]
|
||||||
|
|
||||||
|
containerSecurityContext: {}
|
||||||
|
|
||||||
|
priorityClassName: ""
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 8Gi
|
||||||
|
labels: {}
|
||||||
|
# name: value
|
||||||
|
path: /tmp
|
||||||
|
## A manually managed Persistent Volume and Claim
|
||||||
|
## Requires persistence.enabled: true
|
||||||
|
## If defined, PVC must be created manually before volume will be bound
|
||||||
|
# existingClaim:
|
||||||
|
|
||||||
|
## stirling-pdf data Persistent Volume Storage Class
|
||||||
|
## If defined, storageClassName: <storageClass>
|
||||||
|
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||||
|
## If undefined (the default) or set to null, no storageClassName spec is
|
||||||
|
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||||
|
## GKE, AWS & OpenStack)
|
||||||
|
##
|
||||||
|
# storageClass: "-"
|
||||||
|
# volumeName:
|
||||||
|
pv:
|
||||||
|
enabled: false
|
||||||
|
pvname:
|
||||||
|
capacity:
|
||||||
|
storage: 8Gi
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
nfs:
|
||||||
|
server:
|
||||||
|
path:
|
||||||
|
|
||||||
|
## Init containers parameters:
|
||||||
|
## volumePermissions: Change the owner of the persistent volume mountpoint to RunAsUser:fsGroup
|
||||||
|
##
|
||||||
|
volumePermissions:
|
||||||
|
image:
|
||||||
|
registry: docker.io
|
||||||
|
repository: bitnami/minideb
|
||||||
|
tag: buster
|
||||||
|
pullPolicy: Always
|
||||||
|
## Optionally specify an array of imagePullSecrets.
|
||||||
|
## Secrets must be manually created in the namespace.
|
||||||
|
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||||
|
##
|
||||||
|
# pullSecrets:
|
||||||
|
# - myRegistryKeySecretName
|
||||||
|
|
||||||
|
## Ingress for load balancer
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
pathType: "ImplementationSpecific"
|
||||||
|
## stirling-pdf Ingress labels
|
||||||
|
##
|
||||||
|
labels: {}
|
||||||
|
# dns: "route53"
|
||||||
|
|
||||||
|
## stirling-pdf Ingress annotations
|
||||||
|
##
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
|
||||||
|
## stirling-pdf Ingress hostnames
|
||||||
|
## Must be provided if Ingress is enabled
|
||||||
|
##
|
||||||
|
hosts: []
|
||||||
|
# - name: stirling-pdf.domain1.com
|
||||||
|
# path: /
|
||||||
|
# tls: false
|
||||||
|
# - name: stirling-pdf.domain2.com
|
||||||
|
# path: /
|
||||||
|
#
|
||||||
|
# ## Set this to true in order to enable TLS on the ingress record
|
||||||
|
# tls: true
|
||||||
|
#
|
||||||
|
# ## If TLS is set to true, you must declare what secret will store the key/certificate for TLS
|
||||||
|
# ## Secrets must be added manually to the namespace
|
||||||
|
# tlsSecret: stirling-pdf.domain2-tls
|
||||||
|
|
||||||
|
# For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName
|
||||||
|
# See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress
|
||||||
|
ingressClassName:
|
||||||
|
|
||||||
@@ -13,11 +13,13 @@ ignore = [
|
|||||||
'PDFToText.tags',
|
'PDFToText.tags',
|
||||||
'adminUserSettings.admin',
|
'adminUserSettings.admin',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
'survey.button',
|
||||||
'watermark.type.1',
|
'watermark.type.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
[cs_CZ]
|
[cs_CZ]
|
||||||
ignore = [
|
ignore = [
|
||||||
|
'info',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'pipeline.header',
|
'pipeline.header',
|
||||||
'text',
|
'text',
|
||||||
@@ -37,12 +39,10 @@ ignore = [
|
|||||||
'addPageNumbers.selectText.3',
|
'addPageNumbers.selectText.3',
|
||||||
'alphabet',
|
'alphabet',
|
||||||
'certSign.name',
|
'certSign.name',
|
||||||
'home.pipeline.title',
|
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'licenses.version',
|
'licenses.version',
|
||||||
'pipeline.title',
|
'pipeline.title',
|
||||||
'pipelineOptions.pipelineHeader',
|
'pipelineOptions.pipelineHeader',
|
||||||
'pro',
|
|
||||||
'sponsor',
|
'sponsor',
|
||||||
'text',
|
'text',
|
||||||
'watermark.type.1',
|
'watermark.type.1',
|
||||||
@@ -79,6 +79,7 @@ ignore = [
|
|||||||
'alphabet',
|
'alphabet',
|
||||||
'compare.document.1',
|
'compare.document.1',
|
||||||
'compare.document.2',
|
'compare.document.2',
|
||||||
|
'info',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'licenses.license',
|
'licenses.license',
|
||||||
'licenses.module',
|
'licenses.module',
|
||||||
@@ -86,6 +87,8 @@ ignore = [
|
|||||||
'licenses.version',
|
'licenses.version',
|
||||||
'pdfOrganiser.mode',
|
'pdfOrganiser.mode',
|
||||||
'pipeline.title',
|
'pipeline.title',
|
||||||
|
'pipelineOptions.pipelineHeader',
|
||||||
|
'sponsor',
|
||||||
'watermark.type.2',
|
'watermark.type.2',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -102,8 +105,11 @@ ignore = [
|
|||||||
[hr_HR]
|
[hr_HR]
|
||||||
ignore = [
|
ignore = [
|
||||||
'PDFToBook.selectText.1',
|
'PDFToBook.selectText.1',
|
||||||
|
'font',
|
||||||
'home.pipeline.title',
|
'home.pipeline.title',
|
||||||
|
'info',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
'pdfOrganiser.tags',
|
||||||
'showJS.tags',
|
'showJS.tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -119,6 +125,7 @@ ignore = [
|
|||||||
|
|
||||||
[it_IT]
|
[it_IT]
|
||||||
ignore = [
|
ignore = [
|
||||||
|
'font',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'no',
|
'no',
|
||||||
'password',
|
'password',
|
||||||
@@ -141,10 +148,18 @@ ignore = [
|
|||||||
|
|
||||||
[nl_NL]
|
[nl_NL]
|
||||||
ignore = [
|
ignore = [
|
||||||
|
'HTMLToPDF.print',
|
||||||
|
'adjustContrast.contrast',
|
||||||
'compare.document.1',
|
'compare.document.1',
|
||||||
'compare.document.2',
|
'compare.document.2',
|
||||||
|
'error',
|
||||||
|
'getPdfInfo.downloadJson',
|
||||||
|
'help',
|
||||||
|
'info',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'navbar.allTools',
|
'navbar.allTools',
|
||||||
|
'printFile.submit',
|
||||||
|
'showJS.downloadJS',
|
||||||
'sponsor',
|
'sponsor',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -166,6 +181,7 @@ ignore = [
|
|||||||
|
|
||||||
[pt_BR]
|
[pt_BR]
|
||||||
ignore = [
|
ignore = [
|
||||||
|
'changeMetadata.trapped',
|
||||||
'language.direction',
|
'language.direction',
|
||||||
'pipelineOptions.pipelineHeader',
|
'pipelineOptions.pipelineHeader',
|
||||||
]
|
]
|
||||||
@@ -211,6 +227,7 @@ ignore = [
|
|||||||
[th_TH]
|
[th_TH]
|
||||||
ignore = [
|
ignore = [
|
||||||
'language.direction',
|
'language.direction',
|
||||||
|
'pipeline.title',
|
||||||
'pipelineOptions.pipelineHeader',
|
'pipelineOptions.pipelineHeader',
|
||||||
'showJS.tags',
|
'showJS.tags',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Check if a key was provided
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
echo "Please provide a key to remove."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
key_to_remove="$1"
|
|
||||||
|
|
||||||
for file in ../src/main/resources/messages_*.properties; do
|
|
||||||
# If the key ends with a dot, remove all keys starting with it
|
|
||||||
if [[ "$key_to_remove" == *. ]]; then
|
|
||||||
sed -i "/^${key_to_remove//./\\.}/d" "$file"
|
|
||||||
else
|
|
||||||
# Otherwise, remove only the exact key match
|
|
||||||
sed -i "/^${key_to_remove//./\\.}=/d" "$file"
|
|
||||||
fi
|
|
||||||
echo "Updated $file"
|
|
||||||
done
|
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
package stirling.software.SPDF.EE;
|
package stirling.software.SPDF.EE;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Lazy
|
@Lazy
|
||||||
@Slf4j
|
|
||||||
public class EEAppConfig {
|
public class EEAppConfig {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EEAppConfig.class);
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
||||||
|
|
||||||
@Bean(name = "runningEE")
|
@Bean(name = "runningEE")
|
||||||
|
|||||||
@@ -33,15 +33,11 @@ public class SPdfApplication {
|
|||||||
@Autowired private Environment env;
|
@Autowired private Environment env;
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private static String baseUrlStatic;
|
|
||||||
private static String serverPortStatic;
|
private static String serverPortStatic;
|
||||||
|
|
||||||
@Value("${baseUrl:http://localhost}")
|
|
||||||
private String baseUrl;
|
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
public void setServerPortStatic(String port) {
|
public void setServerPortStatic(String port) {
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
if (port.equalsIgnoreCase("auto")) {
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
SPdfApplication.serverPortStatic =
|
SPdfApplication.serverPortStatic =
|
||||||
"0"; // This will let Spring Boot assign an available port
|
"0"; // This will let Spring Boot assign an available port
|
||||||
@@ -69,13 +65,12 @@ public class SPdfApplication {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
baseUrlStatic = this.baseUrl;
|
|
||||||
// Check if the BROWSER_OPEN environment variable is set to true
|
// Check if the BROWSER_OPEN environment variable is set to true
|
||||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String url = baseUrl + ":" + getStaticPort();
|
String url = "http://localhost:" + getStaticPort();
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
@@ -83,9 +78,9 @@ public class SPdfApplication {
|
|||||||
// For Windows
|
// For Windows
|
||||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
} else if (os.contains("mac")) {
|
} else if (os.contains("mac")) {
|
||||||
SystemCommand.runCommand(rt, "open " + url);
|
rt.exec("open " + url);
|
||||||
} else if (os.contains("nix") || os.contains("nux")) {
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
rt.exec("xdg-open " + url);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
logger.error("Error opening browser: {}", e.getMessage());
|
||||||
@@ -143,18 +138,10 @@ public class SPdfApplication {
|
|||||||
|
|
||||||
private static void printStartupLogs() {
|
private static void printStartupLogs() {
|
||||||
logger.info("Stirling-PDF Started.");
|
logger.info("Stirling-PDF Started.");
|
||||||
String url = baseUrlStatic + ":" + getStaticPort();
|
String url = "http://localhost:" + getStaticPort();
|
||||||
logger.info("Navigate to {}", url);
|
logger.info("Navigate to {}", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
|
||||||
return baseUrlStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNonStaticBaseUrl() {
|
|
||||||
return baseUrlStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getStaticPort() {
|
public static String getStaticPort() {
|
||||||
return serverPortStatic;
|
return serverPortStatic;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
@@ -163,14 +162,12 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "analyticsPrompt")
|
@Bean(name = "analyticsPrompt")
|
||||||
@Scope("request")
|
|
||||||
public boolean analyticsPrompt() {
|
public boolean analyticsPrompt() {
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() == null
|
return applicationProperties.getSystem().getEnableAnalytics() == null
|
||||||
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "analyticsEnabled")
|
@Bean(name = "analyticsEnabled")
|
||||||
@Scope("request")
|
|
||||||
public boolean analyticsEnabled() {
|
public boolean analyticsEnabled() {
|
||||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() != null
|
return applicationProperties.getSystem().getEnableAnalytics() != null
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -43,7 +42,7 @@ public class EndpointConfiguration {
|
|||||||
|
|
||||||
public void disableEndpoint(String endpoint) {
|
public void disableEndpoint(String endpoint) {
|
||||||
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
||||||
logger.debug("Disabling {}", endpoint);
|
logger.info("Disabling {}", endpoint);
|
||||||
endpointStatuses.put(endpoint, false);
|
endpointStatuses.put(endpoint, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,23 +76,6 @@ public class EndpointConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logDisabledEndpointsSummary() {
|
|
||||||
List<String> disabledList =
|
|
||||||
endpointStatuses.entrySet().stream()
|
|
||||||
.filter(entry -> !entry.getValue()) // only get disabled endpoints (value
|
|
||||||
// is false)
|
|
||||||
.map(Map.Entry::getKey)
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (!disabledList.isEmpty()) {
|
|
||||||
logger.info(
|
|
||||||
"Total disabled endpoints: {}. Disabled endpoints: {}",
|
|
||||||
disabledList.size(),
|
|
||||||
String.join(", ", disabledList));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
// Adding endpoints to "PageOps" group
|
// Adding endpoints to "PageOps" group
|
||||||
addEndpointToGroup("PageOps", "remove-pages");
|
addEndpointToGroup("PageOps", "remove-pages");
|
||||||
@@ -117,6 +99,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Convert", "img-to-pdf");
|
addEndpointToGroup("Convert", "img-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("Convert", "file-to-pdf");
|
addEndpointToGroup("Convert", "file-to-pdf");
|
||||||
|
addEndpointToGroup("Convert", "xlsx-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-word");
|
addEndpointToGroup("Convert", "pdf-to-word");
|
||||||
addEndpointToGroup("Convert", "pdf-to-presentation");
|
addEndpointToGroup("Convert", "pdf-to-presentation");
|
||||||
addEndpointToGroup("Convert", "pdf-to-text");
|
addEndpointToGroup("Convert", "pdf-to-text");
|
||||||
@@ -162,6 +145,7 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("CLI", "repair");
|
addEndpointToGroup("CLI", "repair");
|
||||||
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("CLI", "file-to-pdf");
|
addEndpointToGroup("CLI", "file-to-pdf");
|
||||||
|
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
||||||
addEndpointToGroup("CLI", "pdf-to-word");
|
addEndpointToGroup("CLI", "pdf-to-word");
|
||||||
addEndpointToGroup("CLI", "pdf-to-presentation");
|
addEndpointToGroup("CLI", "pdf-to-presentation");
|
||||||
addEndpointToGroup("CLI", "pdf-to-html");
|
addEndpointToGroup("CLI", "pdf-to-html");
|
||||||
@@ -179,26 +163,25 @@ public class EndpointConfiguration {
|
|||||||
|
|
||||||
// python
|
// python
|
||||||
addEndpointToGroup("Python", "extract-image-scans");
|
addEndpointToGroup("Python", "extract-image-scans");
|
||||||
|
addEndpointToGroup("Python", REMOVE_BLANKS);
|
||||||
addEndpointToGroup("Python", "html-to-pdf");
|
addEndpointToGroup("Python", "html-to-pdf");
|
||||||
addEndpointToGroup("Python", "url-to-pdf");
|
addEndpointToGroup("Python", "url-to-pdf");
|
||||||
addEndpointToGroup("Python", "pdf-to-img");
|
addEndpointToGroup("Python", "pdf-to-img");
|
||||||
addEndpointToGroup("Python", "file-to-pdf");
|
|
||||||
|
|
||||||
// openCV
|
// openCV
|
||||||
addEndpointToGroup("OpenCV", "extract-image-scans");
|
addEndpointToGroup("OpenCV", "extract-image-scans");
|
||||||
|
addEndpointToGroup("OpenCV", REMOVE_BLANKS);
|
||||||
|
|
||||||
// LibreOffice
|
// LibreOffice
|
||||||
addEndpointToGroup("LibreOffice", "repair");
|
addEndpointToGroup("LibreOffice", "repair");
|
||||||
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
||||||
|
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-rtf");
|
addEndpointToGroup("LibreOffice", "pdf-to-rtf");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
||||||
|
|
||||||
// Unoconv
|
|
||||||
addEndpointToGroup("Unoconv", "file-to-pdf");
|
|
||||||
|
|
||||||
// OCRmyPDF
|
// OCRmyPDF
|
||||||
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
||||||
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
||||||
@@ -247,18 +230,6 @@ public class EndpointConfiguration {
|
|||||||
addEndpointToGroup("Javascript", "sign");
|
addEndpointToGroup("Javascript", "sign");
|
||||||
addEndpointToGroup("Javascript", "compare");
|
addEndpointToGroup("Javascript", "compare");
|
||||||
addEndpointToGroup("Javascript", "adjust-contrast");
|
addEndpointToGroup("Javascript", "adjust-contrast");
|
||||||
|
|
||||||
// Ghostscript dependent endpoints
|
|
||||||
addEndpointToGroup("Ghostscript", "compress-pdf");
|
|
||||||
addEndpointToGroup("Ghostscript", "pdf-to-pdfa");
|
|
||||||
addEndpointToGroup("Ghostscript", "repair");
|
|
||||||
|
|
||||||
// Weasyprint dependent endpoints
|
|
||||||
addEndpointToGroup("Weasyprint", "html-to-pdf");
|
|
||||||
addEndpointToGroup("Weasyprint", "url-to-pdf");
|
|
||||||
|
|
||||||
// Pdftohtml dependent endpoints
|
|
||||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
@@ -280,9 +251,5 @@ public class EndpointConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getEndpointsForGroup(String group) {
|
|
||||||
return endpointGroups.getOrDefault(group, new HashSet<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String REMOVE_BLANKS = "remove-blanks";
|
private static final String REMOVE_BLANKS = "remove-blanks";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
package stirling.software.SPDF.config;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Slf4j
|
|
||||||
public class ExternalAppDepConfig {
|
|
||||||
@Autowired private EndpointConfiguration endpointConfiguration;
|
|
||||||
|
|
||||||
private boolean isCommandAvailable(String command) {
|
|
||||||
try {
|
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
|
||||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
|
||||||
processBuilder.command("where", command);
|
|
||||||
} else {
|
|
||||||
processBuilder.command("which", command);
|
|
||||||
}
|
|
||||||
Process process = processBuilder.start();
|
|
||||||
int exitCode = process.waitFor();
|
|
||||||
return exitCode == 0;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("Error checking for command {}: {}", command, e.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, List<String>> commandToGroupMapping =
|
|
||||||
new HashMap<>() {
|
|
||||||
{
|
|
||||||
put("gs", List.of("Ghostscript"));
|
|
||||||
put("soffice", List.of("LibreOffice"));
|
|
||||||
put("ocrmypdf", List.of("OCRmyPDF"));
|
|
||||||
put("weasyprint", List.of("Weasyprint"));
|
|
||||||
put("pdftohtml", List.of("Pdftohtml"));
|
|
||||||
put("unoconv", List.of("Unoconv"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private List<String> getAffectedFeatures(String group) {
|
|
||||||
return endpointConfiguration.getEndpointsForGroup(group).stream()
|
|
||||||
.map(endpoint -> formatEndpointAsFeature(endpoint))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatEndpointAsFeature(String endpoint) {
|
|
||||||
// First replace common terms
|
|
||||||
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
|
|
||||||
|
|
||||||
// Split into words and capitalize each word
|
|
||||||
return Arrays.stream(feature.split("\\s+"))
|
|
||||||
.map(word -> capitalizeWord(word))
|
|
||||||
.collect(Collectors.joining(" "));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String capitalizeWord(String word) {
|
|
||||||
if (word.isEmpty()) {
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
if ("pdf".equalsIgnoreCase(word)) {
|
|
||||||
return "PDF";
|
|
||||||
}
|
|
||||||
return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkDependencyAndDisableGroup(String command) {
|
|
||||||
boolean isAvailable = isCommandAvailable(command);
|
|
||||||
if (!isAvailable) {
|
|
||||||
List<String> affectedGroups = commandToGroupMapping.get(command);
|
|
||||||
|
|
||||||
if (affectedGroups != null) {
|
|
||||||
for (String group : affectedGroups) {
|
|
||||||
List<String> affectedFeatures = getAffectedFeatures(group);
|
|
||||||
endpointConfiguration.disableGroup(group);
|
|
||||||
log.warn(
|
|
||||||
"Missing dependency: {} - Disabling group: {} (Affected features: {})",
|
|
||||||
command,
|
|
||||||
group,
|
|
||||||
affectedFeatures != null && !affectedFeatures.isEmpty()
|
|
||||||
? String.join(", ", affectedFeatures)
|
|
||||||
: "unknown");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void checkDependencies() {
|
|
||||||
|
|
||||||
// Check core dependencies
|
|
||||||
checkDependencyAndDisableGroup("gs");
|
|
||||||
checkDependencyAndDisableGroup("soffice");
|
|
||||||
checkDependencyAndDisableGroup("ocrmypdf");
|
|
||||||
checkDependencyAndDisableGroup("weasyprint");
|
|
||||||
checkDependencyAndDisableGroup("pdftohtml");
|
|
||||||
checkDependencyAndDisableGroup("unoconv");
|
|
||||||
|
|
||||||
// Special handling for Python/OpenCV dependencies
|
|
||||||
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
|
|
||||||
if (!pythonAvailable) {
|
|
||||||
List<String> pythonFeatures = getAffectedFeatures("Python");
|
|
||||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
|
||||||
|
|
||||||
endpointConfiguration.disableGroup("Python");
|
|
||||||
endpointConfiguration.disableGroup("OpenCV");
|
|
||||||
log.warn(
|
|
||||||
"Missing dependency: Python - Disabling Python features: {} and OpenCV features: {}",
|
|
||||||
String.join(", ", pythonFeatures),
|
|
||||||
String.join(", ", openCVFeatures));
|
|
||||||
} else {
|
|
||||||
// If Python is available, check for OpenCV
|
|
||||||
try {
|
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder();
|
|
||||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
|
||||||
processBuilder.command("python", "-c", "import cv2");
|
|
||||||
} else {
|
|
||||||
processBuilder.command("python3", "-c", "import cv2");
|
|
||||||
}
|
|
||||||
Process process = processBuilder.start();
|
|
||||||
int exitCode = process.waitFor();
|
|
||||||
if (exitCode != 0) {
|
|
||||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
|
||||||
endpointConfiguration.disableGroup("OpenCV");
|
|
||||||
log.warn(
|
|
||||||
"OpenCV not available in Python - Disabling OpenCV features: {}",
|
|
||||||
String.join(", ", openCVFeatures));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
|
||||||
endpointConfiguration.disableGroup("OpenCV");
|
|
||||||
log.warn(
|
|
||||||
"Error checking OpenCV: {} - Disabling OpenCV features: {}",
|
|
||||||
e.getMessage(),
|
|
||||||
String.join(", ", openCVFeatures));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endpointConfiguration.logDisabledEndpointsSummary();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,6 @@ import org.springframework.core.Ordered;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import io.micrometer.common.util.StringUtils;
|
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@@ -41,23 +39,4 @@ public class InitialSetup {
|
|||||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initLegalUrls() throws IOException {
|
|
||||||
// Initialize Terms and Conditions
|
|
||||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
|
||||||
if (StringUtils.isEmpty(termsUrl)) {
|
|
||||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
|
||||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
|
|
||||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Privacy Policy
|
|
||||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
|
||||||
if (StringUtils.isEmpty(privacyUrl)) {
|
|
||||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
|
||||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
|
|
||||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,237 +1,27 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
|
||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
import com.coveo.saml.SamlClient;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.Provider;
|
|
||||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLogoutSuccess(
|
public void onLogoutSuccess(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
if (!response.isCommitted()) {
|
if (request.getParameter("userIsDisabled") != null) {
|
||||||
// Handle user logout due to disabled account
|
getRedirectStrategy()
|
||||||
if (request.getParameter("userIsDisabled") != null) {
|
.sendRedirect(request, response, "/login?erroroauth=userIsDisabled");
|
||||||
response.sendRedirect(
|
return;
|
||||||
request.getContextPath() + "/login?erroroauth=userIsDisabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle OAuth2 authentication error
|
|
||||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
|
||||||
response.sendRedirect(
|
|
||||||
request.getContextPath() + "/login?erroroauth=userAlreadyExistsWeb");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (authentication != null) {
|
|
||||||
// Handle SAML2 logout redirection
|
|
||||||
if (authentication instanceof Saml2Authentication) {
|
|
||||||
getRedirect_saml2(request, response, authentication);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle OAuth2 logout redirection
|
|
||||||
else if (authentication instanceof OAuth2AuthenticationToken) {
|
|
||||||
getRedirect_oauth2(request, response, authentication);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle Username/Password logout
|
|
||||||
else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle unknown authentication types
|
|
||||||
else {
|
|
||||||
log.error(
|
|
||||||
"authentication class unknown: "
|
|
||||||
+ authentication.getClass().getSimpleName());
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Redirect to login page after logout
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect for SAML2 authentication logout
|
|
||||||
private void getRedirect_saml2(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
|
||||||
String registrationId = samlConf.getRegistrationId();
|
|
||||||
|
|
||||||
Saml2Authentication samlAuthentication = (Saml2Authentication) authentication;
|
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
|
||||||
|
|
||||||
String nameIdValue = principal.getName();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Read certificate from the resource
|
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
|
||||||
X509Certificate certificate = CertificateUtils.readCertificate(certificateResource);
|
|
||||||
|
|
||||||
List<X509Certificate> certificates = new ArrayList<>();
|
|
||||||
certificates.add(certificate);
|
|
||||||
|
|
||||||
// Construct URLs required for SAML configuration
|
|
||||||
String serverUrl =
|
|
||||||
SPdfApplication.getStaticBaseUrl() + ":" + SPdfApplication.getStaticPort();
|
|
||||||
|
|
||||||
String relyingPartyIdentifier =
|
|
||||||
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
|
||||||
|
|
||||||
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
|
|
||||||
|
|
||||||
String idpUrl = samlConf.getIdpSingleLogoutUrl();
|
|
||||||
|
|
||||||
String idpIssuer = samlConf.getIdpIssuer();
|
|
||||||
|
|
||||||
// Create SamlClient instance for SAML logout
|
|
||||||
SamlClient samlClient =
|
|
||||||
new SamlClient(
|
|
||||||
relyingPartyIdentifier,
|
|
||||||
assertionConsumerServiceUrl,
|
|
||||||
idpUrl,
|
|
||||||
idpIssuer,
|
|
||||||
certificates,
|
|
||||||
SamlClient.SamlIdpBinding.POST);
|
|
||||||
|
|
||||||
// Read private key for service provider
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
|
||||||
RSAPrivateKey privateKey = CertificateUtils.readPrivateKey(privateKeyResource);
|
|
||||||
|
|
||||||
// Set service provider keys for the SamlClient
|
|
||||||
samlClient.setSPKeys(certificate, privateKey);
|
|
||||||
|
|
||||||
// Redirect to identity provider for logout
|
|
||||||
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(nameIdValue, e);
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect for OAuth2 authentication logout
|
|
||||||
private void getRedirect_oauth2(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException {
|
|
||||||
String param = "logout=true";
|
|
||||||
String registrationId = null;
|
|
||||||
String issuer = null;
|
|
||||||
String clientId = null;
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
|
|
||||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
|
||||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
|
||||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get OAuth2 provider details from configuration
|
|
||||||
Provider provider = oauth.getClient().get(registrationId);
|
|
||||||
issuer = provider.getIssuer();
|
|
||||||
clientId = provider.getClientId();
|
|
||||||
} catch (UnsupportedProviderException e) {
|
|
||||||
log.error(e.getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
|
||||||
issuer = oauth.getIssuer();
|
|
||||||
clientId = oauth.getClientId();
|
|
||||||
}
|
|
||||||
String errorMessage = "";
|
|
||||||
// Handle different error scenarios during logout
|
|
||||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
|
||||||
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
|
||||||
} else if ((errorMessage = request.getParameter("error")) != null) {
|
|
||||||
param = "error=" + sanitizeInput(errorMessage);
|
|
||||||
} else if ((errorMessage = request.getParameter("erroroauth")) != null) {
|
|
||||||
param = "erroroauth=" + sanitizeInput(errorMessage);
|
|
||||||
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
|
||||||
param = "error=oauth2AutoCreateDisabled";
|
|
||||||
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
|
|
||||||
param = "erroroauth=oauth2_admin_blocked_user";
|
|
||||||
} else if (request.getParameter("userIsDisabled") != null) {
|
|
||||||
param = "erroroauth=userIsDisabled";
|
|
||||||
} else if (request.getParameter("badcredentials") != null) {
|
|
||||||
param = "error=badcredentials";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
|
|
||||||
// Redirect based on OAuth2 provider
|
|
||||||
switch (registrationId.toLowerCase()) {
|
|
||||||
case "keycloak":
|
|
||||||
// Add Keycloak specific logout URL if needed
|
|
||||||
String logoutUrl =
|
|
||||||
issuer
|
|
||||||
+ "/protocol/openid-connect/logout"
|
|
||||||
+ "?client_id="
|
|
||||||
+ clientId
|
|
||||||
+ "&post_logout_redirect_uri="
|
|
||||||
+ response.encodeRedirectURL(redirect_url);
|
|
||||||
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
|
||||||
response.sendRedirect(logoutUrl);
|
|
||||||
break;
|
|
||||||
case "github":
|
|
||||||
// Add GitHub specific logout URL if needed
|
|
||||||
String githubLogoutUrl = "https://github.com/logout";
|
|
||||||
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
|
||||||
response.sendRedirect(githubLogoutUrl);
|
|
||||||
break;
|
|
||||||
case "google":
|
|
||||||
// Add Google specific logout URL if needed
|
|
||||||
// String googleLogoutUrl =
|
|
||||||
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
|
||||||
// + response.encodeRedirectURL(redirect_url);
|
|
||||||
log.info("Google does not have a specific logout URL");
|
|
||||||
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
|
||||||
// response.sendRedirect(googleLogoutUrl);
|
|
||||||
// break;
|
|
||||||
default:
|
|
||||||
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
|
||||||
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
|
||||||
response.sendRedirect(defaultRedirectUrl);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize input to avoid potential security vulnerabilities
|
|
||||||
private String sanitizeInput(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,40 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
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;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -68,6 +45,12 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@Autowired private CustomUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
@@ -88,49 +71,17 @@ public class SecurityConfiguration {
|
|||||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
@Autowired private SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
|
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.authenticationManager(authenticationManager(http));
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
http.addFilterBefore(
|
http.addFilterBefore(
|
||||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
} else {
|
|
||||||
CookieCsrfTokenRepository cookieRepo =
|
|
||||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
|
||||||
CsrfTokenRequestAttributeHandler requestHandler =
|
|
||||||
new CsrfTokenRequestAttributeHandler();
|
|
||||||
requestHandler.setCsrfRequestAttributeName(null);
|
|
||||||
http.csrf(
|
|
||||||
csrf ->
|
|
||||||
csrf.ignoringRequestMatchers(
|
|
||||||
request -> {
|
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
|
||||||
|
|
||||||
// If there's no API key, don't ignore CSRF
|
|
||||||
// (return false)
|
|
||||||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate API key using existing UserService
|
|
||||||
try {
|
|
||||||
Optional<User> user =
|
|
||||||
userService.getUserByApiKey(apiKey);
|
|
||||||
// If API key is valid, ignore CSRF (return
|
|
||||||
// true)
|
|
||||||
// If API key is invalid, don't ignore CSRF
|
|
||||||
// (return false)
|
|
||||||
return user.isPresent();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// If there's any error validating the API
|
|
||||||
// key, don't ignore CSRF
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.csrfTokenRepository(cookieRepo)
|
|
||||||
.csrfTokenRequestHandler(requestHandler));
|
|
||||||
}
|
}
|
||||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
@@ -143,138 +94,134 @@ public class SecurityConfiguration {
|
|||||||
.sessionRegistry(sessionRegistry)
|
.sessionRegistry(sessionRegistry)
|
||||||
.expiredUrl("/login?logout=true"));
|
.expiredUrl("/login?logout=true"));
|
||||||
|
|
||||||
http.authenticationProvider(daoAuthenticationProvider());
|
http.formLogin(
|
||||||
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
formLogin ->
|
||||||
http.logout(
|
formLogin
|
||||||
logout ->
|
.loginPage("/login")
|
||||||
logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
.successHandler(
|
||||||
.logoutSuccessHandler(
|
new CustomAuthenticationSuccessHandler(
|
||||||
new CustomLogoutSuccessHandler(applicationProperties))
|
loginAttemptService, userService))
|
||||||
.clearAuthentication(true)
|
.defaultSuccessUrl("/")
|
||||||
.invalidateHttpSession(true) // Invalidate session
|
.failureHandler(
|
||||||
.deleteCookies("JSESSIONID", "remember-me"));
|
new CustomAuthenticationFailureHandler(
|
||||||
http.rememberMe(
|
loginAttemptService, userService))
|
||||||
rememberMeConfigurer ->
|
.permitAll())
|
||||||
rememberMeConfigurer // Use the configurator directly
|
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
|
||||||
.tokenRepository(persistentTokenRepository())
|
.logout(
|
||||||
.tokenValiditySeconds(14 * 24 * 60 * 60) // 14 days
|
logout ->
|
||||||
.userDetailsService(
|
logout.logoutRequestMatcher(
|
||||||
userDetailsService) // Your existing UserDetailsService
|
new AntPathRequestMatcher("/logout"))
|
||||||
.useSecureCookie(true) // Enable secure cookie
|
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
|
||||||
.rememberMeParameter("remember-me") // Form parameter name
|
.invalidateHttpSession(true) // Invalidate session
|
||||||
.rememberMeCookieName("remember-me") // Cookie name
|
.deleteCookies("JSESSIONID", "remember-me"))
|
||||||
.alwaysRemember(false));
|
.rememberMe(
|
||||||
http.authorizeHttpRequests(
|
rememberMeConfigurer ->
|
||||||
authz ->
|
rememberMeConfigurer // Use the configurator directly
|
||||||
authz.requestMatchers(
|
.key("uniqueAndSecret")
|
||||||
req -> {
|
.tokenRepository(persistentTokenRepository())
|
||||||
String uri = req.getRequestURI();
|
.tokenValiditySeconds(1209600) // 2 weeks
|
||||||
String contextPath = req.getContextPath();
|
)
|
||||||
|
.authorizeHttpRequests(
|
||||||
|
authz ->
|
||||||
|
authz.requestMatchers(
|
||||||
|
req -> {
|
||||||
|
String uri = req.getRequestURI();
|
||||||
|
String contextPath = req.getContextPath();
|
||||||
|
|
||||||
// Remove the context path from the URI
|
// Remove the context path from the URI
|
||||||
String trimmedUri =
|
String trimmedUri =
|
||||||
uri.startsWith(contextPath)
|
uri.startsWith(contextPath)
|
||||||
? uri.substring(
|
? uri.substring(
|
||||||
contextPath.length())
|
contextPath
|
||||||
: uri;
|
.length())
|
||||||
|
: uri;
|
||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.startsWith("/oauth")
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|| trimmedUri.startsWith("/saml2")
|
|| trimmedUri.startsWith("/saml2")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith("/register")
|
|| trimmedUri.startsWith(
|
||||||
|| trimmedUri.startsWith("/error")
|
"/register")
|
||||||
|| trimmedUri.startsWith("/images/")
|
|| trimmedUri.startsWith("/error")
|
||||||
|| trimmedUri.startsWith("/public/")
|
|| trimmedUri.startsWith("/images/")
|
||||||
|| trimmedUri.startsWith("/css/")
|
|| trimmedUri.startsWith("/public/")
|
||||||
|| trimmedUri.startsWith("/fonts/")
|
|| trimmedUri.startsWith("/css/")
|
||||||
|| trimmedUri.startsWith("/js/")
|
|| trimmedUri.startsWith("/fonts/")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith("/js/")
|
||||||
"/api/v1/info/status");
|
|| trimmedUri.startsWith(
|
||||||
})
|
"/api/v1/info/status");
|
||||||
.permitAll()
|
})
|
||||||
.anyRequest()
|
.permitAll()
|
||||||
.authenticated());
|
.anyRequest()
|
||||||
|
.authenticated());
|
||||||
// Handle User/Password Logins
|
|
||||||
if (applicationProperties.getSecurity().isUserPass()) {
|
|
||||||
http.formLogin(
|
|
||||||
formLogin ->
|
|
||||||
formLogin
|
|
||||||
.loginPage("/login")
|
|
||||||
.successHandler(
|
|
||||||
new CustomAuthenticationSuccessHandler(
|
|
||||||
loginAttemptService, userService))
|
|
||||||
.failureHandler(
|
|
||||||
new CustomAuthenticationFailureHandler(
|
|
||||||
loginAttemptService, userService))
|
|
||||||
.defaultSuccessUrl("/")
|
|
||||||
.permitAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().isOauth2Activ()) {
|
if (applicationProperties.getSecurity().getOauth2() != null
|
||||||
|
&& applicationProperties.getSecurity().getOauth2().getEnabled()
|
||||||
|
&& !applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getLoginMethod()
|
||||||
|
.equalsIgnoreCase("normal")) {
|
||||||
|
|
||||||
http.oauth2Login(
|
http.oauth2Login(
|
||||||
oauth2 ->
|
oauth2 ->
|
||||||
oauth2.loginPage("/oauth2")
|
oauth2.loginPage("/oauth2")
|
||||||
/*
|
/*
|
||||||
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
||||||
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
||||||
is set as true, else login fails with an error message advising the same.
|
is set as true, else login fails with an error message advising the same.
|
||||||
*/
|
*/
|
||||||
.successHandler(
|
|
||||||
new CustomOAuth2AuthenticationSuccessHandler(
|
|
||||||
loginAttemptService,
|
|
||||||
applicationProperties,
|
|
||||||
userService))
|
|
||||||
.failureHandler(
|
|
||||||
new CustomOAuth2AuthenticationFailureHandler())
|
|
||||||
// Add existing Authorities from the database
|
|
||||||
.userInfoEndpoint(
|
|
||||||
userInfoEndpoint ->
|
|
||||||
userInfoEndpoint
|
|
||||||
.oidcUserService(
|
|
||||||
new CustomOAuth2UserService(
|
|
||||||
applicationProperties,
|
|
||||||
userService,
|
|
||||||
loginAttemptService))
|
|
||||||
.userAuthoritiesMapper(
|
|
||||||
userAuthoritiesMapper()))
|
|
||||||
.permitAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle SAML
|
|
||||||
if (applicationProperties.getSecurity().isSaml2Activ()
|
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
|
||||||
http.authenticationProvider(samlAuthenticationProvider());
|
|
||||||
http.saml2Login(
|
|
||||||
saml2 ->
|
|
||||||
saml2.loginPage("/saml2")
|
|
||||||
.successHandler(
|
.successHandler(
|
||||||
new CustomSaml2AuthenticationSuccessHandler(
|
new CustomOAuth2AuthenticationSuccessHandler(
|
||||||
loginAttemptService,
|
loginAttemptService,
|
||||||
applicationProperties,
|
applicationProperties,
|
||||||
userService))
|
userService))
|
||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomSaml2AuthenticationFailureHandler())
|
new CustomOAuth2AuthenticationFailureHandler())
|
||||||
.permitAll())
|
// Add existing Authorities from the database
|
||||||
|
.userInfoEndpoint(
|
||||||
|
userInfoEndpoint ->
|
||||||
|
userInfoEndpoint
|
||||||
|
.oidcUserService(
|
||||||
|
new CustomOAuth2UserService(
|
||||||
|
applicationProperties,
|
||||||
|
userService,
|
||||||
|
loginAttemptService))
|
||||||
|
.userAuthoritiesMapper(
|
||||||
|
userAuthoritiesMapper)))
|
||||||
|
.logout(
|
||||||
|
logout ->
|
||||||
|
logout.logoutSuccessHandler(
|
||||||
|
new CustomOAuth2LogoutSuccessHandler(
|
||||||
|
applicationProperties)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle SAML
|
||||||
|
if (applicationProperties.getSecurity().getSaml() != null
|
||||||
|
&& applicationProperties.getSecurity().getSaml().getEnabled()
|
||||||
|
&& !applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getLoginMethod()
|
||||||
|
.equalsIgnoreCase("normal")) {
|
||||||
|
http.saml2Login(
|
||||||
|
saml2 -> {
|
||||||
|
saml2.loginPage("/saml2")
|
||||||
|
.relyingPartyRegistrationRepository(
|
||||||
|
relyingPartyRegistrationRepository)
|
||||||
|
.successHandler(
|
||||||
|
new CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
loginAttemptService,
|
||||||
|
userService,
|
||||||
|
applicationProperties))
|
||||||
|
.failureHandler(
|
||||||
|
new CustomSAMLAuthenticationFailureHandler());
|
||||||
|
})
|
||||||
.addFilterBefore(
|
.addFilterBefore(
|
||||||
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
} else {
|
|
||||||
CookieCsrfTokenRepository cookieRepo =
|
|
||||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
|
||||||
CsrfTokenRequestAttributeHandler requestHandler =
|
|
||||||
new CsrfTokenRequestAttributeHandler();
|
|
||||||
requestHandler.setCsrfRequestAttributeName(null);
|
|
||||||
http.csrf(
|
|
||||||
csrf ->
|
|
||||||
csrf.csrfTokenRepository(cookieRepo)
|
|
||||||
.csrfTokenRequestHandler(requestHandler));
|
|
||||||
}
|
}
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
@@ -284,234 +231,39 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(
|
||||||
name = "security.saml2.enabled",
|
name = "security.saml.enabled",
|
||||||
havingValue = "true",
|
havingValue = "true",
|
||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public AuthenticationProvider samlAuthenticationProvider() {
|
public AuthenticationProvider samlAuthenticationProvider() {
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
authenticationProvider.setResponseAuthenticationConverter(
|
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
||||||
new CustomSaml2ResponseAuthenticationConverter(userService));
|
|
||||||
return authenticationProvider;
|
return authenticationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Registration Repository for OAUTH2 OIDC Login
|
// @Bean
|
||||||
@Bean
|
// public AuthenticationProvider daoAuthenticationProvider() {
|
||||||
@ConditionalOnProperty(
|
// DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
value = "security.oauth2.enabled",
|
// provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
||||||
havingValue = "true",
|
// provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
||||||
matchIfMissing = false)
|
// return provider;
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
// }
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
|
||||||
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
|
||||||
log.error("At least one OAuth2 provider must be configured");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
|
||||||
.registrationId(keycloak.getName())
|
|
||||||
.clientId(keycloak.getClientId())
|
|
||||||
.clientSecret(keycloak.getClientSecret())
|
|
||||||
.scope(keycloak.getScopes())
|
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
|
||||||
.clientName(keycloak.getClientName())
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
|
||||||
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
return github != null && github.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
|
||||||
.clientId(github.getClientId())
|
|
||||||
.clientSecret(github.getClientSecret())
|
|
||||||
.scope(github.getScopes())
|
|
||||||
.authorizationUri(github.getAuthorizationuri())
|
|
||||||
.tokenUri(github.getTokenuri())
|
|
||||||
.userInfoUri(github.getUserinfouri())
|
|
||||||
.userNameAttributeName(github.getUseAsUsername())
|
|
||||||
.clientName(github.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null
|
|
||||||
|| oauth.getIssuer() == null
|
|
||||||
|| oauth.getIssuer().isEmpty()
|
|
||||||
|| oauth.getClientId() == null
|
|
||||||
|| oauth.getClientId().isEmpty()
|
|
||||||
|| oauth.getClientSecret() == null
|
|
||||||
|| oauth.getClientSecret().isEmpty()
|
|
||||||
|| oauth.getScopes() == null
|
|
||||||
|| oauth.getScopes().isEmpty()
|
|
||||||
|| oauth.getUseAsUsername() == null
|
|
||||||
|| oauth.getUseAsUsername().isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId("oidc")
|
|
||||||
.clientId(oauth.getClientId())
|
|
||||||
.clientSecret(oauth.getClientSecret())
|
|
||||||
.scope(oauth.getScopes())
|
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
|
||||||
.clientName("OIDC")
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||||
name = "security.saml2.enabled",
|
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||||
havingValue = "true",
|
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||||
matchIfMissing = false)
|
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
// authenticationManagerBuilder =
|
||||||
|
// authenticationManagerBuilder.authenticationProvider(
|
||||||
|
// daoAuthenticationProvider()); // Benutzername/Passwort
|
||||||
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
if (applicationProperties.getSecurity().getSaml() != null
|
||||||
|
&& applicationProperties.getSecurity().getSaml().getEnabled()) {
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
authenticationManagerBuilder.authenticationProvider(
|
||||||
|
samlAuthenticationProvider()); // SAML
|
||||||
Saml2X509Credential signingCredential =
|
}
|
||||||
new Saml2X509Credential(
|
return authenticationManagerBuilder.build();
|
||||||
CertificateUtils.readPrivateKey(privateKeyResource),
|
|
||||||
CertificateUtils.readCertificate(certificateResource),
|
|
||||||
Saml2X509CredentialType.SIGNING);
|
|
||||||
|
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
|
||||||
|
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
|
||||||
|
|
||||||
RelyingPartyRegistration rp =
|
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
|
||||||
.signingX509Credentials((c) -> c.add(signingCredential))
|
|
||||||
.assertingPartyDetails(
|
|
||||||
(details) ->
|
|
||||||
details.entityId(samlConf.getIdpIssuer())
|
|
||||||
.singleSignOnServiceLocation(
|
|
||||||
samlConf.getIdpSingleLoginUrl())
|
|
||||||
.verificationX509Credentials(
|
|
||||||
(c) -> c.add(verificationCredential))
|
|
||||||
.wantAuthnRequestsSigned(true))
|
|
||||||
.build();
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
|
||||||
provider.setUserDetailsService(userDetailsService);
|
|
||||||
provider.setPasswordEncoder(passwordEncoder());
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
|
||||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
|
||||||
return (authorities) -> {
|
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
|
||||||
|
|
||||||
authorities.forEach(
|
|
||||||
authority -> {
|
|
||||||
// Add existing OAUTH2 Authorities
|
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
|
||||||
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
|
||||||
String useAsUsername =
|
|
||||||
applicationProperties
|
|
||||||
.getSecurity()
|
|
||||||
.getOauth2()
|
|
||||||
.getUseAsUsername();
|
|
||||||
Optional<User> userOpt =
|
|
||||||
userService.findByUsernameIgnoreCase(
|
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
|
||||||
new SimpleGrantedAuthority(
|
|
||||||
userService.findRole(user).getAuthority()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mappedAuthorities;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
@@ -112,9 +111,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter()
|
response.getWriter()
|
||||||
.write(
|
.write(
|
||||||
"Authentication required. Please provide a X-API-KEY in request header.\n"
|
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternatively you can disable authentication if this is unexpected");
|
||||||
+ "This is found in Settings -> Account Settings -> API Key\n"
|
|
||||||
+ "Alternatively you can disable authentication if this is unexpected");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,8 +124,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
username = ((UserDetails) principal).getUsername();
|
username = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
username = ((OAuth2User) principal).getName();
|
username = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
username = (String) principal;
|
username = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,9 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.Authority;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
@@ -33,7 +30,6 @@ import stirling.software.SPDF.repository.AuthorityRepository;
|
|||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
|
||||||
public class UserService implements UserServiceInterface {
|
public class UserService implements UserServiceInterface {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
@Autowired private UserRepository userRepository;
|
||||||
@@ -48,7 +44,9 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
public long getTotalUserCount() {
|
||||||
|
return userRepository.count();
|
||||||
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
||||||
@@ -304,13 +302,7 @@ public class UserService implements UserServiceInterface {
|
|||||||
boolean isValidEmail =
|
boolean isValidEmail =
|
||||||
username.matches(
|
username.matches(
|
||||||
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
|
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
|
||||||
|
return isValidSimpleUsername || isValidEmail;
|
||||||
List<String> notAllowedUserList = new ArrayList<>();
|
|
||||||
notAllowedUserList.add("ALL_USERS".toLowerCase());
|
|
||||||
|
|
||||||
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
|
|
||||||
|
|
||||||
return (isValidSimpleUsername || isValidEmail) && !notAllowedUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getInvalidUsernameMessage() {
|
private String getInvalidUsernameMessage() {
|
||||||
@@ -346,10 +338,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
OAuth2User oAuth2User = (OAuth2User) principal;
|
OAuth2User oAuth2User = (OAuth2User) principal;
|
||||||
usernameP = oAuth2User.getName();
|
usernameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
CustomSaml2AuthenticatedPrincipal saml2User =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
usernameP = saml2User.getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
usernameP = (String) principal;
|
usernameP = (String) principal;
|
||||||
}
|
}
|
||||||
@@ -365,21 +353,8 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
return ((UserDetails) principal).getUsername();
|
return ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
|
||||||
return ((OAuth2User) principal)
|
|
||||||
.getAttribute(
|
|
||||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
return ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
|
||||||
return (String) principal;
|
|
||||||
} else {
|
} else {
|
||||||
return principal.toString();
|
return principal.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTotalUsersCount() {
|
|
||||||
return userRepository.count();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
}
|
}
|
||||||
log.error("OAuth2 Authentication error: " + errorCode);
|
log.error("OAuth2 Authentication error: " + errorCode);
|
||||||
log.error("OAuth2AuthenticationException", exception);
|
log.error("OAuth2AuthenticationException", exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=" + errorCode);
|
getRedirectStrategy()
|
||||||
|
.sendRedirect(request, response, "/logout?erroroauth=" + errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.error("Unhandled authentication exception", exception);
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
if (userService.isUserDisabled(username)) {
|
|
||||||
getRedirectStrategy()
|
|
||||||
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (userService.usernameExistsIgnoreCase(username)
|
if (userService.usernameExistsIgnoreCase(username)
|
||||||
&& userService.hasPassword(username)
|
&& userService.hasPassword(username)
|
||||||
&& !userService.isAuthenticationTypeByUsername(
|
&& !userService.isAuthenticationTypeByUsername(
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public CustomOAuth2LogoutSuccessHandler(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
String param = "logout=true";
|
||||||
|
String registrationId = null;
|
||||||
|
String issuer = null;
|
||||||
|
String clientId = null;
|
||||||
|
|
||||||
|
if (authentication == null) {
|
||||||
|
if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/login?erroroauth=userIsDisabled");
|
||||||
|
} else {
|
||||||
|
super.onLogoutSuccess(request, response, authentication);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||||
|
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Provider provider = oauth.getClient().get(registrationId);
|
||||||
|
issuer = provider.getIssuer();
|
||||||
|
clientId = provider.getClientId();
|
||||||
|
} catch (UnsupportedProviderException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||||
|
issuer = oauth.getIssuer();
|
||||||
|
clientId = oauth.getClientId();
|
||||||
|
}
|
||||||
|
String errorMessage = "";
|
||||||
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
|
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||||
|
} else if ((errorMessage = request.getParameter("error")) != null) {
|
||||||
|
param = "error=" + sanitizeInput(errorMessage);
|
||||||
|
} else if ((errorMessage = request.getParameter("erroroauth")) != null) {
|
||||||
|
param = "erroroauth=" + sanitizeInput(errorMessage);
|
||||||
|
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
||||||
|
param = "error=oauth2AutoCreateDisabled";
|
||||||
|
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
|
||||||
|
param = "erroroauth=oauth2_admin_blocked_user";
|
||||||
|
} else if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
param = "erroroauth=userIsDisabled";
|
||||||
|
} else if (request.getParameter("badcredentials") != null) {
|
||||||
|
param = "error=badcredentials";
|
||||||
|
}
|
||||||
|
|
||||||
|
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||||
|
|
||||||
|
switch (registrationId.toLowerCase()) {
|
||||||
|
case "keycloak":
|
||||||
|
// Add Keycloak specific logout URL if needed
|
||||||
|
String logoutUrl =
|
||||||
|
issuer
|
||||||
|
+ "/protocol/openid-connect/logout"
|
||||||
|
+ "?client_id="
|
||||||
|
+ clientId
|
||||||
|
+ "&post_logout_redirect_uri="
|
||||||
|
+ response.encodeRedirectURL(redirect_url);
|
||||||
|
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||||
|
response.sendRedirect(logoutUrl);
|
||||||
|
break;
|
||||||
|
case "github":
|
||||||
|
// Add GitHub specific logout URL if needed
|
||||||
|
String githubLogoutUrl = "https://github.com/logout";
|
||||||
|
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||||
|
response.sendRedirect(githubLogoutUrl);
|
||||||
|
break;
|
||||||
|
case "google":
|
||||||
|
// Add Google specific logout URL if needed
|
||||||
|
// String googleLogoutUrl =
|
||||||
|
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||||
|
// + response.encodeRedirectURL(redirect_url);
|
||||||
|
log.info("Google does not have a specific logout URL");
|
||||||
|
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||||
|
// response.sendRedirect(googleLogoutUrl);
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
||||||
|
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
||||||
|
response.sendRedirect(defaultRedirectUrl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sanitizeInput(String input) {
|
||||||
|
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ConvertResponseToAuthentication
|
||||||
|
implements Converter<ResponseToken, Saml2Authentication> {
|
||||||
|
|
||||||
|
private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup;
|
||||||
|
|
||||||
|
public ConvertResponseToAuthentication(
|
||||||
|
Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) {
|
||||||
|
this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||||
|
final Assertion assertion =
|
||||||
|
CollectionUtils.firstElement(responseToken.getResponse().getAssertions());
|
||||||
|
final Map<String, List<Object>> attributes =
|
||||||
|
SamlAssertionUtils.getAssertionAttributes(assertion);
|
||||||
|
final String registrationId =
|
||||||
|
responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
|
||||||
|
final ScimSaml2AuthenticatedPrincipal principal =
|
||||||
|
new ScimSaml2AuthenticatedPrincipal(
|
||||||
|
assertion,
|
||||||
|
attributes,
|
||||||
|
saml2AuthorityAttributeLookup.getIdentityMappings(registrationId));
|
||||||
|
final Collection<? extends GrantedAuthority> assertionAuthorities =
|
||||||
|
getAssertionAuthorities(
|
||||||
|
attributes,
|
||||||
|
saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId));
|
||||||
|
return new Saml2Authentication(
|
||||||
|
principal, responseToken.getToken().getSaml2Response(), assertionAuthorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<? extends GrantedAuthority> getAssertionAuthorities(
|
||||||
|
final Map<String, List<Object>> attributes, final String authoritiesAttributeName) {
|
||||||
|
if (attributes == null || attributes.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Object> groups = new ArrayList<>(attributes.get(authoritiesAttributeName));
|
||||||
|
return groups.stream()
|
||||||
|
.filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.DisabledException;
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
AuthenticationException exception)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
if (exception instanceof BadCredentialsException) {
|
||||||
|
log.error("BadCredentialsException", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof DisabledException) {
|
||||||
|
log.error("User is deactivated: ", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof LockedException) {
|
||||||
|
log.error("Account locked: ", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof Saml2AuthenticationException) {
|
||||||
|
log.error("SAML2 Authentication error: ", exception);
|
||||||
|
getRedirectStrategy()
|
||||||
|
.sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomSAMLAuthenticationSuccessHandler
|
||||||
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
private LoginAttemptService loginAttemptService;
|
||||||
|
private UserService userService;
|
||||||
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
LoginAttemptService loginAttemptService,
|
||||||
|
UserService userService,
|
||||||
|
ApplicationProperties applicationProperties) {
|
||||||
|
this.loginAttemptService = loginAttemptService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
String username = "";
|
||||||
|
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
OAuth2User oauthUser = (OAuth2User) principal;
|
||||||
|
username = oauthUser.getName();
|
||||||
|
} else if (principal instanceof UserDetails) {
|
||||||
|
UserDetails oauthUser = (UserDetails) principal;
|
||||||
|
username = oauthUser.getUsername();
|
||||||
|
} else if (principal instanceof ScimSaml2AuthenticatedPrincipal) {
|
||||||
|
ScimSaml2AuthenticatedPrincipal samlPrincipal =
|
||||||
|
(ScimSaml2AuthenticatedPrincipal) principal;
|
||||||
|
username = samlPrincipal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the saved request
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
String contextPath = request.getContextPath();
|
||||||
|
SavedRequest savedRequest =
|
||||||
|
(session != null)
|
||||||
|
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (savedRequest != null
|
||||||
|
&& !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {
|
||||||
|
// Redirect to the original destination
|
||||||
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
|
} else {
|
||||||
|
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
|
if (session != null) {
|
||||||
|
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
||||||
|
}
|
||||||
|
throw new LockedException(
|
||||||
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
|
}
|
||||||
|
if (userService.usernameExistsIgnoreCase(username)
|
||||||
|
&& userService.hasPassword(username)
|
||||||
|
&& !userService.isAuthenticationTypeByUsername(
|
||||||
|
username, AuthenticationType.OAUTH2)
|
||||||
|
&& oAuth.getAutoCreateUser()) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (oAuth.getBlockRegistration()
|
||||||
|
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
||||||
|
}
|
||||||
|
response.sendRedirect(contextPath + "/");
|
||||||
|
return;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
String redirectUrl = determineTargetUrl(request, response, authentication);
|
||||||
|
|
||||||
|
if (response.isCommitted()) {
|
||||||
|
log.debug("Response has already been committed. Unable to redirect to " + redirectUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String determineTargetUrl(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Authentication authentication) {
|
||||||
|
// Default to the root URL
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
public interface Saml2AuthorityAttributeLookup {
|
||||||
|
String getAuthorityAttribute(String registrationId);
|
||||||
|
|
||||||
|
SimpleScimMappings getIdentityMappings(String registrationId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthorityAttribute(String registrationId) {
|
||||||
|
return "authorityAttributeName";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleScimMappings getIdentityMappings(String registrationId) {
|
||||||
|
return new SimpleScimMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.opensaml.core.xml.XMLObject;
|
||||||
|
import org.opensaml.core.xml.schema.*;
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
|
||||||
|
public class SamlAssertionUtils {
|
||||||
|
|
||||||
|
public static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
|
||||||
|
Map<String, List<Object>> attributeMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
assertion
|
||||||
|
.getAttributeStatements()
|
||||||
|
.forEach(
|
||||||
|
attributeStatement -> {
|
||||||
|
attributeStatement
|
||||||
|
.getAttributes()
|
||||||
|
.forEach(
|
||||||
|
attribute -> {
|
||||||
|
List<Object> attributeValues = new ArrayList<>();
|
||||||
|
|
||||||
|
attribute
|
||||||
|
.getAttributeValues()
|
||||||
|
.forEach(
|
||||||
|
xmlObject -> {
|
||||||
|
Object attributeValue =
|
||||||
|
getXmlObjectValue(
|
||||||
|
xmlObject);
|
||||||
|
if (attributeValue != null) {
|
||||||
|
attributeValues.add(
|
||||||
|
attributeValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attributeMap.put(
|
||||||
|
attribute.getName(), attributeValues);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return attributeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getXmlObjectValue(XMLObject xmlObject) {
|
||||||
|
if (xmlObject instanceof XSAny) {
|
||||||
|
return ((XSAny) xmlObject).getTextContent();
|
||||||
|
} else if (xmlObject instanceof XSString) {
|
||||||
|
return ((XSString) xmlObject).getValue();
|
||||||
|
} else if (xmlObject instanceof XSInteger) {
|
||||||
|
return ((XSInteger) xmlObject).getValue();
|
||||||
|
} else if (xmlObject instanceof XSURI) {
|
||||||
|
return ((XSURI) xmlObject).getURI();
|
||||||
|
} else if (xmlObject instanceof XSBoolean) {
|
||||||
|
return ((XSBoolean) xmlObject).getValue().getValue();
|
||||||
|
} else if (xmlObject instanceof XSDateTime) {
|
||||||
|
Instant dateTime = ((XSDateTime) xmlObject).getValue();
|
||||||
|
return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class SamlConfig {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository()
|
||||||
|
throws CertificateException {
|
||||||
|
RelyingPartyRegistration registration =
|
||||||
|
RelyingPartyRegistrations.fromMetadataLocation(
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSaml()
|
||||||
|
.getIdpMetadataLocation())
|
||||||
|
.entityId(applicationProperties.getSecurity().getSaml().getEntityId())
|
||||||
|
.registrationId(
|
||||||
|
applicationProperties.getSecurity().getSaml().getRegistrationId())
|
||||||
|
.build();
|
||||||
|
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import com.unboundid.scim2.common.types.Email;
|
||||||
|
import com.unboundid.scim2.common.types.Name;
|
||||||
|
import com.unboundid.scim2.common.types.UserResource;
|
||||||
|
|
||||||
|
public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final transient UserResource userResource;
|
||||||
|
|
||||||
|
public ScimSaml2AuthenticatedPrincipal(
|
||||||
|
final Assertion assertion,
|
||||||
|
final Map<String, List<Object>> attributes,
|
||||||
|
final SimpleScimMappings attributeMappings) {
|
||||||
|
Assert.notNull(assertion, "assertion cannot be null");
|
||||||
|
Assert.notNull(assertion.getSubject(), "assertion subject cannot be null");
|
||||||
|
Assert.notNull(
|
||||||
|
assertion.getSubject().getNameID(), "assertion subject NameID cannot be null");
|
||||||
|
Assert.notNull(attributes, "attributes cannot be null");
|
||||||
|
Assert.notNull(attributeMappings, "attributeMappings cannot be null");
|
||||||
|
|
||||||
|
final Name name =
|
||||||
|
new Name()
|
||||||
|
.setFamilyName(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getFamilyName))
|
||||||
|
.setGivenName(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getGivenName));
|
||||||
|
|
||||||
|
final List<Email> emails = new ArrayList<>(1);
|
||||||
|
emails.add(
|
||||||
|
new Email()
|
||||||
|
.setValue(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getEmail))
|
||||||
|
.setPrimary(true));
|
||||||
|
|
||||||
|
userResource =
|
||||||
|
new UserResource()
|
||||||
|
.setUserName(assertion.getSubject().getNameID().getValue())
|
||||||
|
.setName(name)
|
||||||
|
.setEmails(emails);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAttribute(
|
||||||
|
final Map<String, List<Object>> attributes,
|
||||||
|
final SimpleScimMappings simpleScimMappings,
|
||||||
|
final Function<SimpleScimMappings, String> attributeMapper) {
|
||||||
|
|
||||||
|
final String key = attributeMapper.apply(simpleScimMappings);
|
||||||
|
|
||||||
|
final List<Object> values = attributes.getOrDefault(key, Collections.emptyList());
|
||||||
|
|
||||||
|
return values.stream()
|
||||||
|
.filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.userResource.getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserResource getUserResource() {
|
||||||
|
return this.userResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SimpleScimMappings {
|
||||||
|
String givenName;
|
||||||
|
String familyName;
|
||||||
|
String email;
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
|
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
|
|
||||||
public class CertificateUtils {
|
|
||||||
|
|
||||||
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
|
||||||
try (PemReader pemReader =
|
|
||||||
new PemReader(
|
|
||||||
new InputStreamReader(
|
|
||||||
certificateResource.getInputStream(), StandardCharsets.UTF_8))) {
|
|
||||||
PemObject pemObject = pemReader.readPemObject();
|
|
||||||
byte[] decodedCert = pemObject.getContent();
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
|
|
||||||
try (PemReader pemReader =
|
|
||||||
new PemReader(
|
|
||||||
new InputStreamReader(
|
|
||||||
privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
|
|
||||||
PemObject pemObject = pemReader.readPemObject();
|
|
||||||
byte[] decodedKey = pemObject.getContent();
|
|
||||||
return (RSAPrivateKey)
|
|
||||||
KeyFactory.getInstance("RSA")
|
|
||||||
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
|
||||||
|
|
||||||
public class CustomSaml2AuthenticatedPrincipal
|
|
||||||
implements Saml2AuthenticatedPrincipal, Serializable {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Map<String, List<Object>> attributes;
|
|
||||||
private final String nameId;
|
|
||||||
private final List<String> sessionIndexes;
|
|
||||||
|
|
||||||
public CustomSaml2AuthenticatedPrincipal(
|
|
||||||
String name,
|
|
||||||
Map<String, List<Object>> attributes,
|
|
||||||
String nameId,
|
|
||||||
List<String> sessionIndexes) {
|
|
||||||
this.name = name;
|
|
||||||
this.attributes = attributes;
|
|
||||||
this.nameId = nameId;
|
|
||||||
this.sessionIndexes = sessionIndexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<Object>> getAttributes() {
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNameId() {
|
|
||||||
return this.nameId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getSessionIndexes() {
|
|
||||||
return this.sessionIndexes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.ProviderNotFoundException;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.saml2.core.Saml2Error;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailure(
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
AuthenticationException exception)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
if (exception instanceof Saml2AuthenticationException) {
|
|
||||||
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
|
||||||
getRedirectStrategy()
|
|
||||||
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
|
|
||||||
} else if (exception instanceof ProviderNotFoundException) {
|
|
||||||
getRedirectStrategy()
|
|
||||||
.sendRedirect(
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
"/login?erroroauth=not_authentication_provider_found");
|
|
||||||
}
|
|
||||||
log.error("AuthenticationException: " + exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
|
||||||
import org.springframework.security.web.savedrequest.SavedRequest;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class CustomSaml2AuthenticationSuccessHandler
|
|
||||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
private LoginAttemptService loginAttemptService;
|
|
||||||
|
|
||||||
private ApplicationProperties applicationProperties;
|
|
||||||
private UserService userService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
|
|
||||||
Object principal = authentication.getPrincipal();
|
|
||||||
|
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
// Get the saved request
|
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
SavedRequest savedRequest =
|
|
||||||
(session != null)
|
|
||||||
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (savedRequest != null
|
|
||||||
&& !RequestUriUtils.isStaticResource(
|
|
||||||
contextPath, savedRequest.getRedirectUrl())) {
|
|
||||||
// Redirect to the original destination
|
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
} else {
|
|
||||||
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
|
|
||||||
|
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
|
||||||
if (session != null) {
|
|
||||||
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
|
||||||
}
|
|
||||||
throw new LockedException(
|
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
|
||||||
}
|
|
||||||
if (userService.usernameExistsIgnoreCase(username)
|
|
||||||
&& userService.hasPassword(username)
|
|
||||||
&& !userService.isAuthenticationTypeByUsername(
|
|
||||||
username, AuthenticationType.OAUTH2)
|
|
||||||
&& saml2.getAutoCreateUser()) {
|
|
||||||
response.sendRedirect(
|
|
||||||
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (saml2.getBlockRegistration()
|
|
||||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
|
||||||
response.sendRedirect(
|
|
||||||
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser());
|
|
||||||
response.sendRedirect(contextPath + "/");
|
|
||||||
return;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import org.opensaml.core.xml.XMLObject;
|
|
||||||
import org.opensaml.core.xml.schema.XSBoolean;
|
|
||||||
import org.opensaml.core.xml.schema.XSString;
|
|
||||||
import org.opensaml.saml.saml2.core.Assertion;
|
|
||||||
import org.opensaml.saml.saml2.core.Attribute;
|
|
||||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
|
||||||
import org.opensaml.saml.saml2.core.AuthnStatement;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class CustomSaml2ResponseAuthenticationConverter
|
|
||||||
implements Converter<ResponseToken, Saml2Authentication> {
|
|
||||||
|
|
||||||
private UserService userService;
|
|
||||||
|
|
||||||
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
|
|
||||||
this.userService = userService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Saml2Authentication convert(ResponseToken responseToken) {
|
|
||||||
// Extract the assertion from the response
|
|
||||||
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
|
|
||||||
|
|
||||||
// Extract the NameID
|
|
||||||
String nameId = assertion.getSubject().getNameID().getValue();
|
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(nameId);
|
|
||||||
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
simpleGrantedAuthority =
|
|
||||||
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the SessionIndexes
|
|
||||||
List<String> sessionIndexes = new ArrayList<>();
|
|
||||||
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
|
|
||||||
sessionIndexes.add(authnStatement.getSessionIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the Attributes
|
|
||||||
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
|
||||||
|
|
||||||
// Create the custom principal
|
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
|
||||||
new CustomSaml2AuthenticatedPrincipal(nameId, attributes, nameId, sessionIndexes);
|
|
||||||
|
|
||||||
// Create the Saml2Authentication
|
|
||||||
return new Saml2Authentication(
|
|
||||||
principal,
|
|
||||||
responseToken.getToken().getSaml2Response(),
|
|
||||||
Collections.singletonList(simpleGrantedAuthority));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
|
|
||||||
Map<String, List<Object>> attributes = new HashMap<>();
|
|
||||||
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
|
|
||||||
for (Attribute attribute : attributeStatement.getAttributes()) {
|
|
||||||
String attributeName = attribute.getName();
|
|
||||||
List<Object> values = new ArrayList<>();
|
|
||||||
for (XMLObject xmlObject : attribute.getAttributeValues()) {
|
|
||||||
log.info("BOOL: " + ((XSBoolean) xmlObject).getValue());
|
|
||||||
values.add(((XSString) xmlObject).getValue());
|
|
||||||
}
|
|
||||||
attributes.put(attributeName, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.model.SessionEntity;
|
import stirling.software.SPDF.model.SessionEntity;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -51,8 +50,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
principalName = ((UserDetails) principal).getUsername();
|
principalName = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
@@ -82,8 +79,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
principalName = ((UserDetails) principal).getUsername();
|
principalName = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import stirling.software.SPDF.service.LanguageService;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/js")
|
|
||||||
public class AdditionalLanguageJsController {
|
|
||||||
|
|
||||||
@Autowired private LanguageService languageService;
|
|
||||||
|
|
||||||
@Hidden
|
|
||||||
@GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript")
|
|
||||||
public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException {
|
|
||||||
List<String> supportedLanguages = languageService.getSupportedLanguages();
|
|
||||||
|
|
||||||
response.setContentType("application/javascript");
|
|
||||||
PrintWriter writer = response.getWriter();
|
|
||||||
|
|
||||||
// Erstelle das JavaScript dynamisch
|
|
||||||
writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";");
|
|
||||||
|
|
||||||
// Generiere die `getDetailedLanguageCode`-Funktion
|
|
||||||
writer.println(
|
|
||||||
"""
|
|
||||||
function getDetailedLanguageCode() {
|
|
||||||
const userLanguages = navigator.languages ? navigator.languages : [navigator.language];
|
|
||||||
for (let lang of userLanguages) {
|
|
||||||
let matchedLang = supportedLanguages.find(supportedLang => supportedLang.startsWith(lang.replace('-', '_')));
|
|
||||||
if (matchedLang) {
|
|
||||||
return matchedLang;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fallback
|
|
||||||
return "en_GB";
|
|
||||||
}
|
|
||||||
""");
|
|
||||||
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hilfsfunktion zum Konvertieren der Liste in ein JSON-Array
|
|
||||||
private String toJsonArray(List<String> list) {
|
|
||||||
StringBuilder jsonArray = new StringBuilder("[");
|
|
||||||
for (int i = 0; i < list.size(); i++) {
|
|
||||||
jsonArray.append("\"").append(list.get(i)).append("\"");
|
|
||||||
if (i < list.size() - 1) {
|
|
||||||
jsonArray.append(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsonArray.append("]");
|
|
||||||
return jsonArray.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
@@ -22,7 +21,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
|
||||||
public class PdfImageRemovalController {
|
public class PdfImageRemovalController {
|
||||||
|
|
||||||
// Service for removing images from PDFs
|
// Service for removing images from PDFs
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class ScalePagesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
||||||
if ("KEEP".equals(targetPDRectangle)) {
|
if (targetPDRectangle.equals("KEEP")) {
|
||||||
if (sourceDocument.getNumberOfPages() == 0) {
|
if (sourceDocument.getNumberOfPages() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ public class SettingsController {
|
|||||||
}
|
}
|
||||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||||
|
|
||||||
return ResponseEntity.ok("Updated");
|
return ResponseEntity.ok("Updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
@@ -337,8 +336,6 @@ public class UserController {
|
|||||||
userNameP = ((UserDetails) principal).getUsername();
|
userNameP = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
userNameP = ((OAuth2User) principal).getName();
|
userNameP = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
userNameP = (String) principal;
|
userNameP = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class ConvertImgPDFController {
|
|||||||
result =
|
result =
|
||||||
PdfUtils.convertFromPdf(
|
PdfUtils.convertFromPdf(
|
||||||
pdfBytes,
|
pdfBytes,
|
||||||
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
|
imageFormat.equalsIgnoreCase("webp") ? "png" : imageFormat.toUpperCase(),
|
||||||
colorTypeResult,
|
colorTypeResult,
|
||||||
singleImage,
|
singleImage,
|
||||||
Integer.valueOf(dpi),
|
Integer.valueOf(dpi),
|
||||||
@@ -90,9 +90,9 @@ public class ConvertImgPDFController {
|
|||||||
if (result == null || result.length == 0) {
|
if (result == null || result.length == 0) {
|
||||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||||
}
|
}
|
||||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
if (imageFormat.equalsIgnoreCase("webp") && !CheckProgramInstall.isPythonAvailable()) {
|
||||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||||
} else if ("webp".equalsIgnoreCase(imageFormat)
|
} else if (imageFormat.equalsIgnoreCase("webp")
|
||||||
&& CheckProgramInstall.isPythonAvailable()) {
|
&& CheckProgramInstall.isPythonAvailable()) {
|
||||||
// Write the output stream to a temp file
|
// Write the output stream to a temp file
|
||||||
Path tempFile = Files.createTempFile("temp_png", ".png");
|
Path tempFile = Files.createTempFile("temp_png", ".png");
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
|
||||||
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
|
||||||
public class ReplaceAndInvertColorController {
|
public class ReplaceAndInvertColorController {
|
||||||
|
|
||||||
private ReplaceAndInvertColorService replaceAndInvertColorService;
|
private ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||||
|
|||||||
@@ -4,6 +4,4 @@ public interface UserServiceInterface {
|
|||||||
String getApiKeyForUser(String username);
|
String getApiKeyForUser(String username);
|
||||||
|
|
||||||
String getCurrentUsername();
|
String getCurrentUsername();
|
||||||
|
|
||||||
long getTotalUsersCount();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@@ -18,39 +14,12 @@ import java.security.UnrecoverableKeyException;
|
|||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
|
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDStream;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName;
|
|
||||||
import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode;
|
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
|
||||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
|
||||||
import org.apache.pdfbox.util.Matrix;
|
|
||||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
import org.bouncycastle.asn1.x500.RDN;
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
|
||||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.openssl.PEMDecryptorProvider;
|
import org.bouncycastle.openssl.PEMDecryptorProvider;
|
||||||
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
|
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
|
||||||
@@ -66,7 +35,6 @@ import org.bouncycastle.pkcs.PKCSException;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -94,8 +62,6 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CreateSignature extends CreateSignatureBase {
|
class CreateSignature extends CreateSignatureBase {
|
||||||
File logoFile;
|
|
||||||
|
|
||||||
public CreateSignature(KeyStore keystore, char[] pin)
|
public CreateSignature(KeyStore keystore, char[] pin)
|
||||||
throws KeyStoreException,
|
throws KeyStoreException,
|
||||||
UnrecoverableKeyException,
|
UnrecoverableKeyException,
|
||||||
@@ -103,101 +69,6 @@ public class CertSignController {
|
|||||||
IOException,
|
IOException,
|
||||||
CertificateException {
|
CertificateException {
|
||||||
super(keystore, pin);
|
super(keystore, pin);
|
||||||
ClassPathResource resource = new ClassPathResource("static/images/signature.png");
|
|
||||||
try (InputStream is = resource.getInputStream()) {
|
|
||||||
logoFile = Files.createTempFile("signature", ".png").toFile();
|
|
||||||
FileUtils.copyInputStreamToFile(is, logoFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Failed to load image signature file");
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputStream createVisibleSignature(
|
|
||||||
PDDocument srcDoc, PDSignature signature, Integer pageNumber, Boolean showLogo)
|
|
||||||
throws IOException {
|
|
||||||
// modified from org.apache.pdfbox.examples.signature.CreateVisibleSignature2
|
|
||||||
try (PDDocument doc = new PDDocument()) {
|
|
||||||
PDPage page = new PDPage(srcDoc.getPage(pageNumber).getMediaBox());
|
|
||||||
doc.addPage(page);
|
|
||||||
PDAcroForm acroForm = new PDAcroForm(doc);
|
|
||||||
doc.getDocumentCatalog().setAcroForm(acroForm);
|
|
||||||
PDSignatureField signatureField = new PDSignatureField(acroForm);
|
|
||||||
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
|
|
||||||
List<PDField> acroFormFields = acroForm.getFields();
|
|
||||||
acroForm.setSignaturesExist(true);
|
|
||||||
acroForm.setAppendOnly(true);
|
|
||||||
acroForm.getCOSObject().setDirect(true);
|
|
||||||
acroFormFields.add(signatureField);
|
|
||||||
|
|
||||||
PDRectangle rect = new PDRectangle(0, 0, 200, 50);
|
|
||||||
|
|
||||||
widget.setRectangle(rect);
|
|
||||||
|
|
||||||
// from PDVisualSigBuilder.createHolderForm()
|
|
||||||
PDStream stream = new PDStream(doc);
|
|
||||||
PDFormXObject form = new PDFormXObject(stream);
|
|
||||||
PDResources res = new PDResources();
|
|
||||||
form.setResources(res);
|
|
||||||
form.setFormType(1);
|
|
||||||
PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight());
|
|
||||||
float height = bbox.getHeight();
|
|
||||||
form.setBBox(bbox);
|
|
||||||
PDFont font = new PDType1Font(FontName.TIMES_BOLD);
|
|
||||||
|
|
||||||
// from PDVisualSigBuilder.createAppearanceDictionary()
|
|
||||||
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
|
|
||||||
appearance.getCOSObject().setDirect(true);
|
|
||||||
PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
|
|
||||||
appearance.setNormalAppearance(appearanceStream);
|
|
||||||
widget.setAppearance(appearance);
|
|
||||||
|
|
||||||
try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) {
|
|
||||||
if (showLogo) {
|
|
||||||
cs.saveGraphicsState();
|
|
||||||
PDExtendedGraphicsState extState = new PDExtendedGraphicsState();
|
|
||||||
extState.setBlendMode(BlendMode.MULTIPLY);
|
|
||||||
extState.setNonStrokingAlphaConstant(0.5f);
|
|
||||||
cs.setGraphicsStateParameters(extState);
|
|
||||||
cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));
|
|
||||||
PDImageXObject img =
|
|
||||||
PDImageXObject.createFromFileByExtension(logoFile, doc);
|
|
||||||
cs.drawImage(img, 100, 0);
|
|
||||||
cs.restoreGraphicsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// show text
|
|
||||||
float fontSize = 10;
|
|
||||||
float leading = fontSize * 1.5f;
|
|
||||||
cs.beginText();
|
|
||||||
cs.setFont(font, fontSize);
|
|
||||||
cs.setNonStrokingColor(Color.black);
|
|
||||||
cs.newLineAtOffset(fontSize, height - leading);
|
|
||||||
cs.setLeading(leading);
|
|
||||||
|
|
||||||
X509Certificate cert = (X509Certificate) getCertificateChain()[0];
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/2914521/
|
|
||||||
X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName());
|
|
||||||
RDN cn = x500Name.getRDNs(BCStyle.CN)[0];
|
|
||||||
String name = IETFUtils.valueToString(cn.getFirst().getValue());
|
|
||||||
|
|
||||||
String date = signature.getSignDate().getTime().toString();
|
|
||||||
String reason = signature.getReason();
|
|
||||||
|
|
||||||
cs.showText("Signed by " + name);
|
|
||||||
cs.newLine();
|
|
||||||
cs.showText(date);
|
|
||||||
cs.newLine();
|
|
||||||
cs.showText(reason);
|
|
||||||
|
|
||||||
cs.endText();
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
doc.save(baos);
|
|
||||||
return new ByteArrayInputStream(baos.toByteArray());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +97,7 @@ public class CertSignController {
|
|||||||
String reason = request.getReason();
|
String reason = request.getReason();
|
||||||
String location = request.getLocation();
|
String location = request.getLocation();
|
||||||
String name = request.getName();
|
String name = request.getName();
|
||||||
Integer pageNumber = request.getPageNumber() - 1;
|
Integer pageNumber = request.getPageNumber();
|
||||||
Boolean showLogo = request.isShowLogo();
|
|
||||||
|
|
||||||
if (certType == null) {
|
if (certType == null) {
|
||||||
throw new IllegalArgumentException("Cert type must be provided");
|
throw new IllegalArgumentException("Cert type must be provided");
|
||||||
@@ -256,19 +126,11 @@ public class CertSignController {
|
|||||||
throw new IllegalArgumentException("Invalid cert type: " + certType);
|
throw new IllegalArgumentException("Invalid cert type: " + certType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: page number
|
||||||
|
|
||||||
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
sign(
|
sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason);
|
||||||
pdfDocumentFactory,
|
|
||||||
pdf.getBytes(),
|
|
||||||
baos,
|
|
||||||
createSignature,
|
|
||||||
showSignature,
|
|
||||||
pageNumber,
|
|
||||||
name,
|
|
||||||
location,
|
|
||||||
reason,
|
|
||||||
showLogo);
|
|
||||||
return WebResponseUtils.boasToWebResponse(
|
return WebResponseUtils.boasToWebResponse(
|
||||||
baos,
|
baos,
|
||||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||||
@@ -280,12 +142,9 @@ public class CertSignController {
|
|||||||
byte[] input,
|
byte[] input,
|
||||||
OutputStream output,
|
OutputStream output,
|
||||||
CreateSignature instance,
|
CreateSignature instance,
|
||||||
Boolean showSignature,
|
|
||||||
Integer pageNumber,
|
|
||||||
String name,
|
String name,
|
||||||
String location,
|
String location,
|
||||||
String reason,
|
String reason) {
|
||||||
Boolean showLogo) {
|
|
||||||
try (PDDocument doc = pdfDocumentFactory.load(input)) {
|
try (PDDocument doc = pdfDocumentFactory.load(input)) {
|
||||||
PDSignature signature = new PDSignature();
|
PDSignature signature = new PDSignature();
|
||||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||||
@@ -295,17 +154,7 @@ public class CertSignController {
|
|||||||
signature.setReason(reason);
|
signature.setReason(reason);
|
||||||
signature.setSignDate(Calendar.getInstance());
|
signature.setSignDate(Calendar.getInstance());
|
||||||
|
|
||||||
if (showSignature) {
|
doc.addSignature(signature, instance);
|
||||||
SignatureOptions signatureOptions = new SignatureOptions();
|
|
||||||
signatureOptions.setVisualSignature(
|
|
||||||
instance.createVisibleSignature(doc, signature, pageNumber, showLogo));
|
|
||||||
signatureOptions.setPage(pageNumber);
|
|
||||||
|
|
||||||
doc.addSignature(signature, instance, signatureOptions);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
doc.addSignature(signature, instance);
|
|
||||||
}
|
|
||||||
doc.saveIncremental(output);
|
doc.saveIncremental(output);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("exception", e);
|
logger.error("exception", e);
|
||||||
|
|||||||
@@ -187,31 +187,18 @@ public class WatermarkController {
|
|||||||
float watermarkHeight = heightSpacer + fontSize * textLines.length;
|
float watermarkHeight = heightSpacer + fontSize * textLines.length;
|
||||||
float pageWidth = page.getMediaBox().getWidth();
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
float pageHeight = page.getMediaBox().getHeight();
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
|
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
||||||
// Calculating the new width and height depending on the angle.
|
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
||||||
float radians = (float) Math.toRadians(rotation);
|
|
||||||
float newWatermarkWidth =
|
|
||||||
(float)
|
|
||||||
(Math.abs(watermarkWidth * Math.cos(radians))
|
|
||||||
+ Math.abs(watermarkHeight * Math.sin(radians)));
|
|
||||||
float newWatermarkHeight =
|
|
||||||
(float)
|
|
||||||
(Math.abs(watermarkWidth * Math.sin(radians))
|
|
||||||
+ Math.abs(watermarkHeight * Math.cos(radians)));
|
|
||||||
|
|
||||||
// Calculating the number of rows and columns.
|
|
||||||
int watermarkRows = (int) (pageHeight / newWatermarkHeight + 1);
|
|
||||||
int watermarkCols = (int) (pageWidth / newWatermarkWidth + 1);
|
|
||||||
|
|
||||||
// Add the text watermark
|
// Add the text watermark
|
||||||
for (int i = 0; i <= watermarkRows; i++) {
|
for (int i = 0; i < watermarkRows; i++) {
|
||||||
for (int j = 0; j <= watermarkCols; j++) {
|
for (int j = 0; j < watermarkCols; j++) {
|
||||||
contentStream.beginText();
|
contentStream.beginText();
|
||||||
contentStream.setTextMatrix(
|
contentStream.setTextMatrix(
|
||||||
Matrix.getRotateInstance(
|
Matrix.getRotateInstance(
|
||||||
(float) Math.toRadians(rotation),
|
(float) Math.toRadians(rotation),
|
||||||
j * newWatermarkWidth,
|
j * watermarkWidth,
|
||||||
i * newWatermarkHeight));
|
i * watermarkHeight));
|
||||||
|
|
||||||
for (int k = 0; k < textLines.length; ++k) {
|
for (int k = 0; k < textLines.length; ++k) {
|
||||||
contentStream.showText(textLines[k]);
|
contentStream.showText(textLines[k]);
|
||||||
|
|||||||
@@ -21,13 +21,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.*;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
@@ -54,54 +51,38 @@ public class AccountWebController {
|
|||||||
|
|
||||||
Map<String, String> providerList = new HashMap<>();
|
Map<String, String> providerList = new HashMap<>();
|
||||||
|
|
||||||
Security securityProps = applicationProperties.getSecurity();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
OAUTH2 oauth = securityProps.getOauth2();
|
|
||||||
if (oauth != null) {
|
if (oauth != null) {
|
||||||
if (oauth.getEnabled()) {
|
if (oauth.isSettingsValid()) {
|
||||||
if (oauth.isSettingsValid()) {
|
providerList.put("oidc", oauth.getProvider());
|
||||||
providerList.put("/oauth2/authorization/oidc", oauth.getProvider());
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
if (google.isSettingsValid()) {
|
||||||
|
providerList.put(google.getName(), google.getClientName());
|
||||||
}
|
}
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client != null) {
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
if (google.isSettingsValid()) {
|
|
||||||
providerList.put(
|
|
||||||
"/oauth2/authorization/" + google.getName(),
|
|
||||||
google.getClientName());
|
|
||||||
}
|
|
||||||
|
|
||||||
GithubProvider github = client.getGithub();
|
GithubProvider github = client.getGithub();
|
||||||
if (github.isSettingsValid()) {
|
if (github.isSettingsValid()) {
|
||||||
providerList.put(
|
providerList.put(github.getName(), github.getClientName());
|
||||||
"/oauth2/authorization/" + github.getName(),
|
}
|
||||||
github.getClientName());
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
if (keycloak.isSettingsValid()) {
|
if (keycloak.isSettingsValid()) {
|
||||||
providerList.put(
|
providerList.put(keycloak.getName(), keycloak.getClientName());
|
||||||
"/oauth2/authorization/" + keycloak.getName(),
|
|
||||||
keycloak.getClientName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SAML2 saml2 = securityProps.getSaml2();
|
|
||||||
if (securityProps.isSaml2Activ()
|
|
||||||
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
|
||||||
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
|
|
||||||
}
|
|
||||||
// Remove any null keys/values from the providerList
|
// Remove any null keys/values from the providerList
|
||||||
providerList
|
providerList
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
||||||
model.addAttribute("providerlist", providerList);
|
model.addAttribute("providerlist", providerList);
|
||||||
|
|
||||||
model.addAttribute("loginMethod", securityProps.getLoginMethod());
|
model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod());
|
||||||
boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false;
|
model.addAttribute(
|
||||||
model.addAttribute("altLogin", altLogin);
|
"oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled());
|
||||||
|
|
||||||
model.addAttribute("currentPage", "login");
|
model.addAttribute("currentPage", "login");
|
||||||
|
|
||||||
@@ -164,17 +145,6 @@ public class AccountWebController {
|
|||||||
case "userIsDisabled":
|
case "userIsDisabled":
|
||||||
erroroauth = "login.userIsDisabled";
|
erroroauth = "login.userIsDisabled";
|
||||||
break;
|
break;
|
||||||
case "invalid_destination":
|
|
||||||
erroroauth = "login.invalid_destination";
|
|
||||||
break;
|
|
||||||
// Valid InResponseTo was not available from the validation context, unable to
|
|
||||||
// evaluate
|
|
||||||
case "invalid_in_response_to":
|
|
||||||
erroroauth = "login.invalid_in_response_to";
|
|
||||||
break;
|
|
||||||
case "not_authentication_provider_found":
|
|
||||||
erroroauth = "login.not_authentication_provider_found";
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -379,17 +349,6 @@ public class AccountWebController {
|
|||||||
// Add oAuth2 Login attributes to the model
|
// Add oAuth2 Login attributes to the model
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
// Cast the principal object to OAuth2User
|
|
||||||
CustomSaml2AuthenticatedPrincipal userDetails =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
|
||||||
username = userDetails.getName();
|
|
||||||
// Add oAuth2 Login attributes to the model
|
|
||||||
model.addAttribute("oAuth2Login", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user =
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
|
||||||
import stirling.software.SPDF.model.SignatureFile;
|
|
||||||
import stirling.software.SPDF.service.SignatureService;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class GeneralWebController {
|
public class GeneralWebController {
|
||||||
@@ -175,28 +171,11 @@ public class GeneralWebController {
|
|||||||
return "split-pdfs";
|
return "split-pdfs";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
|
|
||||||
private static final String ALL_USERS_FOLDER = "ALL_USERS";
|
|
||||||
|
|
||||||
@Autowired private SignatureService signatureService;
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private UserServiceInterface userService;
|
|
||||||
|
|
||||||
@GetMapping("/sign")
|
@GetMapping("/sign")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String signForm(Model model) {
|
public String signForm(Model model) {
|
||||||
String username = "";
|
|
||||||
if (userService != null) {
|
|
||||||
username = userService.getCurrentUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get signatures from both personal and ALL_USERS folders
|
|
||||||
List<SignatureFile> signatures = signatureService.getAvailableSignatures(username);
|
|
||||||
|
|
||||||
model.addAttribute("currentPage", "sign");
|
model.addAttribute("currentPage", "sign");
|
||||||
model.addAttribute("fonts", getFontNames());
|
model.addAttribute("fonts", getFontNames());
|
||||||
model.addAttribute("signatures", signatures);
|
|
||||||
return "sign";
|
return "sign";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
|
||||||
import stirling.software.SPDF.service.SignatureService;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/api/v1/general")
|
|
||||||
public class SignatureController {
|
|
||||||
|
|
||||||
@Autowired private SignatureService signatureService;
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private UserServiceInterface userService;
|
|
||||||
|
|
||||||
@GetMapping("/sign/{fileName}")
|
|
||||||
public ResponseEntity<byte[]> getSignature(@PathVariable(name = "fileName") String fileName)
|
|
||||||
throws IOException {
|
|
||||||
String username = "NON_SECURITY_USER";
|
|
||||||
if (userService != null) {
|
|
||||||
username = userService.getCurrentUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify access permission
|
|
||||||
if (!signatureService.hasAccessToFile(username, fileName)) {
|
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] imageBytes = signatureService.getSignatureBytes(username, fileName);
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(MediaType.IMAGE_JPEG) // Adjust based on file type
|
|
||||||
.body(imageBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
@@ -22,8 +18,6 @@ import org.springframework.core.io.FileSystemResource;
|
|||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
@@ -47,7 +41,7 @@ public class ApplicationProperties {
|
|||||||
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
||||||
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
||||||
private AutoPipeline autoPipeline = new AutoPipeline();
|
private AutoPipeline autoPipeline = new AutoPipeline();
|
||||||
private ProcessExecutor processExecutor = new ProcessExecutor();
|
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class AutoPipeline {
|
public static class AutoPipeline {
|
||||||
@@ -69,108 +63,41 @@ public class ApplicationProperties {
|
|||||||
private Boolean csrfDisabled;
|
private Boolean csrfDisabled;
|
||||||
private InitialLogin initialLogin = new InitialLogin();
|
private InitialLogin initialLogin = new InitialLogin();
|
||||||
private OAUTH2 oauth2 = new OAUTH2();
|
private OAUTH2 oauth2 = new OAUTH2();
|
||||||
private SAML2 saml2 = new SAML2();
|
private SAML saml = new SAML();
|
||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
|
|
||||||
public Boolean isAltLogin() {
|
|
||||||
return saml2.getEnabled() || oauth2.getEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LoginMethods {
|
|
||||||
ALL("all"),
|
|
||||||
NORMAL("normal"),
|
|
||||||
OAUTH2("oauth2"),
|
|
||||||
SAML2("saml2");
|
|
||||||
|
|
||||||
private String method;
|
|
||||||
|
|
||||||
LoginMethods(String method) {
|
|
||||||
this.method = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUserPass() {
|
|
||||||
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|
|
||||||
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOauth2Activ() {
|
|
||||||
return (oauth2 != null
|
|
||||||
&& oauth2.getEnabled()
|
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSaml2Activ() {
|
|
||||||
return (saml2 != null
|
|
||||||
&& saml2.getEnabled()
|
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class InitialLogin {
|
public static class InitialLogin {
|
||||||
private String username;
|
private String username;
|
||||||
@ToString.Exclude private String password;
|
@ToString.Exclude private String password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Data
|
||||||
@Setter
|
public static class SAML {
|
||||||
public static class SAML2 {
|
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
private Boolean autoCreateUser = false;
|
private String entityId;
|
||||||
private Boolean blockRegistration = false;
|
private String registrationId;
|
||||||
private String registrationId = "stirling";
|
private String spBaseUrl;
|
||||||
private String idpMetadataUri;
|
private String idpMetadataLocation;
|
||||||
private String idpSingleLogoutUrl;
|
private KeyStore keystore;
|
||||||
private String idpSingleLoginUrl;
|
|
||||||
private String idpIssuer;
|
|
||||||
private String idpCert;
|
|
||||||
private String privateKey;
|
|
||||||
private String spCert;
|
|
||||||
|
|
||||||
public InputStream getIdpMetadataUri() throws IOException {
|
@Data
|
||||||
if (idpMetadataUri.startsWith("classpath:")) {
|
public static class KeyStore {
|
||||||
return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
|
private String keystoreLocation;
|
||||||
.getInputStream();
|
private String keystorePassword;
|
||||||
}
|
private String keyAlias;
|
||||||
try {
|
private String keyPassword;
|
||||||
URI uri = new URI(idpMetadataUri);
|
private String realmCertificateAlias;
|
||||||
URL url = uri.toURL();
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setRequestMethod("GET");
|
|
||||||
return connection.getInputStream();
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new IOException("Invalid URI format: " + idpMetadataUri, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Resource getSpCert() {
|
public Resource getKeystoreResource() {
|
||||||
if (spCert.startsWith("classpath:")) {
|
if (keystoreLocation.startsWith("classpath:")) {
|
||||||
return new ClassPathResource(spCert.substring("classpath:".length()));
|
return new ClassPathResource(
|
||||||
} else {
|
keystoreLocation.substring("classpath:".length()));
|
||||||
return new FileSystemResource(spCert);
|
} else {
|
||||||
}
|
return new FileSystemResource(keystoreLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource getidpCert() {
|
|
||||||
if (idpCert.startsWith("classpath:")) {
|
|
||||||
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
|
||||||
} else {
|
|
||||||
return new FileSystemResource(idpCert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Resource getPrivateKey() {
|
|
||||||
if (privateKey.startsWith("classpath:")) {
|
|
||||||
return new ClassPathResource(privateKey.substring("classpath:".length()));
|
|
||||||
} else {
|
|
||||||
return new FileSystemResource(privateKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,98 +237,4 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class ProcessExecutor {
|
|
||||||
private SessionLimit sessionLimit = new SessionLimit();
|
|
||||||
private TimeoutMinutes timeoutMinutes = new TimeoutMinutes();
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class SessionLimit {
|
|
||||||
private int libreOfficeSessionLimit;
|
|
||||||
private int pdfToHtmlSessionLimit;
|
|
||||||
private int ocrMyPdfSessionLimit;
|
|
||||||
private int pythonOpenCvSessionLimit;
|
|
||||||
private int ghostScriptSessionLimit;
|
|
||||||
private int weasyPrintSessionLimit;
|
|
||||||
private int installAppSessionLimit;
|
|
||||||
private int calibreSessionLimit;
|
|
||||||
|
|
||||||
public int getLibreOfficeSessionLimit() {
|
|
||||||
return libreOfficeSessionLimit > 0 ? libreOfficeSessionLimit : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPdfToHtmlSessionLimit() {
|
|
||||||
return pdfToHtmlSessionLimit > 0 ? pdfToHtmlSessionLimit : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOcrMyPdfSessionLimit() {
|
|
||||||
return ocrMyPdfSessionLimit > 0 ? ocrMyPdfSessionLimit : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPythonOpenCvSessionLimit() {
|
|
||||||
return pythonOpenCvSessionLimit > 0 ? pythonOpenCvSessionLimit : 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGhostScriptSessionLimit() {
|
|
||||||
return ghostScriptSessionLimit > 0 ? ghostScriptSessionLimit : 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWeasyPrintSessionLimit() {
|
|
||||||
return weasyPrintSessionLimit > 0 ? weasyPrintSessionLimit : 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInstallAppSessionLimit() {
|
|
||||||
return installAppSessionLimit > 0 ? installAppSessionLimit : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCalibreSessionLimit() {
|
|
||||||
return calibreSessionLimit > 0 ? calibreSessionLimit : 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class TimeoutMinutes {
|
|
||||||
private long libreOfficeTimeoutMinutes;
|
|
||||||
private long pdfToHtmlTimeoutMinutes;
|
|
||||||
private long ocrMyPdfTimeoutMinutes;
|
|
||||||
private long pythonOpenCvTimeoutMinutes;
|
|
||||||
private long ghostScriptTimeoutMinutes;
|
|
||||||
private long weasyPrintTimeoutMinutes;
|
|
||||||
private long installAppTimeoutMinutes;
|
|
||||||
private long calibreTimeoutMinutes;
|
|
||||||
|
|
||||||
public long getLibreOfficeTimeoutMinutes() {
|
|
||||||
return libreOfficeTimeoutMinutes > 0 ? libreOfficeTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getPdfToHtmlTimeoutMinutes() {
|
|
||||||
return pdfToHtmlTimeoutMinutes > 0 ? pdfToHtmlTimeoutMinutes : 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getOcrMyPdfTimeoutMinutes() {
|
|
||||||
return ocrMyPdfTimeoutMinutes > 0 ? ocrMyPdfTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getPythonOpenCvTimeoutMinutes() {
|
|
||||||
return pythonOpenCvTimeoutMinutes > 0 ? pythonOpenCvTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getGhostScriptTimeoutMinutes() {
|
|
||||||
return ghostScriptTimeoutMinutes > 0 ? ghostScriptTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getWeasyPrintTimeoutMinutes() {
|
|
||||||
return weasyPrintTimeoutMinutes > 0 ? weasyPrintTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getInstallAppTimeoutMinutes() {
|
|
||||||
return installAppTimeoutMinutes > 0 ? installAppTimeoutMinutes : 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCalibreTimeoutMinutes() {
|
|
||||||
return calibreTimeoutMinutes > 0 ? calibreTimeoutMinutes : 30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class Provider implements ProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isValid(Collection<String> value, String name) {
|
protected boolean isValid(Collection<String> value, String name) {
|
||||||
@@ -26,55 +27,66 @@ public class Provider implements ProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getScopes() {
|
public Collection<String> getScopes() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setScopes(String scopes) {
|
public void setScopes(String scopes) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUseAsUsername() {
|
public String getUseAsUsername() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUseAsUsername(String useAsUsername) {
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIssuer() {
|
public String getIssuer() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setIssuer(String issuer) {
|
public void setIssuer(String issuer) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientSecret() {
|
public String getClientSecret() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setClientSecret(String clientSecret) {
|
public void setClientSecret(String clientSecret) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setClientId(String clientId) {
|
public void setClientId(String clientId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package stirling.software.SPDF.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class SignatureFile {
|
|
||||||
private String fileName;
|
|
||||||
private String category; // "Personal" or "Shared"
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,4 @@ public class SignPDFWithCertRequest extends PDFFile {
|
|||||||
description =
|
description =
|
||||||
"The page number where the signature should be visible. This is required if showSignature is set to true")
|
"The page number where the signature should be visible. This is required if showSignature is set to true")
|
||||||
private Integer pageNumber;
|
private Integer pageNumber;
|
||||||
|
|
||||||
@Schema(description = "Whether to visually show a signature logo along with the signature")
|
|
||||||
private boolean showLogo;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.Date;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
|
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.PersistentLogin;
|
import stirling.software.SPDF.model.PersistentLogin;
|
||||||
|
|
||||||
@@ -14,7 +13,6 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
|||||||
@Autowired private PersistentLoginRepository persistentLoginRepository;
|
@Autowired private PersistentLoginRepository persistentLoginRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public void createNewToken(PersistentRememberMeToken token) {
|
public void createNewToken(PersistentRememberMeToken token) {
|
||||||
PersistentLogin newToken = new PersistentLogin();
|
PersistentLogin newToken = new PersistentLogin();
|
||||||
newToken.setSeries(token.getSeries());
|
newToken.setSeries(token.getSeries());
|
||||||
@@ -25,7 +23,6 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public void updateToken(String series, String tokenValue, Date lastUsed) {
|
public void updateToken(String series, String tokenValue, Date lastUsed) {
|
||||||
PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);
|
PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);
|
||||||
if (existingToken != null) {
|
if (existingToken != null) {
|
||||||
@@ -46,11 +43,11 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public void removeUserTokens(String username) {
|
public void removeUserTokens(String username) {
|
||||||
try {
|
for (PersistentLogin token : persistentLoginRepository.findAll()) {
|
||||||
persistentLoginRepository.deleteByUsername(username);
|
if (token.getUsername().equals(username)) {
|
||||||
} catch (Exception e) {
|
persistentLoginRepository.delete(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,4 @@ import org.springframework.stereotype.Repository;
|
|||||||
import stirling.software.SPDF.model.PersistentLogin;
|
import stirling.software.SPDF.model.PersistentLogin;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {
|
public interface PersistentLoginRepository extends JpaRepository<PersistentLogin, String> {}
|
||||||
void deleteByUsername(String username);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
package stirling.software.SPDF.service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class LanguageService {
|
|
||||||
|
|
||||||
private final PathMatchingResourcePatternResolver resourcePatternResolver =
|
|
||||||
new PathMatchingResourcePatternResolver();
|
|
||||||
|
|
||||||
public List<String> getSupportedLanguages() {
|
|
||||||
List<String> supportedLanguages = new ArrayList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Resource[] resources =
|
|
||||||
resourcePatternResolver.getResources("classpath*:messages_*.properties");
|
|
||||||
for (Resource resource : resources) {
|
|
||||||
if (resource.exists() && resource.isReadable()) {
|
|
||||||
String filename = resource.getFilename();
|
|
||||||
if (filename != null
|
|
||||||
&& filename.startsWith("messages_")
|
|
||||||
&& filename.endsWith(".properties")) {
|
|
||||||
String languageCode =
|
|
||||||
filename.replace("messages_", "").replace(".properties", "");
|
|
||||||
supportedLanguages.add(languageCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return supportedLanguages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.posthog.java.PostHog;
|
import com.posthog.java.PostHog;
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -27,23 +26,20 @@ public class PostHogService {
|
|||||||
private final PostHog postHog;
|
private final PostHog postHog;
|
||||||
private final String uniqueId;
|
private final String uniqueId;
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final UserServiceInterface userService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PostHogService(
|
public PostHogService(
|
||||||
PostHog postHog,
|
PostHog postHog,
|
||||||
@Qualifier("UUID") String uuid,
|
@Qualifier("UUID") String uuid,
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties) {
|
||||||
@Autowired(required = false) UserServiceInterface userService) {
|
|
||||||
this.postHog = postHog;
|
this.postHog = postHog;
|
||||||
this.uniqueId = uuid;
|
this.uniqueId = uuid;
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.userService = userService;
|
|
||||||
captureSystemInfo();
|
captureSystemInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void captureSystemInfo() {
|
private void captureSystemInfo() {
|
||||||
if (!Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -54,7 +50,7 @@ public class PostHogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void captureEvent(String eventName, Map<String, Object> properties) {
|
public void captureEvent(String eventName, Map<String, Object> properties) {
|
||||||
if (!Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
postHog.capture(uniqueId, eventName, properties);
|
postHog.capture(uniqueId, eventName, properties);
|
||||||
@@ -138,10 +134,6 @@ public class PostHogService {
|
|||||||
}
|
}
|
||||||
metrics.put("application_properties", captureApplicationProperties());
|
metrics.put("application_properties", captureApplicationProperties());
|
||||||
|
|
||||||
if (userService != null) {
|
|
||||||
metrics.put("total_users_created", userService.getTotalUsersCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
metrics.put("error", e.getMessage());
|
metrics.put("error", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
package stirling.software.SPDF.service;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.thymeleaf.util.StringUtils;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.model.SignatureFile;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class SignatureService {
|
|
||||||
|
|
||||||
private static final String SIGNATURE_BASE_PATH = "customFiles/signatures/";
|
|
||||||
private static final String ALL_USERS_FOLDER = "ALL_USERS";
|
|
||||||
|
|
||||||
public boolean hasAccessToFile(String username, String fileName) throws IOException {
|
|
||||||
validateFileName(fileName);
|
|
||||||
// Check if file exists in user's personal folder or ALL_USERS folder
|
|
||||||
Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);
|
|
||||||
Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);
|
|
||||||
|
|
||||||
return Files.exists(userPath) || Files.exists(allUsersPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SignatureFile> getAvailableSignatures(String username) {
|
|
||||||
List<SignatureFile> signatures = new ArrayList<>();
|
|
||||||
|
|
||||||
// Get signatures from user's personal folder
|
|
||||||
if (!StringUtils.isEmptyOrWhitespace(username)) {
|
|
||||||
Path userFolder = Paths.get(SIGNATURE_BASE_PATH, username);
|
|
||||||
if (Files.exists(userFolder)) {
|
|
||||||
try {
|
|
||||||
signatures.addAll(getSignaturesFromFolder(userFolder, "Personal"));
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error reading user signatures folder", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get signatures from ALL_USERS folder
|
|
||||||
Path allUsersFolder = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER);
|
|
||||||
if (Files.exists(allUsersFolder)) {
|
|
||||||
try {
|
|
||||||
signatures.addAll(getSignaturesFromFolder(allUsersFolder, "Shared"));
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error reading shared signatures folder", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return signatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SignatureFile> getSignaturesFromFolder(Path folder, String category)
|
|
||||||
throws IOException {
|
|
||||||
return Files.list(folder)
|
|
||||||
.filter(path -> isImageFile(path))
|
|
||||||
.map(path -> new SignatureFile(path.getFileName().toString(), category))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSignatureBytes(String username, String fileName) throws IOException {
|
|
||||||
validateFileName(fileName);
|
|
||||||
// First try user's personal folder
|
|
||||||
Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);
|
|
||||||
if (Files.exists(userPath)) {
|
|
||||||
return Files.readAllBytes(userPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then try ALL_USERS folder
|
|
||||||
Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);
|
|
||||||
if (Files.exists(allUsersPath)) {
|
|
||||||
return Files.readAllBytes(allUsersPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new FileNotFoundException("Signature file not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isImageFile(Path path) {
|
|
||||||
String fileName = path.getFileName().toString().toLowerCase();
|
|
||||||
return fileName.endsWith(".jpg")
|
|
||||||
|| fileName.endsWith(".jpeg")
|
|
||||||
|| fileName.endsWith(".png")
|
|
||||||
|| fileName.endsWith(".gif");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateFileName(String fileName) {
|
|
||||||
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
|
|
||||||
throw new IllegalArgumentException("Invalid filename");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,13 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.image.AffineTransformOp;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBuffer;
|
import java.awt.image.DataBuffer;
|
||||||
import java.awt.image.DataBufferByte;
|
import java.awt.image.DataBufferByte;
|
||||||
import java.awt.image.DataBufferInt;
|
import java.awt.image.DataBufferInt;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import com.drew.imaging.ImageMetadataReader;
|
|
||||||
import com.drew.imaging.ImageProcessingException;
|
|
||||||
import com.drew.metadata.Metadata;
|
|
||||||
import com.drew.metadata.MetadataException;
|
|
||||||
import com.drew.metadata.exif.ExifSubIFDDirectory;
|
|
||||||
|
|
||||||
public class ImageProcessingUtils {
|
public class ImageProcessingUtils {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
|
||||||
|
|
||||||
static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
|
static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
|
||||||
BufferedImage convertedImage;
|
BufferedImage convertedImage;
|
||||||
switch (colorType) {
|
switch (colorType) {
|
||||||
@@ -77,51 +59,4 @@ public class ImageProcessingUtils {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double extractImageOrientation(InputStream is) throws IOException {
|
|
||||||
try {
|
|
||||||
Metadata metadata = ImageMetadataReader.readMetadata(is);
|
|
||||||
ExifSubIFDDirectory directory =
|
|
||||||
metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
|
|
||||||
if (directory == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int orientationTag = directory.getInt(ExifSubIFDDirectory.TAG_ORIENTATION);
|
|
||||||
switch (orientationTag) {
|
|
||||||
case 1:
|
|
||||||
return 0;
|
|
||||||
case 6:
|
|
||||||
return 90;
|
|
||||||
case 3:
|
|
||||||
return 180;
|
|
||||||
case 8:
|
|
||||||
return 270;
|
|
||||||
default:
|
|
||||||
logger.warn("Unknown orientation tag: {}", orientationTag);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} catch (ImageProcessingException | MetadataException e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BufferedImage applyOrientation(BufferedImage image, double orientation) {
|
|
||||||
if (orientation == 0) {
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
AffineTransform transform =
|
|
||||||
AffineTransform.getRotateInstance(
|
|
||||||
Math.toRadians(orientation),
|
|
||||||
image.getWidth() / 2.0,
|
|
||||||
image.getHeight() / 2.0);
|
|
||||||
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
|
|
||||||
return op.filter(image, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BufferedImage loadImageWithExifOrientation(MultipartFile file)
|
|
||||||
throws IOException {
|
|
||||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
|
||||||
double orientation = extractImageOrientation(file.getInputStream());
|
|
||||||
return applyOrientation(image, orientation);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,8 +194,7 @@ public class PdfUtils {
|
|||||||
|
|
||||||
pdfDocument.close();
|
pdfDocument.close();
|
||||||
|
|
||||||
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842"
|
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
|
||||||
// for A4
|
|
||||||
String[] dimensions = expectedPageSize.split("x");
|
String[] dimensions = expectedPageSize.split("x");
|
||||||
float expectedPageWidth = Float.parseFloat(dimensions[0]);
|
float expectedPageWidth = Float.parseFloat(dimensions[0]);
|
||||||
float expectedPageHeight = Float.parseFloat(dimensions[1]);
|
float expectedPageHeight = Float.parseFloat(dimensions[1]);
|
||||||
@@ -408,7 +407,7 @@ public class PdfUtils {
|
|||||||
addImageToDocument(doc, pdImage, fitOption, autoRotate);
|
addImageToDocument(doc, pdImage, fitOption, autoRotate);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BufferedImage image = ImageProcessingUtils.loadImageWithExifOrientation(file);
|
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||||
BufferedImage convertedImage =
|
BufferedImage convertedImage =
|
||||||
ImageProcessingUtils.convertColorType(image, colorType);
|
ImageProcessingUtils.convertColorType(image, colorType);
|
||||||
// Use JPEGFactory if it's JPEG since JPEG is lossy
|
// Use JPEGFactory if it's JPEG since JPEG is lossy
|
||||||
|
|||||||
@@ -18,14 +18,10 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import io.github.pixee.security.BoundedLineReader;
|
import io.github.pixee.security.BoundedLineReader;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
|
|
||||||
public class ProcessExecutor {
|
public class ProcessExecutor {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
|
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
|
||||||
|
|
||||||
private static ApplicationProperties applicationProperties = new ApplicationProperties();
|
|
||||||
|
|
||||||
public enum Processes {
|
public enum Processes {
|
||||||
LIBRE_OFFICE,
|
LIBRE_OFFICE,
|
||||||
PDFTOHTML,
|
PDFTOHTML,
|
||||||
@@ -49,90 +45,26 @@ public class ProcessExecutor {
|
|||||||
key -> {
|
key -> {
|
||||||
int semaphoreLimit =
|
int semaphoreLimit =
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case LIBRE_OFFICE ->
|
case LIBRE_OFFICE -> 1;
|
||||||
applicationProperties
|
case PDFTOHTML -> 1;
|
||||||
.getProcessExecutor()
|
case OCR_MY_PDF -> 2;
|
||||||
.getSessionLimit()
|
case PYTHON_OPENCV -> 8;
|
||||||
.getLibreOfficeSessionLimit();
|
case GHOSTSCRIPT -> 16;
|
||||||
case PDFTOHTML ->
|
case WEASYPRINT -> 16;
|
||||||
applicationProperties
|
case INSTALL_APP -> 1;
|
||||||
.getProcessExecutor()
|
case CALIBRE -> 1;
|
||||||
.getSessionLimit()
|
|
||||||
.getPdfToHtmlSessionLimit();
|
|
||||||
case OCR_MY_PDF ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getOcrMyPdfSessionLimit();
|
|
||||||
case PYTHON_OPENCV ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getPythonOpenCvSessionLimit();
|
|
||||||
case GHOSTSCRIPT ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getGhostScriptSessionLimit();
|
|
||||||
case WEASYPRINT ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getWeasyPrintSessionLimit();
|
|
||||||
case INSTALL_APP ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getInstallAppSessionLimit();
|
|
||||||
case CALIBRE ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getSessionLimit()
|
|
||||||
.getCalibreSessionLimit();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
long timeoutMinutes =
|
long timeoutMinutes =
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case LIBRE_OFFICE ->
|
case LIBRE_OFFICE -> 30;
|
||||||
applicationProperties
|
case PDFTOHTML -> 20;
|
||||||
.getProcessExecutor()
|
case OCR_MY_PDF -> 30;
|
||||||
.getTimeoutMinutes()
|
case PYTHON_OPENCV -> 30;
|
||||||
.getLibreOfficeTimeoutMinutes();
|
case GHOSTSCRIPT -> 30;
|
||||||
case PDFTOHTML ->
|
case WEASYPRINT -> 30;
|
||||||
applicationProperties
|
case INSTALL_APP -> 60;
|
||||||
.getProcessExecutor()
|
case CALIBRE -> 30;
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getPdfToHtmlTimeoutMinutes();
|
|
||||||
case OCR_MY_PDF ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getOcrMyPdfTimeoutMinutes();
|
|
||||||
case PYTHON_OPENCV ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getPythonOpenCvTimeoutMinutes();
|
|
||||||
case GHOSTSCRIPT ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getGhostScriptTimeoutMinutes();
|
|
||||||
case WEASYPRINT ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getWeasyPrintTimeoutMinutes();
|
|
||||||
case INSTALL_APP ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getInstallAppTimeoutMinutes();
|
|
||||||
case CALIBRE ->
|
|
||||||
applicationProperties
|
|
||||||
.getProcessExecutor()
|
|
||||||
.getTimeoutMinutes()
|
|
||||||
.getCalibreTimeoutMinutes();
|
|
||||||
};
|
};
|
||||||
return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes);
|
return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a temporary file, with the original filename from the multipart file
|
// Create a temporary file, with the original filename from the multipart file
|
||||||
File file = Files.createTempFile("temp", getFileInput().getOriginalFilename()).toFile();
|
File file = File.createTempFile("temp", getFileInput().getOriginalFilename());
|
||||||
|
|
||||||
// Transfer the content of the multipart file to the file
|
// Transfer the content of the multipart file to the file
|
||||||
getFileInput().transferTo(file);
|
getFileInput().transferTo(file);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {
|
|||||||
public InputStreamResource replace() throws IOException {
|
public InputStreamResource replace() throws IOException {
|
||||||
|
|
||||||
// Create a temporary file, with the original filename from the multipart file
|
// Create a temporary file, with the original filename from the multipart file
|
||||||
File file = Files.createTempFile("temp", getFileInput().getOriginalFilename()).toFile();
|
File file = File.createTempFile("temp", getFileInput().getOriginalFilename());
|
||||||
|
|
||||||
// Transfer the content of the multipart file to the file
|
// Transfer the content of the multipart file to the file
|
||||||
getFileInput().transferTo(file);
|
getFileInput().transferTo(file);
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ springdoc.swagger-ui.url=/v1/api-docs
|
|||||||
|
|
||||||
|
|
||||||
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
||||||
posthog.host=https://eu.i.posthog.com
|
posthog.host=https://eu.i.posthog.com
|
||||||
@@ -76,18 +76,15 @@ donate=تبرع
|
|||||||
color=لون
|
color=لون
|
||||||
sponsor=راعٍ
|
sponsor=راعٍ
|
||||||
info=معلومات
|
info=معلومات
|
||||||
pro=محترف
|
pro=Pro
|
||||||
page=صفحة
|
page=Page
|
||||||
pages=صفحات
|
pages=Pages
|
||||||
loading=جارٍ التحميل...
|
|
||||||
addToDoc=إضافة إلى المستند
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=سياسة الخصوصية
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=شروط الاستخدام
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Accessibility
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=سياسة ملفات تعريف الارتباط
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=بيان الهوية
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -117,8 +114,8 @@ pipelineOptions.validateButton=تحقق
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=ترقية إلى محترف
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=هذه الخاصية متوفرة فقط للمستخدمين المحترفين.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
@@ -126,12 +123,12 @@ enterpriseEdition.ssoAdvert=Looking for more user management features? Check out
|
|||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=هل تريد تحسين Stirling PDF؟
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF يحتوي على إحصائيات مختصة للمساعدة في تحسين المنتج. لا نتبع أي معلومات شخصية أو محتوى الملفات.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=يرجى مراعاة تفعيل الإحصائيات لمساعدتنا على نمو Stirling-PDF وتوفير فهم أفضل لمستخدمينا.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=تفعيل الإحصائيات
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=تعطيل الإحصائيات
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=يمكنك تغيير إعدادات الإحصائيات في ملف config/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +139,13 @@ navbar.language=اللغات
|
|||||||
navbar.settings=إعدادات
|
navbar.settings=إعدادات
|
||||||
navbar.allTools=أدوات
|
navbar.allTools=أدوات
|
||||||
navbar.multiTool=أدوات متعددة
|
navbar.multiTool=أدوات متعددة
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=تنظيم
|
navbar.sections.organize=تنظيم
|
||||||
navbar.sections.convertTo=تحويل الى PDF
|
navbar.sections.convertTo=تحويل الى PDF
|
||||||
navbar.sections.convertFrom=تحويل من PDF
|
navbar.sections.convertFrom=تحويل من PDF
|
||||||
navbar.sections.security=التوقيع والأمان
|
navbar.sections.security=التوقيع والأمان
|
||||||
navbar.sections.advance=متقدم
|
navbar.sections.advance=متقدم
|
||||||
navbar.sections.edit=عرض وتعديل
|
navbar.sections.edit=عرض وتعديل
|
||||||
navbar.sections.popular=المفضل
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -248,7 +244,6 @@ database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خ
|
|||||||
database.failedImportFile=فشل استيراد الملف
|
database.failedImportFile=فشل استيراد الملف
|
||||||
|
|
||||||
session.expired=Your session has expired. Please refresh the page and try again.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -508,28 +503,28 @@ home.removeImagePdf.desc=إزالة الصورة من PDF لتقليل حجم ا
|
|||||||
removeImagePdf.tags=إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم
|
removeImagePdf.tags=إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=تجزئة المستندات PDF حسب الفصول
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=قسم مستند PDF إلى ملفات متعددة بناءً على هيكل فصوله.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=تجزئة، فصول، علامات تبويب، تنظيم
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=استبدال-إلغاء مirro لون PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Replace and Invert Color
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=استبدال الألوان للنصوص والخلفيات في المستندات PDF وإلغاء تعكير اللون الكامل للمستند لتقليل حجم الملف
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=استبدال اللون، عمليات الصفحة، الخلفية، جانب الخادم
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=خيارات استبدال-إلغاء مirro لون
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Default(Default high contrast colors)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=خصيصة (ألوان شخصية)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=خيارات ألوان التباين العالي
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=نص أبيض على خلفية سوداء
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=نص أسود على خلفية بيضاء
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=نص صفرة على خلفية سوداء
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=نص أخضر على خلفية سوداء
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=اختر لون النص
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=اختر لون الخلفية
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=استبدال
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -556,9 +551,10 @@ login.oauth2AccessDenied=تم رفض الوصول
|
|||||||
login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة
|
login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة
|
||||||
login.oauth2InvalidIdToken=رمز الهوية غير صالح
|
login.oauth2InvalidIdToken=رمز الهوية غير صالح
|
||||||
login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول.
|
login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول.
|
||||||
login.alreadyLoggedIn=لقد تسجل دخولًا إلى
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=أجهزة أخرى. يرجى تسجيل الخروج من الأجهزة وحاول مرة أخرى.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=لديك عدة جلسات نشطة
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=حجب تلقائي
|
autoRedact.title=حجب تلقائي
|
||||||
@@ -733,7 +729,7 @@ pageLayout.submit=إرسال
|
|||||||
scalePages.title=ضبط مقياس الصفحة
|
scalePages.title=ضبط مقياس الصفحة
|
||||||
scalePages.header=ضبط مقياس الصفحة
|
scalePages.header=ضبط مقياس الصفحة
|
||||||
scalePages.pageSize=حجم صفحة المستند.
|
scalePages.pageSize=حجم صفحة المستند.
|
||||||
scalePages.keepPageSize=الحجم الأصلي
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=مستوى التكبير (الاقتصاص) للصفحة.
|
scalePages.scaleFactor=مستوى التكبير (الاقتصاص) للصفحة.
|
||||||
scalePages.submit=إرسال
|
scalePages.submit=إرسال
|
||||||
|
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=إظهار التوقيع
|
|||||||
certSign.reason=السبب
|
certSign.reason=السبب
|
||||||
certSign.location=الموقع
|
certSign.location=الموقع
|
||||||
certSign.name=الاسم
|
certSign.name=الاسم
|
||||||
certSign.showLogo=عرض الشعار
|
|
||||||
certSign.submit=توقيع PDF
|
certSign.submit=توقيع PDF
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=لون التظليل 2:
|
|||||||
compare.document.1=المستند 1
|
compare.document.1=المستند 1
|
||||||
compare.document.2=المستند 2
|
compare.document.2=المستند 2
|
||||||
compare.submit=مقارنة
|
compare.submit=مقارنة
|
||||||
compare.complex.message=أو كلا المستندين المقدمين كبيران حجمًا، مما يؤدي إلى تقليل دقة المقارنة
|
|
||||||
compare.large.file.message=أو كلا المستندين المقدمين كبيرة حجمهما للتعامل معهما
|
|
||||||
compare.no.text.message=أحد أو كلي المستندات المرجوة للمقارنة لا يحتوي على محتوى نصي. يرجى اختيار مستندات تحتوي على نص لم يتم التعرف عليه.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=الكتب والكوميكس إلى PDF
|
BookToPDF.title=الكتب والكوميكس إلى PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=رسم التوقيع
|
|||||||
sign.text=إدخال النص
|
sign.text=إدخال النص
|
||||||
sign.clear=مسح
|
sign.clear=مسح
|
||||||
sign.add=إضافة
|
sign.add=إضافة
|
||||||
sign.saved=توقيعات تم حفظها
|
|
||||||
sign.save=حفظ توقيع
|
|
||||||
sign.personalSigs=توقيعات شخصية
|
|
||||||
sign.sharedSigs=توقيعات مشتركة
|
|
||||||
sign.noSavedSigs=لم يتم العثور على توقيعات محفوظة
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(مثال: 1,3,2 أو 4-8,2,10-12 أو 2n-1)
|
|||||||
multiTool.title=أداة متعددة PDF
|
multiTool.title=أداة متعددة PDF
|
||||||
multiTool.header=أداة متعددة PDF
|
multiTool.header=أداة متعددة PDF
|
||||||
multiTool.uploadPrompts=اسم الملف
|
multiTool.uploadPrompts=اسم الملف
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=عرض PDF
|
viewPdf.title=عرض PDF
|
||||||
@@ -1206,8 +1182,8 @@ licenses.license=الترخيص
|
|||||||
survey.nav=استطلاع
|
survey.nav=استطلاع
|
||||||
survey.title=استطلاع Stirling-PDF
|
survey.title=استطلاع Stirling-PDF
|
||||||
survey.description=Stirling-PDF لا يحتوي على تتبع لذا نريد أن نسمع من مستخدمينا لتحسين Stirling-PDF!
|
survey.description=Stirling-PDF لا يحتوي على تتبع لذا نريد أن نسمع من مستخدمينا لتحسين Stirling-PDF!
|
||||||
survey.changes=تحديث Stirling-PDF منذ آخر استبيان! للحصول على المزيد من المعلومات الرجاء زيارة مقالتنا في المدونة هنا:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=مع هذه التحديثات، نستفيد من الدعم العملي والمنحة المالية
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=يرجى النظر في المشاركة في استطلاعنا!
|
survey.please=يرجى النظر في المشاركة في استطلاعنا!
|
||||||
survey.disabled=(سيتم تعطيل النافذة المنبثقة للاستطلاع في التحديثات التالية ولكنها ستكون متاحة في أسفل الصفحة)
|
survey.disabled=(سيتم تعطيل النافذة المنبثقة للاستطلاع في التحديثات التالية ولكنها ستكون متاحة في أسفل الصفحة)
|
||||||
survey.button=المشاركة في الاستطلاع
|
survey.button=المشاركة في الاستطلاع
|
||||||
@@ -1235,13 +1211,15 @@ removeImage.removeImage=إزالة الصورة
|
|||||||
removeImage.submit=إزالة الصورة
|
removeImage.submit=إزالة الصورة
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=تجزئة المستند حسب الفصول
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=تجزئة المستند حسب الفصول
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=مستوى العلامات التذكارية
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=شامل البيانات المرفقة
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Allow Duplicates
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=تمثيل البيانات الأصلية: إذا تم اختيارها، سترمز البيانات المرجعية الأصلية إلى كل PDF مجزأ.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=سماح بالتكرار: إذا تم اختياره، يسمح بوجود معاينات متعددة في الصفحة نفسها لخلق ملفات PDF منفصلة.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=تقطيع ملف PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Размер на шрифт
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Име на шрифт
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=Изберете PDF(и)
|
pdfPrompt=Изберете PDF(и)
|
||||||
multiPdfPrompt=Изберете PDF (2+)
|
multiPdfPrompt=Изберете PDF (2+)
|
||||||
multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете
|
multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете
|
||||||
@@ -56,12 +56,12 @@ userNotFoundMessage=Потребителят не е намерен
|
|||||||
incorrectPasswordMessage=Текущата парола е неправилна.
|
incorrectPasswordMessage=Текущата парола е неправилна.
|
||||||
usernameExistsMessage=Новият потребител вече съществува.
|
usernameExistsMessage=Новият потребител вече съществува.
|
||||||
invalidUsernameMessage=Невалидно потребителско име, потребителското име може да съдържа само букви, цифри и следните специални знаци @._+- или трябва да е валиден имейл адрес.
|
invalidUsernameMessage=Невалидно потребителско име, потребителското име може да съдържа само букви, цифри и следните специални знаци @._+- или трябва да е валиден имейл адрес.
|
||||||
invalidPasswordMessage=Паролата не трябва да е празна и не трябва да има интервали в началото или в края.
|
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||||
confirmPasswordErrorMessage=Нова парола и Потвърждаване на новата парола трябва да съвпадат.
|
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
|
||||||
deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
|
deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
|
||||||
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
|
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
|
||||||
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
|
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
|
||||||
disabledCurrentUserMessage=Текущият потребител не може да бъде деактивиран
|
disabledCurrentUserMessage=The current user cannot be disabled
|
||||||
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
|
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
|
||||||
userAlreadyExistsOAuthMessage=Потребителят вече съществува като OAuth2 потребител.
|
userAlreadyExistsOAuthMessage=Потребителят вече съществува като OAuth2 потребител.
|
||||||
userAlreadyExistsWebMessage=Потребителят вече съществува като уеб-потребител.
|
userAlreadyExistsWebMessage=Потребителят вече съществува като уеб-потребител.
|
||||||
@@ -75,19 +75,16 @@ visitGithub=Посетете Github Repository
|
|||||||
donate=Направете дарение
|
donate=Направете дарение
|
||||||
color=Цвят
|
color=Цвят
|
||||||
sponsor=Спонсор
|
sponsor=Спонсор
|
||||||
info=Информация
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Страница
|
page=Page
|
||||||
pages=Страници
|
pages=Pages
|
||||||
loading=Loading...
|
|
||||||
addToDoc=Add to Document
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Политика за поверителност
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Правила и условия
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Достъпност
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Политика за бисквитки
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Отпечатък
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -99,7 +96,7 @@ pipeline.defaultOption=Персонализиран
|
|||||||
pipeline.submitButton=Подайте
|
pipeline.submitButton=Подайте
|
||||||
pipeline.help=Pipeline Помощ
|
pipeline.help=Pipeline Помощ
|
||||||
pipeline.scanHelp=Помощ за сканиране на папки
|
pipeline.scanHelp=Помощ за сканиране на папки
|
||||||
pipeline.deletePrompt=Сигурни ли сте, че искате да изтриете pipeline
|
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Pipeline Options #
|
# Pipeline Options #
|
||||||
@@ -117,21 +114,21 @@ pipelineOptions.validateButton=Валидирай
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Направете надстройка до Pro версията
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Тази функция е достъпна само за потребители на Pro версията.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro поддържа YAML конфигурационни файлове и други SSO функции.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Търсите повече функции за управление на потребителите? Погледнете за Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Искате ли да подобрите Stirling PDF?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF включва анализи, за да ни помогне да подобрим продукта. Ние не проследяваме лична информация или съдържание на файлове.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Моля, обмислете възможността за анализ, за да помогнете на Stirling-PDF да расте и да ни позволи да разберем по-добре нашите потребители.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Активиране на анализа
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Деактивиране на анализа
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Можете да промените настройките за анализ във config/settings.yml файла
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +139,13 @@ navbar.language=Езици
|
|||||||
navbar.settings=Настройки
|
navbar.settings=Настройки
|
||||||
navbar.allTools=Инструменти
|
navbar.allTools=Инструменти
|
||||||
navbar.multiTool=Мулти инструменти
|
navbar.multiTool=Мулти инструменти
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Организирайте
|
navbar.sections.organize=Организирайте
|
||||||
navbar.sections.convertTo=Преобразуване в PDF
|
navbar.sections.convertTo=Преобразуване в PDF
|
||||||
navbar.sections.convertFrom=Преобразуване от PDF
|
navbar.sections.convertFrom=Преобразуване от PDF
|
||||||
navbar.sections.security=Подписване и сигурност
|
navbar.sections.security=Подписване и сигурност
|
||||||
navbar.sections.advance=Разширено
|
navbar.sections.advance=Разширено
|
||||||
navbar.sections.edit=Преглед и редактиране
|
navbar.sections.edit=Преглед и редактиране
|
||||||
navbar.sections.popular=Популярни
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -206,9 +202,9 @@ adminUserSettings.header=Настройки за администраторск
|
|||||||
adminUserSettings.admin=Администратор
|
adminUserSettings.admin=Администратор
|
||||||
adminUserSettings.user=Потребител
|
adminUserSettings.user=Потребител
|
||||||
adminUserSettings.addUser=Добавяне на нов потребител
|
adminUserSettings.addUser=Добавяне на нов потребител
|
||||||
adminUserSettings.deleteUser=Изтриване на потребител
|
adminUserSettings.deleteUser=Delete User
|
||||||
adminUserSettings.confirmDeleteUser=Трябва ли потребителят да бъде изтрит?
|
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||||
adminUserSettings.confirmChangeUserStatus=Трябва ли потребителят да бъде деактивиран/активиран?
|
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||||
adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
|
adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
|
||||||
adminUserSettings.roles=Роли
|
adminUserSettings.roles=Роли
|
||||||
adminUserSettings.role=Роля
|
adminUserSettings.role=Роля
|
||||||
@@ -222,33 +218,32 @@ adminUserSettings.forceChange=Принудете потребителя да п
|
|||||||
adminUserSettings.submit=Съхранете потребителя
|
adminUserSettings.submit=Съхранете потребителя
|
||||||
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
||||||
adminUserSettings.authenticated=Удостоверен
|
adminUserSettings.authenticated=Удостоверен
|
||||||
adminUserSettings.editOwnProfil=Редактиране на собствен профил
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=активиран потребител
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=деактивиран потребител
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=Активни потребители:
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=Деактивирани потребители:
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=Общо потребители:
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=Последна заявка
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=Импорт/Експорт на база данни
|
database.title=Database Import/Export
|
||||||
database.header=Импорт/Експорт на база данни
|
database.header=Database Import/Export
|
||||||
database.fileName=Име на файл
|
database.fileName=File Name
|
||||||
database.creationDate=Дата на създаване
|
database.creationDate=Creation Date
|
||||||
database.fileSize=Размер на файла
|
database.fileSize=File Size
|
||||||
database.deleteBackupFile=Изтриване на архивен файл
|
database.deleteBackupFile=Delete Backup File
|
||||||
database.importBackupFile=Импортиране на архивен файл
|
database.importBackupFile=Import Backup File
|
||||||
database.downloadBackupFile=Изтеглете архивен файл
|
database.downloadBackupFile=Download Backup File
|
||||||
database.info_1=Когато импортирате данни, е от решаващо значение да осигурите правилната структура. Ако не сте сигурни в това, което правите, потърсете съвет и подкрепа от професионалист. Грешка в структурата може да причини неизправност на приложението, включително пълна невъзможност за стартиране на приложението.
|
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=Името на файла няма значение при качване. След това ще бъде преименуван, за да следва формата backup_user_yyyyMMddHHmm.sql, осигурявайки последователна конвенция за именуване.
|
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=Импортиране на резервно копие
|
database.submit=Import Backup
|
||||||
database.importIntoDatabaseSuccessed=Импортирането в базата данни бе успешно
|
database.importIntoDatabaseSuccessed=Import into database successed
|
||||||
database.fileNotFound=Файлът не е намерен
|
database.fileNotFound=File not Found
|
||||||
database.fileNullOrEmpty=Файлът не трябва да е нулев или празен
|
database.fileNullOrEmpty=File must not be null or empty
|
||||||
database.failedImportFile=Неуспешно импортиране на файл
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Вашата сесия е изтекла. Моля, опреснете страницата и опитайте отново.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -395,9 +390,9 @@ home.certSign.title=Подпишете със сертификат
|
|||||||
home.certSign.desc=Подписва PDF със сертификат/ключ (PEM/P12)
|
home.certSign.desc=Подписва PDF със сертификат/ключ (PEM/P12)
|
||||||
certSign.tags=удостоверяване,PEM,P12,официален,шифроване
|
certSign.tags=удостоверяване,PEM,P12,официален,шифроване
|
||||||
|
|
||||||
home.removeCertSign.title=Премахване на знака за сертификат
|
home.removeCertSign.title=Remove Certificate Sign
|
||||||
home.removeCertSign.desc=Премахване на подпис на сертификат от PDF
|
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||||
removeCertSign.tags=удостоверяване,PEM,P12,официален,декриптиране
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Оформление с няколко страници
|
home.pageLayout.title=Оформление с няколко страници
|
||||||
home.pageLayout.desc=Слейте няколко страници от PDF документ в една страница
|
home.pageLayout.desc=Слейте няколко страници от PDF документ в една страница
|
||||||
@@ -503,33 +498,33 @@ home.BookToPDF.title=Книга към PDF
|
|||||||
home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
|
home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
|
||||||
BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
|
BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
|
||||||
|
|
||||||
home.removeImagePdf.title=Премахване на изображение
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=Премахнете изображението от PDF, за да намалите размера на файла
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Премахване на изображение, операции на страници, админ страна, страна на сървъра
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Разделете PDF по глави
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Разделете PDF на множество файлове въз основа на неговата структура на глави.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=разделяне, глави, отметки, организиране
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Замени-инвертиране-на-цвят
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Замяна-инвертиране на цвят PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Замяна и обръщане на цвят
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Заменете цвета на текста и фона в PDF и обърнете пълния цвят на PDF, за да намалите размера на файла
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Замяна на цвят, операции на страници, заден край, страна на сървъра
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Опции за замяна или инвертиране на цвят
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=По подразбиране (цветове с висок контраст по подразбиране)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=По избор (персонализирани цветове)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Пълно инвертиране (Инвертиране на всички цветове)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Цветови опции с висок контраст
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Бял текст на черен фон
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Черен текст на бял фон
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Жълт текст на черен фон
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Зелен текст на черен фон
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Изберете цвят на текста
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Изберете цвят на фона
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Замени
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -548,17 +543,18 @@ login.locked=Вашият акаунт е заключен.
|
|||||||
login.signinTitle=Моля впишете се
|
login.signinTitle=Моля впишете се
|
||||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||||
login.oauth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
||||||
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
||||||
login.oauth2invalidRequest=Невалидна заявка
|
login.oauth2invalidRequest=Невалидна заявка
|
||||||
login.oauth2AccessDenied=Отказан достъп
|
login.oauth2AccessDenied=Отказан достъп
|
||||||
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
||||||
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
||||||
login.userIsDisabled=Потребителят е деактивиран, влизането в момента е блокирано с това потребителско име. Моля, свържете се с администратора.
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
login.alreadyLoggedIn=Вече сте влезли в
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Имате твърде много активни сесии
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Автоматично редактиране
|
autoRedact.title=Автоматично редактиране
|
||||||
@@ -733,7 +729,7 @@ pageLayout.submit=Подайте
|
|||||||
scalePages.title=Коригиране на мащаба на страницата
|
scalePages.title=Коригиране на мащаба на страницата
|
||||||
scalePages.header=Коригиране на мащаба на страницата
|
scalePages.header=Коригиране на мащаба на страницата
|
||||||
scalePages.pageSize=Размер на страница от документа.
|
scalePages.pageSize=Размер на страница от документа.
|
||||||
scalePages.keepPageSize=Оригинален размер
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница.
|
scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница.
|
||||||
scalePages.submit=Подайте
|
scalePages.submit=Подайте
|
||||||
|
|
||||||
@@ -753,15 +749,14 @@ certSign.showSig=Показване на подпис
|
|||||||
certSign.reason=Причина
|
certSign.reason=Причина
|
||||||
certSign.location=Местоположение
|
certSign.location=Местоположение
|
||||||
certSign.name=Име
|
certSign.name=Име
|
||||||
certSign.showLogo=Show Logo
|
|
||||||
certSign.submit=Подпишете PDF
|
certSign.submit=Подпишете PDF
|
||||||
|
|
||||||
|
|
||||||
#removeCertSign
|
#removeCertSign
|
||||||
removeCertSign.title=Премахване на подписа на сертификата
|
removeCertSign.title=Remove Certificate Signature
|
||||||
removeCertSign.header=Премахнете цифровия сертификат от PDF
|
removeCertSign.header=Remove the digital certificate from the PDF
|
||||||
removeCertSign.selectPDF=Изберете PDF файл:
|
removeCertSign.selectPDF=Select a PDF file:
|
||||||
removeCertSign.submit=Премахване на подпис
|
removeCertSign.submit=Remove Signature
|
||||||
|
|
||||||
|
|
||||||
#removeBlanks
|
#removeBlanks
|
||||||
@@ -783,14 +778,11 @@ removeAnnotations.submit=Премахване
|
|||||||
#compare
|
#compare
|
||||||
compare.title=Сравнявай
|
compare.title=Сравнявай
|
||||||
compare.header=Сравнявай PDF-и
|
compare.header=Сравнявай PDF-и
|
||||||
compare.highlightColor.1=Цвят на маркирането 1:
|
compare.highlightColor.1=Highlight Color 1:
|
||||||
compare.highlightColor.2=Цвят на маркирането 2:
|
compare.highlightColor.2=Highlight Color 2:
|
||||||
compare.document.1=Документ 1
|
compare.document.1=Документ 1
|
||||||
compare.document.2=Документ 2
|
compare.document.2=Документ 2
|
||||||
compare.submit=Сравнявай
|
compare.submit=Сравнявай
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Книги и комикси в PDF
|
BookToPDF.title=Книги и комикси в PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Начертайте подпис
|
|||||||
sign.text=Въвеждане на текст
|
sign.text=Въвеждане на текст
|
||||||
sign.clear=Изчисти
|
sign.clear=Изчисти
|
||||||
sign.add=Добави
|
sign.add=Добави
|
||||||
sign.saved=Saved Signatures
|
|
||||||
sign.save=Save Signature
|
|
||||||
sign.personalSigs=Personal Signatures
|
|
||||||
sign.sharedSigs=Shared Signatures
|
|
||||||
sign.noSavedSigs=No saved signatures found
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -844,7 +831,7 @@ ScannerImageSplit.selectText.7=Минимална контурна площ:
|
|||||||
ScannerImageSplit.selectText.8=Задава минималния праг на контурната площ за изображение
|
ScannerImageSplit.selectText.8=Задава минималния праг на контурната площ за изображение
|
||||||
ScannerImageSplit.selectText.9=Размер на рамката:
|
ScannerImageSplit.selectText.9=Размер на рамката:
|
||||||
ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1).
|
ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1).
|
||||||
ScannerImageSplit.info=Python не е инсталиран. Изисква се да се изпълнява.
|
ScannerImageSplit.info=Python is not installed. It is required to run.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -871,7 +858,7 @@ ocr.submit=Обработка на PDF чрез OCR
|
|||||||
extractImages.title=Извличане на изображения
|
extractImages.title=Извличане на изображения
|
||||||
extractImages.header=Извличане на изображения
|
extractImages.header=Извличане на изображения
|
||||||
extractImages.selectText=Изберете формат на изображението, в който да преобразувате извлечените изображения
|
extractImages.selectText=Изберете формат на изображението, в който да преобразувате извлечените изображения
|
||||||
extractImages.allowDuplicates=Запазване на дублирани изображения
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=Извличане
|
extractImages.submit=Извличане
|
||||||
|
|
||||||
|
|
||||||
@@ -909,7 +896,7 @@ merge.title=Обединяване
|
|||||||
merge.header=Обединяване на множество PDF файлове (2+)
|
merge.header=Обединяване на множество PDF файлове (2+)
|
||||||
merge.sortByName=Сортиране по име
|
merge.sortByName=Сортиране по име
|
||||||
merge.sortByDate=Сортиране по дата
|
merge.sortByDate=Сортиране по дата
|
||||||
merge.removeCertSign=Премахване на цифровия подпис в обединения файл?
|
merge.removeCertSign=Remove digital signature in the merged file?
|
||||||
merge.submit=Обединяване
|
merge.submit=Обединяване
|
||||||
|
|
||||||
|
|
||||||
@@ -927,7 +914,7 @@ pdfOrganiser.mode.6=Четно-нечетно разделяне
|
|||||||
pdfOrganiser.mode.7=Премахни първо
|
pdfOrganiser.mode.7=Премахни първо
|
||||||
pdfOrganiser.mode.8=Премахване на последния
|
pdfOrganiser.mode.8=Премахване на последния
|
||||||
pdfOrganiser.mode.9=Премахване на първия и последния
|
pdfOrganiser.mode.9=Премахване на първия и последния
|
||||||
pdfOrganiser.mode.10=Обединяване на четно и нечетно
|
pdfOrganiser.mode.10=Odd-Even Merge
|
||||||
pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
||||||
|
|
||||||
|
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
|||||||
multiTool.title=PDF Мулти инструмент
|
multiTool.title=PDF Мулти инструмент
|
||||||
multiTool.header=PDF Мулти инструмент
|
multiTool.header=PDF Мулти инструмент
|
||||||
multiTool.uploadPrompts=Име на файл
|
multiTool.uploadPrompts=Име на файл
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=Преглед на PDF
|
viewPdf.title=Преглед на PDF
|
||||||
@@ -1007,7 +983,7 @@ pdfToImage.color=Цвят
|
|||||||
pdfToImage.grey=Скала на сивото
|
pdfToImage.grey=Скала на сивото
|
||||||
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
|
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
|
||||||
pdfToImage.submit=Преобразуване
|
pdfToImage.submit=Преобразуване
|
||||||
pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1044,7 +1020,7 @@ watermark.selectText.6=дължинаSpacer (Разстояние между в
|
|||||||
watermark.selectText.7=Непрозрачност (0% - 100%):
|
watermark.selectText.7=Непрозрачност (0% - 100%):
|
||||||
watermark.selectText.8=Тип воден знак:
|
watermark.selectText.8=Тип воден знак:
|
||||||
watermark.selectText.9=Изображение за воден знак:
|
watermark.selectText.9=Изображение за воден знак:
|
||||||
watermark.selectText.10=Конвертирайте PDF в PDF-изображение
|
watermark.selectText.10=Convert PDF to PDF-Image
|
||||||
watermark.submit=Добавяне на воден знак
|
watermark.submit=Добавяне на воден знак
|
||||||
watermark.type.1=Текст
|
watermark.type.1=Текст
|
||||||
watermark.type.2=Изображение
|
watermark.type.2=Изображение
|
||||||
@@ -1101,7 +1077,7 @@ pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A
|
|||||||
pdfToPDFA.submit=Преобразуване
|
pdfToPDFA.submit=Преобразуване
|
||||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||||
pdfToPDFA.outputFormat=Изходен формат
|
pdfToPDFA.outputFormat=Изходен формат
|
||||||
pdfToPDFA.pdfWithDigitalSignature=PDF файлът съдържа цифров подпис. Това ще бъде премахнато в следващата стъпка.
|
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@@ -1142,10 +1118,10 @@ PDFToXML.credit=Тази услуга използва LibreOffice за прео
|
|||||||
PDFToXML.submit=Преобразуване
|
PDFToXML.submit=Преобразуване
|
||||||
|
|
||||||
#PDFToCSV
|
#PDFToCSV
|
||||||
PDFToCSV.title=PDF към CSV
|
PDFToCSV.title=PDF ??? CSV
|
||||||
PDFToCSV.header=PDF към CSV
|
PDFToCSV.header=PDF ??? CSV
|
||||||
PDFToCSV.prompt=Изберете страница за извличане на таблица
|
PDFToCSV.prompt=Изберете страница за извличане на таблица
|
||||||
PDFToCSV.submit=Преобразуване
|
PDFToCSV.submit=????
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
split-by-size-or-count.title=Разделяне на PDF по размер или брой
|
split-by-size-or-count.title=Разделяне на PDF по размер или брой
|
||||||
@@ -1203,15 +1179,15 @@ licenses.version=Версия
|
|||||||
licenses.license=Лиценз
|
licenses.license=Лиценз
|
||||||
|
|
||||||
#survey
|
#survey
|
||||||
survey.nav=Анкета
|
survey.nav=Survey
|
||||||
survey.title=Stirling-PDF Анкета
|
survey.title=Stirling-PDF Survey
|
||||||
survey.description=Stirling-PDF няма проследяване, така че искаме да чуем мнението на нашите потребители за подобряване на Stirling-PDF!
|
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF се промени от последното проучване! За да научите повече, моля, проверете публикацията в нашия блог тук:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=С тези промени получаваме платена бизнес подкрепа и финансиране
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Моля, помислете дали да не участвате в нашата анкета!
|
survey.please=Please consider taking our survey!
|
||||||
survey.disabled=(Изскачащият прозорец с анкетата ще бъде деактивиран при следващите актуализации, но ще бъде наличен в долната част на страницата)
|
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||||
survey.button=Участвайте в анкетата
|
survey.button=Take Survey
|
||||||
survey.dontShowAgain=Не показвай повече
|
survey.dontShowAgain=Don't show again
|
||||||
|
|
||||||
|
|
||||||
#error
|
#error
|
||||||
@@ -1229,19 +1205,21 @@ error.discordSubmit=Discord - Изпратете запитване за под
|
|||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=Премахване на изображението
|
removeImage.title=Remove image
|
||||||
removeImage.header=Премахване на изображението
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=Премахване на изображението
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=Премахване на изображението
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
|
splitByChapters.title=Split PDF by Chapters
|
||||||
|
splitByChapters.header=Split PDF by Chapters
|
||||||
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Разделете PDF по глави
|
|
||||||
splitByChapters.header=Разделете PDF по глави
|
|
||||||
splitByChapters.bookmarkLevel=Ниво на отметка
|
|
||||||
splitByChapters.includeMetadata=Включете метаданни
|
|
||||||
splitByChapters.allowDuplicates=Разрешаване на дубликати
|
|
||||||
splitByChapters.desc.1=Този инструмент разделя PDF файл на множество PDF файлове въз основа на неговата структура на глави.
|
|
||||||
splitByChapters.desc.2=Ниво на отметка: Изберете нивото на отметките, които да използвате за разделяне (0 за най-високо ниво, 1 за второ ниво и т.н.).
|
|
||||||
splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF.
|
|
||||||
splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове.
|
|
||||||
splitByChapters.submit=Разделяне на PDF
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Velikost písma
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Název písma
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=Vyberte PDF soubory
|
pdfPrompt=Vyberte PDF soubory
|
||||||
multiPdfPrompt=Vyberte PDF soubory (2+)
|
multiPdfPrompt=Vyberte PDF soubory (2+)
|
||||||
multiPdfDropPrompt=Vyberte (nebo přetáhněte) všechny požadované PDF soubory
|
multiPdfDropPrompt=Vyberte (nebo přetáhněte) všechny požadované PDF soubory
|
||||||
@@ -56,12 +56,12 @@ userNotFoundMessage=Uživatel nenalezen.
|
|||||||
incorrectPasswordMessage=Současné heslo není správné.
|
incorrectPasswordMessage=Současné heslo není správné.
|
||||||
usernameExistsMessage=Nové uživatelské jméno již existuje.
|
usernameExistsMessage=Nové uživatelské jméno již existuje.
|
||||||
invalidUsernameMessage=Nesprávné uživatelské jméno, smí obsahovat pouze písmena, číslice a následující speciální znaky @._+- nebo musí být validní emailová adresa.
|
invalidUsernameMessage=Nesprávné uživatelské jméno, smí obsahovat pouze písmena, číslice a následující speciální znaky @._+- nebo musí být validní emailová adresa.
|
||||||
invalidPasswordMessage=Heslo nemůže být prázdné a nemůže mít mezery na začátku nebo konci.
|
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||||
confirmPasswordErrorMessage=Nové heslo musí shodovat s potvrzujícím novým heslem.
|
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
|
||||||
deleteCurrentUserMessage=Nelze smazat aktuální přihlášeného uživatele.
|
deleteCurrentUserMessage=Nelze smazat aktuální přihlášeného uživatele.
|
||||||
deleteUsernameExistsMessage=Uživatelské jméno neexistuje a nelze ho smazat.
|
deleteUsernameExistsMessage=Uživatelské jméno neexistuje a nelze ho smazat.
|
||||||
downgradeCurrentUserMessage=Nelze snížit roli aktuálního uživatele.
|
downgradeCurrentUserMessage=Nelze snížit roli aktuálního uživatele.
|
||||||
disabledCurrentUserMessage=Současný uživatel nemůže být zakázán
|
disabledCurrentUserMessage=The current user cannot be disabled
|
||||||
downgradeCurrentUserLongMessage=Nelze snížit roli aktuálního uživatele. Proto nebude aktuální uživatel zobrazen.
|
downgradeCurrentUserLongMessage=Nelze snížit roli aktuálního uživatele. Proto nebude aktuální uživatel zobrazen.
|
||||||
userAlreadyExistsOAuthMessage=Uživatel již existuje jako OAuth2 uživatel.
|
userAlreadyExistsOAuthMessage=Uživatel již existuje jako OAuth2 uživatel.
|
||||||
userAlreadyExistsWebMessage=Uživatel již existuje jako webový uživatel.
|
userAlreadyExistsWebMessage=Uživatel již existuje jako webový uživatel.
|
||||||
@@ -75,19 +75,16 @@ visitGithub=Navštivte Github repozitář
|
|||||||
donate=Přispějte
|
donate=Přispějte
|
||||||
color=Barva
|
color=Barva
|
||||||
sponsor=Sponzor
|
sponsor=Sponzor
|
||||||
info=Informace
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Strana
|
page=Page
|
||||||
pages=Strany
|
pages=Pages
|
||||||
loading=Načítání...
|
|
||||||
addToDoc=Přidat do dokumentu
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Politika soukromí
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Podmínky použití
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Snaha o přístupnost
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Zásada cookies
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Odborné prohlášení
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -99,7 +96,7 @@ pipeline.defaultOption=Vlastní
|
|||||||
pipeline.submitButton=Odeslat
|
pipeline.submitButton=Odeslat
|
||||||
pipeline.help=Pomoc s pipeline
|
pipeline.help=Pomoc s pipeline
|
||||||
pipeline.scanHelp=Pomoc se skenováním adresáře
|
pipeline.scanHelp=Pomoc se skenováním adresáře
|
||||||
pipeline.deletePrompt=Opravdu chcete smazat tento kanál?
|
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Pipeline Options #
|
# Pipeline Options #
|
||||||
@@ -117,21 +114,21 @@ pipelineOptions.validateButton=Ověřit
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Upgradujte na Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Tato funkce je k dispozici pouze pro uživatelé s předplatným Pro.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro podporuje konfigurační soubory YAML a další funkce SSO.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Hledáte větší počet správních funkcí uživatelů? Podívejte se na Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Chcete-li zlepšit Stirling PDF, pomozte nám.
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF má povolené analýzy pro to, abychom mohli zlepšovat produkt. Nezaznamenáváme žádné osobní informace nebo obsah souborů.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Považte za možnost povolení analýz k tomu, abychom mohli růst Stirling-PDF a lépe pochopit naši skupinu uživatelů.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Zapnout analýzy
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Vypnout analýzy
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Můžete změnit nastavení pro analýzy v souboru config/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +139,13 @@ navbar.language=Jazyky
|
|||||||
navbar.settings=Nastavení
|
navbar.settings=Nastavení
|
||||||
navbar.allTools=Nástroje
|
navbar.allTools=Nástroje
|
||||||
navbar.multiTool=Multifunkční nástroje
|
navbar.multiTool=Multifunkční nástroje
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Organizovat
|
navbar.sections.organize=Organizovat
|
||||||
navbar.sections.convertTo=Převést do PDF
|
navbar.sections.convertTo=Převést do PDF
|
||||||
navbar.sections.convertFrom=Převést z PDF
|
navbar.sections.convertFrom=Převést z PDF
|
||||||
navbar.sections.security=Podpis a Bezpečnost
|
navbar.sections.security=Podpis a Bezpečnost
|
||||||
navbar.sections.advance=Pokročilé
|
navbar.sections.advance=Pokročilé
|
||||||
navbar.sections.edit=Prohlédnout a Upravit
|
navbar.sections.edit=Prohlédnout a Upravit
|
||||||
navbar.sections.popular=Populární
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -166,7 +162,7 @@ settings.zipThreshold=Zazipuj soubory, když překročí počet stažených soub
|
|||||||
settings.signOut=Odhlásit
|
settings.signOut=Odhlásit
|
||||||
settings.accountSettings=Nastavení Účtu
|
settings.accountSettings=Nastavení Účtu
|
||||||
settings.bored.help=Umožňuje hru s easter eggy
|
settings.bored.help=Umožňuje hru s easter eggy
|
||||||
settings.cacheInputs.name=Ukládání vstupů formuláře
|
settings.cacheInputs.name=Save form inputs
|
||||||
settings.cacheInputs.help=Umožňuje uložit dříve použité vstupy pro budoucí použití
|
settings.cacheInputs.help=Umožňuje uložit dříve použité vstupy pro budoucí použití
|
||||||
|
|
||||||
changeCreds.title=Změnit pověření
|
changeCreds.title=Změnit pověření
|
||||||
@@ -202,13 +198,13 @@ account.syncToAccount=Synchronizovat Účet <- Prohlížeč
|
|||||||
|
|
||||||
|
|
||||||
adminUserSettings.title=Nastavení Uživatelského Nastavení
|
adminUserSettings.title=Nastavení Uživatelského Nastavení
|
||||||
adminUserSettings.header=Nastavení správce uživatelů
|
adminUserSettings.header=Admin User Control Settings
|
||||||
adminUserSettings.admin=Správce
|
adminUserSettings.admin=Admin
|
||||||
adminUserSettings.user=Uživatel
|
adminUserSettings.user=Uživatel
|
||||||
adminUserSettings.addUser=Přidat Nového Uživatele
|
adminUserSettings.addUser=Přidat Nového Uživatele
|
||||||
adminUserSettings.deleteUser=Smazat uživatele
|
adminUserSettings.deleteUser=Delete User
|
||||||
adminUserSettings.confirmDeleteUser=Měli by se uživatel smazat?
|
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||||
adminUserSettings.confirmChangeUserStatus=Měli by se změnit stav uživatele (vytřída/aktivace)?
|
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||||
adminUserSettings.usernameInfo=Uživatelské Jméno může obsahovat pouze písmena, čísla a následující speciální znaky @._+- nebo musí být správná emailová adresa.
|
adminUserSettings.usernameInfo=Uživatelské Jméno může obsahovat pouze písmena, čísla a následující speciální znaky @._+- nebo musí být správná emailová adresa.
|
||||||
adminUserSettings.roles=Role
|
adminUserSettings.roles=Role
|
||||||
adminUserSettings.role=Role
|
adminUserSettings.role=Role
|
||||||
@@ -222,33 +218,32 @@ adminUserSettings.forceChange=Vynutit uživateli změnu hesla při přihlášen
|
|||||||
adminUserSettings.submit=Uložit Uživatele
|
adminUserSettings.submit=Uložit Uživatele
|
||||||
adminUserSettings.changeUserRole=Zmenit Roli Uživatele
|
adminUserSettings.changeUserRole=Zmenit Roli Uživatele
|
||||||
adminUserSettings.authenticated=Ověřeno
|
adminUserSettings.authenticated=Ověřeno
|
||||||
adminUserSettings.editOwnProfil=Upravit vlastní profil
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=povolený uživatel
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=zakázáný uživatel
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=Aktivní uživatelé:
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=Zakázанные uživatelé:
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=Celkem uživatelů:
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=Poslední žádost
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=Import/Export databáze
|
database.title=Database Import/Export
|
||||||
database.header=Import/Export databáze
|
database.header=Database Import/Export
|
||||||
database.fileName=Název souboru
|
database.fileName=File Name
|
||||||
database.creationDate=Datum vytvoření
|
database.creationDate=Creation Date
|
||||||
database.fileSize=Velikost souboru
|
database.fileSize=File Size
|
||||||
database.deleteBackupFile=Smazat záložní soubor
|
database.deleteBackupFile=Delete Backup File
|
||||||
database.importBackupFile=Import zálohy souboru
|
database.importBackupFile=Import Backup File
|
||||||
database.downloadBackupFile=Stáhnout zálohový soubor
|
database.downloadBackupFile=Download Backup File
|
||||||
database.info_1=Při importu dat je důležité zajistit správnou strukturu. Pokud jste nejistí, jak se chovat, hledajte konzultaci a podporu od profesionala. Chyba v struktuře může vést k selhání aplikace, dokonce i k tomu, že by aplikace nemohla být spuštěna.
|
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=Název souboru nezáleží při nahrávání. Bude jeho zpětně znovu pojmenován podle formáту backup_user_yyyyMMddHHmm.sql, což zajišťuje konzistentní pravidlo označení.
|
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 zálohy
|
database.submit=Import Backup
|
||||||
database.importIntoDatabaseSuccessed=Import do databáze byl úspěšný
|
database.importIntoDatabaseSuccessed=Import into database successed
|
||||||
database.fileNotFound=File not Found
|
database.fileNotFound=File not Found
|
||||||
database.fileNullOrEmpty=Soubor nemůže být null nebo prázdný
|
database.fileNullOrEmpty=File must not be null or empty
|
||||||
database.failedImportFile=Failed Import File
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Vaše sesace vypršela. Prosím obnovte stránku a zkusit to znovu.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -352,7 +347,7 @@ PDFToPresentation.tags=snímky,přehled,kancelář,microsoft
|
|||||||
|
|
||||||
home.PDFToText.title=PDF na RTF (Text)
|
home.PDFToText.title=PDF na RTF (Text)
|
||||||
home.PDFToText.desc=Převod PDF do formátu Textu nebo RTF
|
home.PDFToText.desc=Převod PDF do formátu Textu nebo RTF
|
||||||
PDFToText.tags=bohatý formátování, bohaté formátování, bohatej formátkace textu
|
PDFToText.tags=richformat,richtextformat,rich text format
|
||||||
|
|
||||||
home.PDFToHTML.title=PDF na HTML
|
home.PDFToHTML.title=PDF na HTML
|
||||||
home.PDFToHTML.desc=Převod PDF do formátu HTML
|
home.PDFToHTML.desc=Převod PDF do formátu HTML
|
||||||
@@ -395,9 +390,9 @@ home.certSign.title=Podpis s certifikátem
|
|||||||
home.certSign.desc=Podpis PDF s certifikátem/klíčem (PEM/P12)
|
home.certSign.desc=Podpis PDF s certifikátem/klíčem (PEM/P12)
|
||||||
certSign.tags=autentizace,PEM,P12,oficiální,šifrování
|
certSign.tags=autentizace,PEM,P12,oficiální,šifrování
|
||||||
|
|
||||||
home.removeCertSign.title=Odstranit certifikátovou podepsanost
|
home.removeCertSign.title=Remove Certificate Sign
|
||||||
home.removeCertSign.desc=Odstranit certifikátovou podepsanost z PDF
|
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||||
removeCertSign.tags=autentizace, PEM, P12, úřední, dešifrování
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Vícestránkové rozložení
|
home.pageLayout.title=Vícestránkové rozložení
|
||||||
home.pageLayout.desc=Sloučení více stránek dokumentu PDF do jedné stránky
|
home.pageLayout.desc=Sloučení více stránek dokumentu PDF do jedné stránky
|
||||||
@@ -503,33 +498,33 @@ home.BookToPDF.title=Kniha na PDF
|
|||||||
home.BookToPDF.desc=Převádí formáty knih/komiksů do PDF pomocí calibre
|
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
|
BookToPDF.tags=Kniha,Komiks,Calibre,Konvertovat,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.removeImagePdf.title=Odstranit obrázek
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=Odstranit obrázek z PDF k snížení velikosti souboru
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Odstranit obrázek, operace na stránkách, záložní strana, serverové čidla
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Rozdělit PDF podle kapitol
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Rozdělit PDF do více souborů na základě jeho struktury kapitol.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=rozdělení, kapitoly, zápisky, organizace
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Nahradit inverzní barvu PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Replace and Invert Color
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Nahradit barvy pro text a pozadí v PDF a inverzní celý barvový spektrum PDF k snižení velikosti souboru
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Nahrazovat barvu, operace na stránkách, záložní strana, serverové čidla
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Možnosti nahrazení nebo inverze barev
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Výchozí (vysoká kontrastová barva)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Vlastní (vlastní barvy)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Celé inverzní (inverzní všechny barvy)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Možnosti vysoké kontrastové barvy
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Bílá text na černém pozadí
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Černý text na bílé pozadí
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Zlutý text na černém pozadí
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Zelený text na černém pozadí
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Vyberte barvu textu
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Vyberte barvu pozadí
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Nahradit
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -539,45 +534,46 @@ replace-color.submit=Nahradit
|
|||||||
# #
|
# #
|
||||||
###########################
|
###########################
|
||||||
#login
|
#login
|
||||||
login.title=Přihlášení
|
login.title=Sign in
|
||||||
login.header=Přihlášení
|
login.header=Sign in
|
||||||
login.signin=Přihlásit se
|
login.signin=Sign in
|
||||||
login.rememberme=Zapamatovat si mě
|
login.rememberme=Remember me
|
||||||
login.invalid=Neplatné uživatelské jméno nebo heslo.
|
login.invalid=Invalid username or password.
|
||||||
login.locked=Vaše účto bylo zablokováno.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Prosím, přihlaste se
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Přihlášení prostřednictvím jednotného přihlašovacího systému
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Automatické vytvoření uživatele OAUTH2 je vypnuté
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
login.oauth2AdminBlockedUser=Registrace nebo přihlášení nepozitrovených uživatelů aktuálně jsou zablokovaná. Štěňte na správce.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Požadavek na autorizaci nenalezen
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Neplatný odpověď s informacemi o uživateli
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Neplatný požadavek
|
login.oauth2invalidRequest=Invalid Request
|
||||||
login.oauth2AccessDenied=Přístup zazobán
|
login.oauth2AccessDenied=Access Denied
|
||||||
login.oauth2InvalidTokenResponse=Neplatná odpověď tokenu
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
login.oauth2InvalidIdToken=Neplatný identifikační token
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
login.userIsDisabled=Uživatel je deaktivován, přihlášení aktuálně pro tuto uživatelskou jmena je zakázáno. Kontaktujte správce.
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
login.alreadyLoggedIn=Jste již přihlášeni na
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=zariadení. Odhlašujte se z těchto zařízení a zkuste to znovu.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Máte příliš mnoho aktivních sesií
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatické smazání
|
autoRedact.title=Auto Redact
|
||||||
autoRedact.header=Automatické smazání
|
autoRedact.header=Auto Redact
|
||||||
autoRedact.colorLabel=Barva
|
autoRedact.colorLabel=Colour
|
||||||
autoRedact.textsToRedactLabel=Text k smazání (řádkově oddělený)
|
autoRedact.textsToRedactLabel=Text to Redact (line-separated)
|
||||||
autoRedact.textsToRedactPlaceholder=např. \nKonfidenciální \nSkrytější
|
autoRedact.textsToRedactPlaceholder=e.g. \nConfidential \nTop-Secret
|
||||||
autoRedact.useRegexLabel=Použít regulární výraz
|
autoRedact.useRegexLabel=Use Regex
|
||||||
autoRedact.wholeWordSearchLabel=Hledání celých slov
|
autoRedact.wholeWordSearchLabel=Whole Word Search
|
||||||
autoRedact.customPaddingLabel=Vlastní doplňující vzdálenost
|
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||||
autoRedact.convertPDFToImageLabel=Převést PDF do PDF-Obrázku (Pro odstranění textu za obdélníkem)
|
autoRedact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box)
|
||||||
autoRedact.submitButton=Odeslat
|
autoRedact.submitButton=Submit
|
||||||
|
|
||||||
|
|
||||||
#showJS
|
#showJS
|
||||||
showJS.title=Zobrazit JavaScript
|
showJS.title=Show Javascript
|
||||||
showJS.header=Zobrazit JavaScript
|
showJS.header=Show Javascript
|
||||||
showJS.downloadJS=Stáhnout JavaScript
|
showJS.downloadJS=Download Javascript
|
||||||
showJS.submit=Zobrazit
|
showJS.submit=Show
|
||||||
|
|
||||||
|
|
||||||
#pdfToSinglePage
|
#pdfToSinglePage
|
||||||
@@ -733,7 +729,7 @@ pageLayout.submit=Odeslat
|
|||||||
scalePages.title=Upravit měřítko stránky
|
scalePages.title=Upravit měřítko stránky
|
||||||
scalePages.header=Upravit měřítko stránky
|
scalePages.header=Upravit měřítko stránky
|
||||||
scalePages.pageSize=Velikost stránky dokumentu.
|
scalePages.pageSize=Velikost stránky dokumentu.
|
||||||
scalePages.keepPageSize=Původní velikost
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=Úroveň přiblížení (oříznutí) stránky.
|
scalePages.scaleFactor=Úroveň přiblížení (oříznutí) stránky.
|
||||||
scalePages.submit=Odeslat
|
scalePages.submit=Odeslat
|
||||||
|
|
||||||
@@ -753,15 +749,14 @@ certSign.showSig=Ukázat podpis
|
|||||||
certSign.reason=Důvod
|
certSign.reason=Důvod
|
||||||
certSign.location=Umístění
|
certSign.location=Umístění
|
||||||
certSign.name=Název
|
certSign.name=Název
|
||||||
certSign.showLogo=Zobraz loga
|
|
||||||
certSign.submit=Podepsat PDF
|
certSign.submit=Podepsat PDF
|
||||||
|
|
||||||
|
|
||||||
#removeCertSign
|
#removeCertSign
|
||||||
removeCertSign.title=Odstranit certifikátovou podpisu
|
removeCertSign.title=Remove Certificate Signature
|
||||||
removeCertSign.header=Odstranění digitálního certifikátu z PDF
|
removeCertSign.header=Remove the digital certificate from the PDF
|
||||||
removeCertSign.selectPDF=Vyberte soubor PDF:
|
removeCertSign.selectPDF=Select a PDF file:
|
||||||
removeCertSign.submit=Odstranit podpis
|
removeCertSign.submit=Remove Signature
|
||||||
|
|
||||||
|
|
||||||
#removeBlanks
|
#removeBlanks
|
||||||
@@ -783,14 +778,11 @@ removeAnnotations.submit=Odebrat
|
|||||||
#compare
|
#compare
|
||||||
compare.title=Porovnat
|
compare.title=Porovnat
|
||||||
compare.header=Porovnat PDF
|
compare.header=Porovnat PDF
|
||||||
compare.highlightColor.1=Podtržovací barva 1:
|
compare.highlightColor.1=Highlight Color 1:
|
||||||
compare.highlightColor.2=Podtržovací barva 2:
|
compare.highlightColor.2=Highlight Color 2:
|
||||||
compare.document.1=Dokument 1
|
compare.document.1=Dokument 1
|
||||||
compare.document.2=Dokument 2
|
compare.document.2=Dokument 2
|
||||||
compare.submit=Porovnat
|
compare.submit=Porovnat
|
||||||
compare.complex.message=Jedno nebo oba z předložených dokumentů jsou velké soubory, přesnost porovnání může být snižena
|
|
||||||
compare.large.file.message=Jeden nebo oba předložené dokumenty jsou příliš velké na zpracování
|
|
||||||
compare.no.text.message=Jedno nebo oba vybrané PDF soubory nemají textový obsah. Zvolte PDF soubory se textem pro porovnání.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Knihy a komiksy do PDF
|
BookToPDF.title=Knihy a komiksy do PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Nakreslit podpis
|
|||||||
sign.text=Vstup textu
|
sign.text=Vstup textu
|
||||||
sign.clear=Vymazat
|
sign.clear=Vymazat
|
||||||
sign.add=Přidat
|
sign.add=Přidat
|
||||||
sign.saved=Uložené podpisy
|
|
||||||
sign.save=Uložit podpis
|
|
||||||
sign.personalSigs=Osobní podpisy
|
|
||||||
sign.sharedSigs=Sdílené podpisy
|
|
||||||
sign.noSavedSigs=Nenašly se žádné uložené podpisy
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -836,7 +823,7 @@ flatten.submit=Zploštit
|
|||||||
#ScannerImageSplit
|
#ScannerImageSplit
|
||||||
ScannerImageSplit.selectText.1=Práh úhlu:
|
ScannerImageSplit.selectText.1=Práh úhlu:
|
||||||
ScannerImageSplit.selectText.2=Nastaví minimální absolutní úhel, který je vyžadován k otočení obrázku (výchozí: 10).
|
ScannerImageSplit.selectText.2=Nastaví minimální absolutní úhel, který je vyžadován k otočení obrázku (výchozí: 10).
|
||||||
ScannerImageSplit.selectText.3=Tolerancie:
|
ScannerImageSplit.selectText.3=Tolerance:
|
||||||
ScannerImageSplit.selectText.4=Určuje rozsah barevné variace kolem odhadované barvy pozadí (výchozí: 30).
|
ScannerImageSplit.selectText.4=Určuje rozsah barevné variace kolem odhadované barvy pozadí (výchozí: 30).
|
||||||
ScannerImageSplit.selectText.5=Minimální plocha:
|
ScannerImageSplit.selectText.5=Minimální plocha:
|
||||||
ScannerImageSplit.selectText.6=Nastaví minimální plošný práh pro fotografii (výchozí: 10000).
|
ScannerImageSplit.selectText.6=Nastaví minimální plošný práh pro fotografii (výchozí: 10000).
|
||||||
@@ -844,7 +831,7 @@ ScannerImageSplit.selectText.7=Minimální plocha kontury:
|
|||||||
ScannerImageSplit.selectText.8=Nastaví minimální plošný práh kontury pro fotografii
|
ScannerImageSplit.selectText.8=Nastaví minimální plošný práh kontury pro fotografii
|
||||||
ScannerImageSplit.selectText.9=Velikost okraje:
|
ScannerImageSplit.selectText.9=Velikost okraje:
|
||||||
ScannerImageSplit.selectText.10=Nastaví velikost okraje přidaného a odebraného k zabránění bílých ohraničení ve výstupu (výchozí: 1).
|
ScannerImageSplit.selectText.10=Nastaví velikost okraje přidaného a odebraného k zabránění bílých ohraničení ve výstupu (výchozí: 1).
|
||||||
ScannerImageSplit.info=Python není nainstalován. Je potřeba pro jeho spuštění.
|
ScannerImageSplit.info=Python is not installed. It is required to run.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -871,7 +858,7 @@ ocr.submit=Zpracovat PDF s OCR
|
|||||||
extractImages.title=Extrahovat obrázky
|
extractImages.title=Extrahovat obrázky
|
||||||
extractImages.header=Extrahovat obrázky
|
extractImages.header=Extrahovat obrázky
|
||||||
extractImages.selectText=Vyberte formát obrázku pro extrahované obrázky
|
extractImages.selectText=Vyberte formát obrázku pro extrahované obrázky
|
||||||
extractImages.allowDuplicates=Uložit duplikátní obrázky
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=Extrahovat
|
extractImages.submit=Extrahovat
|
||||||
|
|
||||||
|
|
||||||
@@ -909,7 +896,7 @@ merge.title=Sloučit
|
|||||||
merge.header=Sloučit více PDF (2+)
|
merge.header=Sloučit více PDF (2+)
|
||||||
merge.sortByName=Seřadit podle názvu
|
merge.sortByName=Seřadit podle názvu
|
||||||
merge.sortByDate=Seřadit podle data
|
merge.sortByDate=Seřadit podle data
|
||||||
merge.removeCertSign=Odebrat digitální podpis v převedeném souboru?
|
merge.removeCertSign=Remove digital signature in the merged file?
|
||||||
merge.submit=Sloučit
|
merge.submit=Sloučit
|
||||||
|
|
||||||
|
|
||||||
@@ -927,7 +914,7 @@ pdfOrganiser.mode.6=Liché-Sudé rozdělení
|
|||||||
pdfOrganiser.mode.7=Odstranit první
|
pdfOrganiser.mode.7=Odstranit první
|
||||||
pdfOrganiser.mode.8=Odstranit poslední
|
pdfOrganiser.mode.8=Odstranit poslední
|
||||||
pdfOrganiser.mode.9=Odstranit první a poslední
|
pdfOrganiser.mode.9=Odstranit první a poslední
|
||||||
pdfOrganiser.mode.10=Lomeno spojení
|
pdfOrganiser.mode.10=Odd-Even Merge
|
||||||
pdfOrganiser.placeholder=(např. 1,3,2 nebo 4-8,2,10-12 nebo 2n-1)
|
pdfOrganiser.placeholder=(např. 1,3,2 nebo 4-8,2,10-12 nebo 2n-1)
|
||||||
|
|
||||||
|
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(např. 1,3,2 nebo 4-8,2,10-12 nebo 2n-1)
|
|||||||
multiTool.title=Vícefunkční nástroj pro PDF
|
multiTool.title=Vícefunkční nástroj pro PDF
|
||||||
multiTool.header=Vícefunkční nástroj pro PDF
|
multiTool.header=Vícefunkční nástroj pro PDF
|
||||||
multiTool.uploadPrompts=Název souboru
|
multiTool.uploadPrompts=Název souboru
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=Zobrazit PDF
|
viewPdf.title=Zobrazit PDF
|
||||||
@@ -1007,7 +983,7 @@ pdfToImage.color=Barevný
|
|||||||
pdfToImage.grey=Stupně šedi
|
pdfToImage.grey=Stupně šedi
|
||||||
pdfToImage.blackwhite=Černobílý (Může dojít k ztrátě dat!)
|
pdfToImage.blackwhite=Černobílý (Může dojít k ztrátě dat!)
|
||||||
pdfToImage.submit=Převést
|
pdfToImage.submit=Převést
|
||||||
pdfToImage.info=Python není nainstalován. Potřebuje se pro konverzi do WebP.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1044,7 +1020,7 @@ watermark.selectText.6=Výška mezery (Mezera mezi každým vodoznakem svisle):
|
|||||||
watermark.selectText.7=Průhlednost (0% - 100%):
|
watermark.selectText.7=Průhlednost (0% - 100%):
|
||||||
watermark.selectText.8=Typ vodoznaku:
|
watermark.selectText.8=Typ vodoznaku:
|
||||||
watermark.selectText.9=Obrázek vodoznaku:
|
watermark.selectText.9=Obrázek vodoznaku:
|
||||||
watermark.selectText.10=Převést PDF na PDF-Obrázek
|
watermark.selectText.10=Convert PDF to PDF-Image
|
||||||
watermark.submit=Přidat vodoznak
|
watermark.submit=Přidat vodoznak
|
||||||
watermark.type.1=Text
|
watermark.type.1=Text
|
||||||
watermark.type.2=Obrázek
|
watermark.type.2=Obrázek
|
||||||
@@ -1101,7 +1077,7 @@ pdfToPDFA.credit=Tato služba používá ghostscript pro konverzi do formátu PD
|
|||||||
pdfToPDFA.submit=Převést
|
pdfToPDFA.submit=Převést
|
||||||
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
||||||
pdfToPDFA.outputFormat=Výstupní formát
|
pdfToPDFA.outputFormat=Výstupní formát
|
||||||
pdfToPDFA.pdfWithDigitalSignature=PDF obsahuje digitální podpis, který bude odebrán v následujícím kroku.
|
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@@ -1203,15 +1179,15 @@ licenses.version=Verze
|
|||||||
licenses.license=Licence
|
licenses.license=Licence
|
||||||
|
|
||||||
#survey
|
#survey
|
||||||
survey.nav=Přehled
|
survey.nav=Survey
|
||||||
survey.title=Stirling-PDF Přehled
|
survey.title=Stirling-PDF Survey
|
||||||
survey.description=Stirling-PDF nemá sledování, proto chceme vaše názory k podnětom k lepšímu Stirling-PDF!
|
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF se změnil od posledního příspěvku! Další informace najdete na našem blogu zde:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=S těmito změnami získáváme platné podpory a finanční podporu od business partnerů.
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Please consider taking our survey!
|
survey.please=Please consider taking our survey!
|
||||||
survey.disabled=(Přehled bude v budoucnu zakázán, ale zůstane k dispozici na nástavbě)
|
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||||
survey.button=Udělat Příspěvek
|
survey.button=Take Survey
|
||||||
survey.dontShowAgain=Neukazuj to znovu
|
survey.dontShowAgain=Don't show again
|
||||||
|
|
||||||
|
|
||||||
#error
|
#error
|
||||||
@@ -1229,19 +1205,21 @@ error.discordSubmit=Discord - Odeslat příspěvek podpory
|
|||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=Odstranit obrázek
|
removeImage.title=Remove image
|
||||||
removeImage.header=Odstranit obrázek
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=Odstranit obrázek
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=Odebrat obrázek
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
|
splitByChapters.title=Split PDF by Chapters
|
||||||
|
splitByChapters.header=Split PDF by Chapters
|
||||||
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Podělit PDF podle kapitol
|
|
||||||
splitByChapters.header=Podělení PDF na kapitoly
|
|
||||||
splitByChapters.bookmarkLevel=Úroveň záhlaví
|
|
||||||
splitByChapters.includeMetadata=Zahrnout metadatů
|
|
||||||
splitByChapters.allowDuplicates=Povolit duplicitní záznamy
|
|
||||||
splitByChapters.desc.1=Tento nástroj podělí PDF soubor na více PDF soubory na základě struktury kapitol.
|
|
||||||
splitByChapters.desc.2=Úroveň záhlaví: Zvolte úroveň záhlaví pro použití při oddělování (0 pro hlavní, 1 pro druhou úroveň atd.).
|
|
||||||
splitByChapters.desc.3=Zahrnout metadatů: Pokud je zaškrtnuto, původní metadata PDF souboru budou zahrnuty do každého odděleného PDF souboru.
|
|
||||||
splitByChapters.desc.4=Povolit duplicitní záznamy: Pokud je zaškrtnuto, návštěvníci mohou vytvořit samostatné PDF soubory z více záhlaví na stejné straně.
|
|
||||||
splitByChapters.submit=Podělit se PDF
|
|
||||||
|
|||||||
@@ -74,20 +74,17 @@ seeDockerHub=Se Docker Hub
|
|||||||
visitGithub=Besøg Github Repository
|
visitGithub=Besøg Github Repository
|
||||||
donate=Donér
|
donate=Donér
|
||||||
color=Farve
|
color=Farve
|
||||||
sponsor=Sponsorer
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Sidenummer
|
page=Page
|
||||||
pages=Sideantal
|
pages=Pages
|
||||||
loading=Laster...
|
|
||||||
addToDoc=Tilføj til Dokument
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Vilkår og betingelser
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Adgangsnævnteglen
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Cokiebelejring
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Angivelse af ansvar
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -117,21 +114,21 @@ pipelineOptions.validateButton=Validér
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Opgrader til Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Denne funktion er kun tilgængelig for Pro-brugere.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro understøtter YAML-konfigurationsfiler og andre SSO-funktioner.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=søger du flere funktioner til brugerstyring? Prøv Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Vil du gøre Stirling PDF bedre?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF har indsat analytics for at hjælpe os med at forbedre produktet. Vi følger ikke nogen personoplysninger eller filinhold.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Bevægelsesmæssigt aktiver du analytics for at hjælpe Stirling-PDF med at vokse og til atstå os bedre at forstå vores brugere.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Aktivér analytics
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Deaktiver analytics
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Du kan ændre analytics-indstillingerne i config/settings.yml-filen
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +139,13 @@ navbar.language=Sprog
|
|||||||
navbar.settings=Indstillinger
|
navbar.settings=Indstillinger
|
||||||
navbar.allTools=Værktøjer
|
navbar.allTools=Værktøjer
|
||||||
navbar.multiTool=Multi Værktøjer
|
navbar.multiTool=Multi Værktøjer
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Organisér
|
navbar.sections.organize=Organisér
|
||||||
navbar.sections.convertTo=Konvertér til PDF
|
navbar.sections.convertTo=Konvertér til PDF
|
||||||
navbar.sections.convertFrom=Konvertér fra PDF
|
navbar.sections.convertFrom=Konvertér fra PDF
|
||||||
navbar.sections.security=Signér & Sikkerhed
|
navbar.sections.security=Signér & Sikkerhed
|
||||||
navbar.sections.advance=Avanceret
|
navbar.sections.advance=Avanceret
|
||||||
navbar.sections.edit=Vis & Redigér
|
navbar.sections.edit=Vis & Redigér
|
||||||
navbar.sections.popular=Populære
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -203,7 +199,7 @@ account.syncToAccount=Synkroniser Konto <- Browser
|
|||||||
|
|
||||||
adminUserSettings.title=Brugerkontrolindstillinger
|
adminUserSettings.title=Brugerkontrolindstillinger
|
||||||
adminUserSettings.header=Admin Brugerkontrolindstillinger
|
adminUserSettings.header=Admin Brugerkontrolindstillinger
|
||||||
adminUserSettings.admin=Administrer
|
adminUserSettings.admin=Admin
|
||||||
adminUserSettings.user=Bruger
|
adminUserSettings.user=Bruger
|
||||||
adminUserSettings.addUser=Tilføj Ny Bruger
|
adminUserSettings.addUser=Tilføj Ny Bruger
|
||||||
adminUserSettings.deleteUser=Slet Bruger
|
adminUserSettings.deleteUser=Slet Bruger
|
||||||
@@ -247,8 +243,7 @@ database.fileNotFound=Fil ikke fundet
|
|||||||
database.fileNullOrEmpty=Fil må ikke være null eller tom
|
database.fileNullOrEmpty=Fil må ikke være null eller tom
|
||||||
database.failedImportFile=Kunne ikke importere fil
|
database.failedImportFile=Kunne ikke importere fil
|
||||||
|
|
||||||
session.expired=Din sesions tid har udløbet. Genlad siden og prøv igen.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -508,28 +503,28 @@ home.removeImagePdf.desc=Fjern billede fra PDF for at reducere filstørrelse
|
|||||||
removeImagePdf.tags=Fjern Billede,Sideoperationer,Back end,server side
|
removeImagePdf.tags=Fjern Billede,Sideoperationer,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Partitioner PDF efter kapitler
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Partitioner en PDF i flere filer baseret på dens kapitelstruktur.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=partitionering,kapitler,merker,organisering
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Erstat-omgivende Farve PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Replace and Invert Color
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Erstatt farve for tekst og baggrund i en PDF og omgivende farve til fuld farve af PDF for at redusere filstørrelsen.
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Erstat Farve,Side operationer,Behandling,server side
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Erstatt eller omgivende Farvemuligheder
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Standard (høj kontrastfarver)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Brugerdefineret (anpassede farver)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Inverter alle farver
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Høj kontrastfarveindstillinger
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Hvid tekst på sort baggrund
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Sort tekst på hvid baggrund
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Gul tekst på sort baggrund
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Grøn tekst på sort baggrund
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Vælg tekstfarve
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Vælg baggrundsfarve
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Erstat
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -556,9 +551,10 @@ login.oauth2AccessDenied=Adgang Nægtet
|
|||||||
login.oauth2InvalidTokenResponse=Ugyldigt Token Svar
|
login.oauth2InvalidTokenResponse=Ugyldigt Token Svar
|
||||||
login.oauth2InvalidIdToken=Ugyldigt Id Token
|
login.oauth2InvalidIdToken=Ugyldigt Id Token
|
||||||
login.userIsDisabled=Bruger er deaktiveret, login er i øjeblikket blokeret med dette brugernavn. Kontakt venligst administratoren.
|
login.userIsDisabled=Bruger er deaktiveret, login er i øjeblikket blokeret med dette brugernavn. Kontakt venligst administratoren.
|
||||||
login.alreadyLoggedIn=Du er allerede logget ind på
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=enheder. Log ud af disse enheder og prøv igen.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Du har for mange aktive sessoner
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Rediger
|
autoRedact.title=Auto Rediger
|
||||||
@@ -576,7 +572,7 @@ autoRedact.submitButton=Indsend
|
|||||||
#showJS
|
#showJS
|
||||||
showJS.title=Vis Javascript
|
showJS.title=Vis Javascript
|
||||||
showJS.header=Vis Javascript
|
showJS.header=Vis Javascript
|
||||||
showJS.downloadJS=Last ned Javascript
|
showJS.downloadJS=Download Javascript
|
||||||
showJS.submit=Vis
|
showJS.submit=Vis
|
||||||
|
|
||||||
|
|
||||||
@@ -633,7 +629,7 @@ HTMLToPDF.printBackground=Render baggrunden af hjemmesider.
|
|||||||
HTMLToPDF.defaultHeader=Aktivér Standard Header (Navn og sidenummerAS
|
HTMLToPDF.defaultHeader=Aktivér Standard Header (Navn og sidenummerAS
|
||||||
HTMLToPDF.cssMediaType=Ændre CSS-medietypen for siden.
|
HTMLToPDF.cssMediaType=Ændre CSS-medietypen for siden.
|
||||||
HTMLToPDF.none=Ingen
|
HTMLToPDF.none=Ingen
|
||||||
HTMLToPDF.print=Skriv ud
|
HTMLToPDF.print=Print
|
||||||
HTMLToPDF.screen=Skærm
|
HTMLToPDF.screen=Skærm
|
||||||
|
|
||||||
|
|
||||||
@@ -645,9 +641,9 @@ AddStampRequest.stampText=Stempeltekst
|
|||||||
AddStampRequest.stampImage=Stempelbillede
|
AddStampRequest.stampImage=Stempelbillede
|
||||||
AddStampRequest.alphabet=Alfabet
|
AddStampRequest.alphabet=Alfabet
|
||||||
AddStampRequest.fontSize=Skrift/Billedstørrelse
|
AddStampRequest.fontSize=Skrift/Billedstørrelse
|
||||||
AddStampRequest.rotation=Vendelse
|
AddStampRequest.rotation=Rotation
|
||||||
AddStampRequest.opacity=Gennemsigtighed
|
AddStampRequest.opacity=Gennemsigtighed
|
||||||
AddStampRequest.position=Plassering
|
AddStampRequest.position=Position
|
||||||
AddStampRequest.overrideX=Tilsidesæt X-koordinat
|
AddStampRequest.overrideX=Tilsidesæt X-koordinat
|
||||||
AddStampRequest.overrideY=Tilsidesæt Y-koordinat
|
AddStampRequest.overrideY=Tilsidesæt Y-koordinat
|
||||||
AddStampRequest.customMargin=Brugerdefineret Margin
|
AddStampRequest.customMargin=Brugerdefineret Margin
|
||||||
@@ -671,7 +667,7 @@ addPageNumbers.title=Tilføj Sidenumre
|
|||||||
addPageNumbers.header=Tilføj Sidenumre
|
addPageNumbers.header=Tilføj Sidenumre
|
||||||
addPageNumbers.selectText.1=Vælg PDF-fil:
|
addPageNumbers.selectText.1=Vælg PDF-fil:
|
||||||
addPageNumbers.selectText.2=Marginstørrelse
|
addPageNumbers.selectText.2=Marginstørrelse
|
||||||
addPageNumbers.selectText.3=Plassering
|
addPageNumbers.selectText.3=Position
|
||||||
addPageNumbers.selectText.4=Startnummer
|
addPageNumbers.selectText.4=Startnummer
|
||||||
addPageNumbers.selectText.5=Sider at nummerere
|
addPageNumbers.selectText.5=Sider at nummerere
|
||||||
addPageNumbers.selectText.6=Brugerdefineret Tekst
|
addPageNumbers.selectText.6=Brugerdefineret Tekst
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=Vis Underskrift
|
|||||||
certSign.reason=Årsag
|
certSign.reason=Årsag
|
||||||
certSign.location=Placering
|
certSign.location=Placering
|
||||||
certSign.name=Navn
|
certSign.name=Navn
|
||||||
certSign.showLogo=Vis Logo
|
|
||||||
certSign.submit=Underskriv PDF
|
certSign.submit=Underskriv PDF
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=Fremhævningsfarve 2:
|
|||||||
compare.document.1=Dokument 1
|
compare.document.1=Dokument 1
|
||||||
compare.document.2=Dokument 2
|
compare.document.2=Dokument 2
|
||||||
compare.submit=Sammenlign
|
compare.submit=Sammenlign
|
||||||
compare.complex.message=Et eller begge af de angivne dokumenter er store filer, præcisionen ved sammenligningen kan geminse.
|
|
||||||
compare.large.file.message=Et eller Begge af de Angivne Dokumenter Er For Store At Behandle
|
|
||||||
compare.no.text.message=Et eller Begge Af de Vælgede PDFs Har Ingen Tekstindhold. Vælg Vores PDFs Med Tekst for Sammenligning.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Bøger og Tegneserier til PDF
|
BookToPDF.title=Bøger og Tegneserier til PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Tegn Underskrift
|
|||||||
sign.text=Tekstinput
|
sign.text=Tekstinput
|
||||||
sign.clear=Ryd
|
sign.clear=Ryd
|
||||||
sign.add=Tilføj
|
sign.add=Tilføj
|
||||||
sign.saved=Gemte Signaturer
|
|
||||||
sign.save=Gem Signatur
|
|
||||||
sign.personalSigs=Personlige Signaturer
|
|
||||||
sign.sharedSigs=Delte Signaturer
|
|
||||||
sign.noSavedSigs=Ingen Gemte Signaturer Fundet
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -836,7 +823,7 @@ flatten.submit=Udjævn
|
|||||||
#ScannerImageSplit
|
#ScannerImageSplit
|
||||||
ScannerImageSplit.selectText.1=Vinkeltærskel:
|
ScannerImageSplit.selectText.1=Vinkeltærskel:
|
||||||
ScannerImageSplit.selectText.2=Indstiller den minimale absolutte vinkel, der kræves for at billedet roteres (standard: 10).
|
ScannerImageSplit.selectText.2=Indstiller den minimale absolutte vinkel, der kræves for at billedet roteres (standard: 10).
|
||||||
ScannerImageSplit.selectText.3=Tolerancen:
|
ScannerImageSplit.selectText.3=Tolerance:
|
||||||
ScannerImageSplit.selectText.4=Bestemmer området for farvevariation omkring den estimerede baggrundsfarve (standard: 30).
|
ScannerImageSplit.selectText.4=Bestemmer området for farvevariation omkring den estimerede baggrundsfarve (standard: 30).
|
||||||
ScannerImageSplit.selectText.5=Minimum Areal:
|
ScannerImageSplit.selectText.5=Minimum Areal:
|
||||||
ScannerImageSplit.selectText.6=Indstiller den minimale arealtærskel for et foto (standard: 10000).
|
ScannerImageSplit.selectText.6=Indstiller den minimale arealtærskel for et foto (standard: 10000).
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(f.eks. 1,3,2 eller 4-8,2,10-12 eller 2n-1)
|
|||||||
multiTool.title=PDF Multi Værktøj
|
multiTool.title=PDF Multi Værktøj
|
||||||
multiTool.header=PDF Multi Værktøj
|
multiTool.header=PDF Multi Værktøj
|
||||||
multiTool.uploadPrompts=Filnavn
|
multiTool.uploadPrompts=Filnavn
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=Se PDF
|
viewPdf.title=Se PDF
|
||||||
@@ -1038,7 +1014,7 @@ watermark.header=Tilføj Vandmærke
|
|||||||
watermark.selectText.1=Vælg PDF til at tilføje vandmærke:
|
watermark.selectText.1=Vælg PDF til at tilføje vandmærke:
|
||||||
watermark.selectText.2=Vandmærketekst:
|
watermark.selectText.2=Vandmærketekst:
|
||||||
watermark.selectText.3=Skriftstørrelse:
|
watermark.selectText.3=Skriftstørrelse:
|
||||||
watermark.selectText.4=Vendt Side (0-360):
|
watermark.selectText.4=Rotation (0-360):
|
||||||
watermark.selectText.5=breddeAfstand (Afstand mellem hvert vandmærke vandret):
|
watermark.selectText.5=breddeAfstand (Afstand mellem hvert vandmærke vandret):
|
||||||
watermark.selectText.6=højdeAfstand (Afstand mellem hvert vandmærke lodret):
|
watermark.selectText.6=højdeAfstand (Afstand mellem hvert vandmærke lodret):
|
||||||
watermark.selectText.7=Gennemsigtighed (0% - 100%):
|
watermark.selectText.7=Gennemsigtighed (0% - 100%):
|
||||||
@@ -1206,8 +1182,8 @@ licenses.license=License
|
|||||||
survey.nav=Undersøgelse
|
survey.nav=Undersøgelse
|
||||||
survey.title=Stirling-PDF Undersøgelse
|
survey.title=Stirling-PDF Undersøgelse
|
||||||
survey.description=Stirling-PDF har ingen sporing, så vi vil gerne høre fra vores brugere for at forbedre Stirling-PDF!
|
survey.description=Stirling-PDF har ingen sporing, så vi vil gerne høre fra vores brugere for at forbedre Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF Har Endtes Sidst Ganger du Foresatte En Kig! For At Lære Mere, Se Vores Blog Indlæg Her:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Med Disse Endringer Er Vi Kommet I Betalende Forretningsstøtte og Finansiering
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Overvej venligst at deltage i vores undersøgelse!
|
survey.please=Overvej venligst at deltage i vores undersøgelse!
|
||||||
survey.disabled=(Undersøgelsespop-up vil blive deaktiveret i følgende opdateringer, men vil være tilgængelig i bunden af siden)
|
survey.disabled=(Undersøgelsespop-up vil blive deaktiveret i følgende opdateringer, men vil være tilgængelig i bunden af siden)
|
||||||
survey.button=Tag Undersøgelsen
|
survey.button=Tag Undersøgelsen
|
||||||
@@ -1235,13 +1211,15 @@ removeImage.removeImage=Fjern billede
|
|||||||
removeImage.submit=Fjern
|
removeImage.submit=Fjern
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Del PDF ved Kapitler
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=Splitter PDF efter kapitel
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Bogmærke niveau
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Inkluder metadata
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Tillad duplikater
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Denne værktøj splitter en PDF-fil op i flere PDF'er baseret på dens kapitelstruktur.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Bogmærke niveau: Vælg nivået af bogmærker, der skal bruges til at splittere (0 for hovedniveau, 1 for anden niveau osv.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Inkluder metadata: Hvis markeret, vil den originale PDF's metadata inkluderes i hver splitterdels PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Tillad duplikater: Hvis markeret, tillader det flere bogmærker på samme side til at oprette separate PDF'er.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Splitter PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Schriftgröße
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Schriftart
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=PDF(s) auswählen
|
pdfPrompt=PDF(s) auswählen
|
||||||
multiPdfPrompt=PDFs auswählen(2+)
|
multiPdfPrompt=PDFs auswählen(2+)
|
||||||
multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)
|
multiPdfDropPrompt=Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)
|
||||||
@@ -77,11 +77,8 @@ color=Farbe
|
|||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Informationen
|
info=Informationen
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Seite
|
page=Page
|
||||||
pages=Seiten
|
pages=Pages
|
||||||
loading=Laden...
|
|
||||||
addToDoc=In Dokument hinzufügen
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Datenschutz
|
legal.privacy=Datenschutz
|
||||||
legal.terms=AGB
|
legal.terms=AGB
|
||||||
@@ -117,21 +114,21 @@ pipelineOptions.validateButton=Validieren
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Auf Pro-Version umsteigen
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Diese Funktion ist nur für Pro-Nutzer verfügbar.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling-PDF Pro unterstützt YAML Konfigurationsdateien, SSO und weitere Funktionen.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Suchen Sie weitere Funktionen in der Benutzerverwaltung? Steigen Sie auf die Pro-Version um
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Möchten Sie Stirling-PDF verbessern?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling-PDF verfügt über Opt-in-Analytics, die uns helfen, das Produkt zu verbessern. Wir zeichnen keine persönlichen Informationen oder Dateiinhalte auf.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Bitte erwägen Sie die Analytics zu aktivieren, um Stirling-PDF beim Wachsen zu helfen und um unsere User besser zu verstehen.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Analytics aktivieren
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Analytics deaktivieren
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Sie können die Einstellungen für die Analytics in der config/settings.yml Datei bearbeiten
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +139,13 @@ navbar.language=Sprachen
|
|||||||
navbar.settings=Einstellungen
|
navbar.settings=Einstellungen
|
||||||
navbar.allTools=Werkzeuge
|
navbar.allTools=Werkzeuge
|
||||||
navbar.multiTool=Multitools
|
navbar.multiTool=Multitools
|
||||||
navbar.search=Suche
|
|
||||||
navbar.sections.organize=Organisieren
|
navbar.sections.organize=Organisieren
|
||||||
navbar.sections.convertTo=In PDF konvertieren
|
navbar.sections.convertTo=In PDF konvertieren
|
||||||
navbar.sections.convertFrom=Konvertieren von PDF
|
navbar.sections.convertFrom=Konvertieren von PDF
|
||||||
navbar.sections.security=Zeichen und Sicherheit
|
navbar.sections.security=Zeichen und Sicherheit
|
||||||
navbar.sections.advance=Fortschrittlich
|
navbar.sections.advance=Fortschrittlich
|
||||||
navbar.sections.edit=Anzeigen und Bearbeiten
|
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||||
navbar.sections.popular=Beliebt
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -247,8 +243,7 @@ database.fileNotFound=Datei nicht gefunden
|
|||||||
database.fileNullOrEmpty=Datei darf nicht null oder leer sein
|
database.fileNullOrEmpty=Datei darf nicht null oder leer sein
|
||||||
database.failedImportFile=Dateiimport fehlgeschlagen
|
database.failedImportFile=Dateiimport fehlgeschlagen
|
||||||
|
|
||||||
session.expired=Ihre Sitzung ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -397,7 +392,7 @@ certSign.tags=authentifizieren,pem,p12,offiziell,verschlüsseln
|
|||||||
|
|
||||||
home.removeCertSign.title=Zertifikatsignatur entfernen
|
home.removeCertSign.title=Zertifikatsignatur entfernen
|
||||||
home.removeCertSign.desc=Zertifikatsignatur aus PDF entfernen
|
home.removeCertSign.desc=Zertifikatsignatur aus PDF entfernen
|
||||||
removeCertSign.tags=authentifizieren,PEM,P12,offiziell,entschlüsseln
|
removeCertSign.tags=authentifizieren,PEM,P12,offiziell,entschlüsseln,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Mehrseitiges Layout
|
home.pageLayout.title=Mehrseitiges Layout
|
||||||
home.pageLayout.desc=Mehrere Seiten eines PDF zu einer Seite zusammenführen
|
home.pageLayout.desc=Mehrere Seiten eines PDF zu einer Seite zusammenführen
|
||||||
@@ -508,28 +503,28 @@ home.removeImagePdf.desc=Bild aus PDF entfernen, um die Dateigröße zu verringe
|
|||||||
removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side
|
removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=PDF-Datei nach Kapiteln aufteilen
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Aufteilung einer PDF-Datei in mehrere Dateien auf Basis der Kapitelstruktur.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=aufteilen,kapitel,lesezeichen,organisieren
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Farbe Ersetzen-Invertieren
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Farb-PDF Ersetzen-Invertieren
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Farbe ersetzen und invertieren
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Ersetzen Sie die Farbe des Texts und Hintergrund der PDF-Datei und invertieren Sie die komplette Farbe der PDF-Datei, um die Dateigröße zu reduzieren
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Farbe ersetzen,Seiteneinstellungen,Backend,Serverseite
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Ersetzen oder Invertieren von Farboptionen
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Standard(Standardfarben mit hohem Kontrast)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Benutzerdefiniert(Benutzerdefinierte Farben)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Vollinvertierung(Invertierung aller Farben)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Farboptionen mit hohem Kontrast
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Weißer Text auf schwarzem Hintergrund
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Schwarzer Text auf weißem Hintergrund
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Gelber Text auf schwarzem Hintergrund
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Grüner Text auf schwarzem Hintergrund
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Textfarbe auswählen
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Hintergrundfarbe auswählen
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Ersetzen
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -556,9 +551,10 @@ login.oauth2AccessDenied=Zugriff abgelehnt
|
|||||||
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
|
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
|
||||||
login.oauth2InvalidIdToken=Ungültiges ID-Token
|
login.oauth2InvalidIdToken=Ungültiges ID-Token
|
||||||
login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
|
login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
|
||||||
login.alreadyLoggedIn=Sie sind bereits an
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Sie haben zu viele aktive Sitzungen
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatisch zensieren/schwärzen
|
autoRedact.title=Automatisch zensieren/schwärzen
|
||||||
@@ -708,7 +704,7 @@ autoSplitPDF.header=PDF automatisch teilen
|
|||||||
autoSplitPDF.description=Drucken Sie, fügen Sie ein, scannen Sie, laden Sie hoch und lassen Sie uns Ihre Dokumente automatisch trennen. Kein manuelles Sortieren erforderlich.
|
autoSplitPDF.description=Drucken Sie, fügen Sie ein, scannen Sie, laden Sie hoch und lassen Sie uns Ihre Dokumente automatisch trennen. Kein manuelles Sortieren erforderlich.
|
||||||
autoSplitPDF.selectText.1=Drucken Sie einige Trennblätter aus (schwarz/weiß ist ausreichend).
|
autoSplitPDF.selectText.1=Drucken Sie einige Trennblätter aus (schwarz/weiß ist ausreichend).
|
||||||
autoSplitPDF.selectText.2=Scannen Sie alle Dokumente auf einmal, indem Sie das Trennblatt zwischen die Dokumente einlegen.
|
autoSplitPDF.selectText.2=Scannen Sie alle Dokumente auf einmal, indem Sie das Trennblatt zwischen die Dokumente einlegen.
|
||||||
autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling-PDF den Rest.
|
autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling PDF den Rest.
|
||||||
autoSplitPDF.selectText.4=Trennseiten werden automatisch erkannt und entfernt, so dass ein sauberes Enddokument garantiert ist.
|
autoSplitPDF.selectText.4=Trennseiten werden automatisch erkannt und entfernt, so dass ein sauberes Enddokument garantiert ist.
|
||||||
autoSplitPDF.formPrompt=PDF mit Stirling-PDF Seitentrennern hochladen:
|
autoSplitPDF.formPrompt=PDF mit Stirling-PDF Seitentrennern hochladen:
|
||||||
autoSplitPDF.duplexMode=Duplex-Modus (Scannen von Vorder- und Rückseite)
|
autoSplitPDF.duplexMode=Duplex-Modus (Scannen von Vorder- und Rückseite)
|
||||||
@@ -733,7 +729,7 @@ pageLayout.submit=Abschicken
|
|||||||
scalePages.title=Seitengröße anpassen
|
scalePages.title=Seitengröße anpassen
|
||||||
scalePages.header=Seitengröße anpassen
|
scalePages.header=Seitengröße anpassen
|
||||||
scalePages.pageSize=Format der Seiten des Dokuments
|
scalePages.pageSize=Format der Seiten des Dokuments
|
||||||
scalePages.keepPageSize=Originalgröße beibehalten
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite
|
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite
|
||||||
scalePages.submit=Abschicken
|
scalePages.submit=Abschicken
|
||||||
|
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=Signatur anzeigen
|
|||||||
certSign.reason=Grund
|
certSign.reason=Grund
|
||||||
certSign.location=Standort
|
certSign.location=Standort
|
||||||
certSign.name=Name
|
certSign.name=Name
|
||||||
certSign.showLogo=Logo anzeigen
|
|
||||||
certSign.submit=PDF signieren
|
certSign.submit=PDF signieren
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=Highlight-Farbe 2:
|
|||||||
compare.document.1=Dokument 1
|
compare.document.1=Dokument 1
|
||||||
compare.document.2=Dokument 2
|
compare.document.2=Dokument 2
|
||||||
compare.submit=Vergleichen
|
compare.submit=Vergleichen
|
||||||
compare.complex.message=Eines oder beide Dokumente sind sehr groß, dadurch kann die Genauigkeit des Vergleichs kann beeinträchtigt werden.
|
|
||||||
compare.large.file.message=Eines oder beide Dokumente sind zu groß, um verarbeitet zu werden
|
|
||||||
compare.no.text.message=Ein oder beide ausgewählten PDFs enthalten keine Textinhalt. Wählen Sie bitte PDFs mit Text für die Vergleichsanalyse.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Bücher und Comics zu PDF
|
BookToPDF.title=Bücher und Comics zu PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Signatur zeichnen
|
|||||||
sign.text=Texteingabe
|
sign.text=Texteingabe
|
||||||
sign.clear=Leeren
|
sign.clear=Leeren
|
||||||
sign.add=Signieren
|
sign.add=Signieren
|
||||||
sign.saved=Gespeicherte Signaturen
|
|
||||||
sign.save=Signature speichern
|
|
||||||
sign.personalSigs=Persönliche Signaturen
|
|
||||||
sign.sharedSigs=Geteilte Signaturen
|
|
||||||
sign.noSavedSigs=Es wurden keine gespeicherten Signaturen gefunden
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -871,7 +858,7 @@ ocr.submit=PDF mit OCR verarbeiten
|
|||||||
extractImages.title=Bilder extrahieren
|
extractImages.title=Bilder extrahieren
|
||||||
extractImages.header=Bilder extrahieren
|
extractImages.header=Bilder extrahieren
|
||||||
extractImages.selectText=Wählen Sie das Bildformat aus, in das extrahierte Bilder konvertiert werden sollen
|
extractImages.selectText=Wählen Sie das Bildformat aus, in das extrahierte Bilder konvertiert werden sollen
|
||||||
extractImages.allowDuplicates=Doppelte Bilder speichern
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=Extrahieren
|
extractImages.submit=Extrahieren
|
||||||
|
|
||||||
|
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(z.B. 1,3,2 oder 4-8,2,10-12 oder 2n-1)
|
|||||||
multiTool.title=PDF-Multitool
|
multiTool.title=PDF-Multitool
|
||||||
multiTool.header=PDF-Multitool
|
multiTool.header=PDF-Multitool
|
||||||
multiTool.uploadPrompts=Dateiname
|
multiTool.uploadPrompts=Dateiname
|
||||||
multiTool.selectAll=Alle auswählen
|
|
||||||
multiTool.deselectAll=Auswahl aufheben
|
|
||||||
multiTool.selectPages=Seiten auswählen
|
|
||||||
multiTool.selectedPages=Ausgewählte Seiten
|
|
||||||
multiTool.page=Seite
|
|
||||||
multiTool.deleteSelected=Auswahl löschen
|
|
||||||
multiTool.downloadAll=Downloaden
|
|
||||||
multiTool.downloadSelected=Auswahl downloaden
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=PDF anzeigen
|
viewPdf.title=PDF anzeigen
|
||||||
@@ -1206,8 +1182,8 @@ licenses.license=Lizenz
|
|||||||
survey.nav=Umfrage
|
survey.nav=Umfrage
|
||||||
survey.title=Stirling-PDF-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.description=Stirling-PDF hat kein Tracking, daher möchten wir von unseren Benutzern hören, wie wir Stirling-PDF verbessern können!
|
||||||
survey.changes=Stirling-PDF hat sich seit der letzten Umfrage verändert! Mehr Informationen finden Sie bitte in unserem Blog-Beitrag hier:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Mit diesen Änderungen erhalten wir beauftragte Geschäftsunterstützung und Finanzierung
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Bitte nehmen Sie an unserer Umfrage teil!
|
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.disabled=(Das Umfrage-Popup wird in folgenden Updates deaktiviert, ist aber am Fuß der Seite verfügbar.)
|
||||||
survey.button=Umfrage durchführen
|
survey.button=Umfrage durchführen
|
||||||
@@ -1235,13 +1211,15 @@ removeImage.removeImage=Bild entfernen
|
|||||||
removeImage.submit=Bild entfernen
|
removeImage.submit=Bild entfernen
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=PDF nach Kapiteln aufteilen
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=PDF nach Kapiteln aufteilen
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Lesezeichenebene
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Metadaten einschließen
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Duplikate erlauben
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Dieses Werkzeug teilt eine PDF-Datei auf der Grundlage ihrer Kapitelstruktur in mehrere PDF-Dateien auf.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Lesezeichenebene: Wählen Sie die Ebene der Lesezeichen, die für die Aufteilung verwendet werden soll (0 für die erste Ebene, 1 für die zweite Ebene usw.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Metadaten einschließen: Wenn diese Option aktiviert ist, werden die Metadaten der ursprünglichen PDF-Datei in jede aufgeteilte PDF-Datei übernommen.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Duplikate erlauben: Wenn diese Option aktiviert ist, können mehrere Lesezeichen auf derselben Seite separate PDF Dateien erstellen.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=PDF teilen
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Μέγεθος Τύπου
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Όνομα Τύπου
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=Επιλογή PDF(s)
|
pdfPrompt=Επιλογή PDF(s)
|
||||||
multiPdfPrompt=Επιλογή PDFs (2+)
|
multiPdfPrompt=Επιλογή PDFs (2+)
|
||||||
multiPdfDropPrompt=Επιλογή (ή τράβηγμα αρχείου και απόθεση) όλων των PDF που χρειάζεστε
|
multiPdfDropPrompt=Επιλογή (ή τράβηγμα αρχείου και απόθεση) όλων των PDF που χρειάζεστε
|
||||||
@@ -39,7 +39,7 @@ delete=Διαγραφή
|
|||||||
username=Όνομα Χρήστη
|
username=Όνομα Χρήστη
|
||||||
password=Κωδικός
|
password=Κωδικός
|
||||||
welcome=Καλως Ήλθατε
|
welcome=Καλως Ήλθατε
|
||||||
property=Χαρακτηριστικό
|
property=Property
|
||||||
black=Μαύρο
|
black=Μαύρο
|
||||||
white=Άσπρο
|
white=Άσπρο
|
||||||
red=Κόκκινο
|
red=Κόκκινο
|
||||||
@@ -56,15 +56,15 @@ userNotFoundMessage=Ο χρήστης δεν βρέθηκε.
|
|||||||
incorrectPasswordMessage=Ο τρέχων κωδικός πρόσβασης είναι λανθασμένος.
|
incorrectPasswordMessage=Ο τρέχων κωδικός πρόσβασης είναι λανθασμένος.
|
||||||
usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
|
usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
|
||||||
invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, όνομα χρήστη μπορεί να περιέχει μόνο γράμματα, αριθμούς και τους ακόλουθους ειδικούς χαρακτήρες @._+- ή πρέπει να είναι έγκυρη διεύθυνση email.
|
invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, όνομα χρήστη μπορεί να περιέχει μόνο γράμματα, αριθμούς και τους ακόλουθους ειδικούς χαρακτήρες @._+- ή πρέπει να είναι έγκυρη διεύθυνση email.
|
||||||
invalidPasswordMessage=Ο κωδικός πρόσβασης δεν μπορεί να είναι αδύναμος και δεν μπορεί να έχει χαμογελάτα στην αρχή ή το τέλος.
|
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||||
confirmPasswordErrorMessage=Ο Διαβεβαιωμένος Νέος Κωδικός Πρόσβασης πρέπει να συνάρτησε με το Νέο Κωδίκο Πρόσβασης.
|
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
|
||||||
deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
|
deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
|
||||||
deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
|
deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
|
||||||
downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
|
downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
|
||||||
disabledCurrentUserMessage=Ο σύγχρονος χρήστης δεν μπορεί να απενειλθείται
|
disabledCurrentUserMessage=The current user cannot be disabled
|
||||||
downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
|
downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
|
||||||
userAlreadyExistsOAuthMessage=Το υχής ήδη υπάρχει ως OAuth2 χρήστης.
|
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
||||||
userAlreadyExistsWebMessage=Ο χρήστης εξαρτάται ήδη ως πλήρωμα χρήστη.
|
userAlreadyExistsWebMessage=The user already exists as an web user.
|
||||||
error=Σφάλμα
|
error=Σφάλμα
|
||||||
oops=Ωχ!
|
oops=Ωχ!
|
||||||
help=Βοήθεια
|
help=Βοήθεια
|
||||||
@@ -75,31 +75,28 @@ visitGithub=Επισκεφθείτε το Αποθετήριο του Github
|
|||||||
donate=Δωρισε
|
donate=Δωρισε
|
||||||
color=Χρώμα
|
color=Χρώμα
|
||||||
sponsor=Yποστηρικτής
|
sponsor=Yποστηρικτής
|
||||||
info=Πληροφορίες
|
info=Info
|
||||||
pro=Προ
|
pro=Pro
|
||||||
page=Σελίδα
|
page=Page
|
||||||
pages=Σελίδες
|
pages=Pages
|
||||||
loading=Φόρτωση...
|
|
||||||
addToDoc=Πρόσθεση στο Εκπομπώματο
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Πολιτική Προνομίους
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Φράσεις Υποχρεωτικότητας
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Πρόσβαση
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Πολιτική Χώρου Συνθέσεων
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Κατάσταση
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
###############
|
###############
|
||||||
pipeline.header=Μενού Pipeline (Beta)
|
pipeline.header=Μενού Pipeline (Beta)
|
||||||
pipeline.uploadButton=Αναβάθμιση Καλυτερού
|
pipeline.uploadButton=Upload Custom
|
||||||
pipeline.configureButton=Διαμόρφωσε
|
pipeline.configureButton=Διαμόρφωσε
|
||||||
pipeline.defaultOption=Κατασκευή Μετρόπολης
|
pipeline.defaultOption=Custom
|
||||||
pipeline.submitButton=Υποβολή
|
pipeline.submitButton=Υποβολή
|
||||||
pipeline.help=Βοήθεια για το Pipeline
|
pipeline.help=Βοήθεια για το Pipeline
|
||||||
pipeline.scanHelp=Βοήθεια για Σάρωση Φακέλων
|
pipeline.scanHelp=Βοήθεια για Σάρωση Φακέλων
|
||||||
pipeline.deletePrompt=Είστε σίγουροι ότι θέλετε να διαγράψετε το πρότυπο;
|
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Pipeline Options #
|
# Pipeline Options #
|
||||||
@@ -110,53 +107,52 @@ pipelineOptions.saveSettings=Αποθήκευση Ρυθμίσεων Λειτο
|
|||||||
pipelineOptions.pipelineNamePrompt=Εισαγάγετε το όνομα του pipeline εδώ
|
pipelineOptions.pipelineNamePrompt=Εισαγάγετε το όνομα του pipeline εδώ
|
||||||
pipelineOptions.selectOperation=Επιλέξτε Λειτουργία
|
pipelineOptions.selectOperation=Επιλέξτε Λειτουργία
|
||||||
pipelineOptions.addOperationButton=Προσθήκη λειτουργίας
|
pipelineOptions.addOperationButton=Προσθήκη λειτουργίας
|
||||||
pipelineOptions.pipelineHeader=Πρότυπο:
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
pipelineOptions.saveButton=Λήψη
|
pipelineOptions.saveButton=Λήψη
|
||||||
pipelineOptions.validateButton=Επικυρώνω
|
pipelineOptions.validateButton=Επικυρώνω
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Ενημερώστε σε Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Αυτή η λειτουργία είναι διαθέσιμη μόνο για τους χρήστες Pro.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro υποστηρίζει αρχεία συνασκήματος YAML και άλλες λειτουργίες SSO.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Σάξεις για περισσότερες λειτουργίες διαχείρισης χρηστών; Πроверьте Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Επιθυμείτε να βελτιώσετε το Stirling PDF;
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF έχει επιλεγμένα ανάλυμα για να με βοηθήσει στην βελτίωση του προϊόντος. Δεν ακολουθούμε καμία ιδιοχειρισμένη πληροφορία ή συγκέντρωση υπόλοιπου πλάνου.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Παρακαλώ ενεργοποιήστε τα ανάλυμα για να βοηθήσετε το Stirling-PDF να ξεφύγει και να μας βοηθήσετε στον καλύτερο δυνατό ανάλυμα χρηστών.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Ενεργοποίηση ανάλυμα
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Διακόπτωση ανάλυμα
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Μπορείτε να αλλάξετε τις προπαραστάσεις των ανάλυμα σε υποθέσεις/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
#############
|
#############
|
||||||
navbar.favorite=Πιρημός
|
navbar.favorite=Favorites
|
||||||
navbar.darkmode=Μαύρο Θέμα
|
navbar.darkmode=Μαύρο Θέμα
|
||||||
navbar.language=Γλώσσες
|
navbar.language=Languages
|
||||||
navbar.settings=Ρυθμίσεις
|
navbar.settings=Ρυθμίσεις
|
||||||
navbar.allTools=Εργαλεία
|
navbar.allTools=Tools
|
||||||
navbar.multiTool=Multi Tools
|
navbar.multiTool=Multi Tools
|
||||||
navbar.search=Search
|
navbar.sections.organize=Organize
|
||||||
navbar.sections.organize=Οργάνωση
|
navbar.sections.convertTo=Convert to PDF
|
||||||
navbar.sections.convertTo=Μετατροπή σε PDF
|
navbar.sections.convertFrom=Convert from PDF
|
||||||
navbar.sections.convertFrom=Μετατροπή από PDF
|
navbar.sections.security=Sign & Security
|
||||||
navbar.sections.security=Υπογραφή & Σέκυνση
|
navbar.sections.advance=Advanced
|
||||||
navbar.sections.advance=Διεξοδικό
|
navbar.sections.edit=View & Edit
|
||||||
navbar.sections.edit=Σύντομος Προβολής & Διοίκηση
|
navbar.sections.popular=Popular
|
||||||
navbar.sections.popular=Επιφανειακά
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
#############
|
#############
|
||||||
settings.title=Ρυθμίσεις
|
settings.title=Ρυθμίσεις
|
||||||
settings.update=Υπάρχει διαθέσιμη ενημέρωση
|
settings.update=Υπάρχει διαθέσιμη ενημέρωση
|
||||||
settings.updateAvailable={0} είναι το πρόγραμμα εκτελεσιμωμένο σήμερα. Έχετε διαθέσιμη μια νέα έκδοση (σήμερα: {1}).
|
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||||
settings.appVersion=Έκδοση εφαρμογής:
|
settings.appVersion=Έκδοση εφαρμογής:
|
||||||
settings.downloadOption.title=Επιλέξετε την επιλογή λήψης (Για λήψεις μεμονωμένων αρχείων χωρίς zip):
|
settings.downloadOption.title=Επιλέξετε την επιλογή λήψης (Για λήψεις μεμονωμένων αρχείων χωρίς zip):
|
||||||
settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο
|
settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο
|
||||||
@@ -165,9 +161,9 @@ settings.downloadOption.3=Λήψη αρχείου
|
|||||||
settings.zipThreshold=Αρχεία Zip όταν ο αριθμός των ληφθέντων αρχείων είναι πολύ μεγάλος
|
settings.zipThreshold=Αρχεία Zip όταν ο αριθμός των ληφθέντων αρχείων είναι πολύ μεγάλος
|
||||||
settings.signOut=Αποσύνδεση
|
settings.signOut=Αποσύνδεση
|
||||||
settings.accountSettings=Ρυθμίσεις Λογαριασμού
|
settings.accountSettings=Ρυθμίσεις Λογαριασμού
|
||||||
settings.bored.help=Ενεργοποίηση παιχνίδι ευφήμων
|
settings.bored.help=Enables easter egg game
|
||||||
settings.cacheInputs.name=Αποθήκευση των εισαγωγών
|
settings.cacheInputs.name=Save form inputs
|
||||||
settings.cacheInputs.help=Ενεργοποίηση για να αποθηκεύσετε προηγουμένως χρησιμοποιημένες εισαγωγές για το μέλλον
|
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||||
|
|
||||||
changeCreds.title=Αλλαγή Διαπιστευτηρίων
|
changeCreds.title=Αλλαγή Διαπιστευτηρίων
|
||||||
changeCreds.header=Ενημέρωση των λεπτομερειών του Λογαριασμού σας
|
changeCreds.header=Ενημέρωση των λεπτομερειών του Λογαριασμού σας
|
||||||
@@ -195,7 +191,7 @@ account.signOut=Αποσύνδεση
|
|||||||
account.yourApiKey=Το κλειδί σας για τη διεπαφή προγραμματισμού εφαρμογών (API key)
|
account.yourApiKey=Το κλειδί σας για τη διεπαφή προγραμματισμού εφαρμογών (API key)
|
||||||
account.syncTitle=Συγχρονισμός των ρυθμίσεων του φυλλομετρητή (Web Browser) με τον λογαριασμό
|
account.syncTitle=Συγχρονισμός των ρυθμίσεων του φυλλομετρητή (Web Browser) με τον λογαριασμό
|
||||||
account.settingsCompare=Σύγκριση Ρυθμίσεων:
|
account.settingsCompare=Σύγκριση Ρυθμίσεων:
|
||||||
account.property=Υπεροχή
|
account.property=Property
|
||||||
account.webBrowserSettings=Ρύθμιση φυλλομετρητή (Web Browser)
|
account.webBrowserSettings=Ρύθμιση φυλλομετρητή (Web Browser)
|
||||||
account.syncToBrowser=Συγχρονισμός Λογαριασμού -> Φυλλομετρητή (Web Browser)
|
account.syncToBrowser=Συγχρονισμός Λογαριασμού -> Φυλλομετρητή (Web Browser)
|
||||||
account.syncToAccount=Συγχρονισμός Λογαριασμού <- Φυλλομετρητή (Web Browser)
|
account.syncToAccount=Συγχρονισμός Λογαριασμού <- Φυλλομετρητή (Web Browser)
|
||||||
@@ -206,10 +202,10 @@ adminUserSettings.header=Ρυθμίσεις ελέγχου Διαχειριστ
|
|||||||
adminUserSettings.admin=Διαχειριστής
|
adminUserSettings.admin=Διαχειριστής
|
||||||
adminUserSettings.user=Χρήστης
|
adminUserSettings.user=Χρήστης
|
||||||
adminUserSettings.addUser=Προσθήκη νέου Χρήστη
|
adminUserSettings.addUser=Προσθήκη νέου Χρήστη
|
||||||
adminUserSettings.deleteUser=Διαγραφή Χρηστών
|
adminUserSettings.deleteUser=Delete User
|
||||||
adminUserSettings.confirmDeleteUser=Είναι αποδεκτό να διαγράψετε τον χρήστη;
|
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||||
adminUserSettings.confirmChangeUserStatus=Είναι αποδεκτό να αλλάξετε το σύμβαθρο του χρηστών;
|
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||||
adminUserSettings.usernameInfo=Το όνομα χρήστη μπορεί να περιέχει μόνο γράμματα, αριθμούς και τα εξής διευρυμένα συμβολικά @._+- ή πρέπει να είναι έγκυρο e-mail.
|
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
|
||||||
adminUserSettings.roles=Ρόλοι
|
adminUserSettings.roles=Ρόλοι
|
||||||
adminUserSettings.role=Ρόλος
|
adminUserSettings.role=Ρόλος
|
||||||
adminUserSettings.actions=Ενέργειες
|
adminUserSettings.actions=Ενέργειες
|
||||||
@@ -221,34 +217,33 @@ adminUserSettings.internalApiUser=Εσωτερικός API χρήστης
|
|||||||
adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση
|
adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση
|
||||||
adminUserSettings.submit=Αποθήκευση Χρήστη
|
adminUserSettings.submit=Αποθήκευση Χρήστη
|
||||||
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
|
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
|
||||||
adminUserSettings.authenticated=Εγγεγραφές
|
adminUserSettings.authenticated=Authenticated
|
||||||
adminUserSettings.editOwnProfil=Επεξεργασία Τοποθεσίας Δικότητας
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=Ενεργούς Χρήστης
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=Απενεργούς Χρήστης
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=Ενεργοί Χρήστες:
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=Απενεργοί Χρήστες:
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=Συνολικός Αριθμός Χρηστών:
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=Τελευταία Ερώτηση
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=Εισάγωντας/Εξαγωγή Δεδομένων Βάσης Δεδομένων
|
database.title=Database Import/Export
|
||||||
database.header=Εισάγωντας/Εξαγωγή Δεδομένων Βάσης Δεδομένων
|
database.header=Database Import/Export
|
||||||
database.fileName=Όνομα αρχείου
|
database.fileName=File Name
|
||||||
database.creationDate=Ημερομηνία δημιουργίας
|
database.creationDate=Creation Date
|
||||||
database.fileSize=Μέγεθος αρχείου
|
database.fileSize=File Size
|
||||||
database.deleteBackupFile=Διαγραφή Προγράμματος Ανασυγκρότησης
|
database.deleteBackupFile=Delete Backup File
|
||||||
database.importBackupFile=Εισάγωντας Προγράμματος Ανασυγκρότησης
|
database.importBackupFile=Import Backup File
|
||||||
database.downloadBackupFile=Κατέβασμα Προγράμματος Ανασυγκρότησης
|
database.downloadBackupFile=Download Backup File
|
||||||
database.info_1=Όταν εισάγετε δεδομένα, είναι σημαντικό να εξασφαλίσετε τη σωστή μορφοποίηση. Αν δεν είστε σίγουροι για το ποια πράγματα κάνετε, αιτήστε υποβολή και υποστήριξη από ευελίκτω. Μια σφάλμα στη μορφοποίηση μπορεί να προκαλέσει καταστάσεις λάθους στην εφαρμογή, αν όχι ολόκληρη την αποχώρηση της εφαρμογής.
|
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=Το ονόμα του αρχείου δεν έχει σημασία όταν ξεκινάτε μια επέμβαση. Αλλαγήται αργότερα για να ακολουθήσει το σχήμα backup_ωςώντας_YYYYMMDDHHmm.sql, επιτρέποντας μια συνεχές κατάληψη ονόματος.
|
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=Εισάγωντας Προγράμματος Ανασυγκρότησης
|
database.submit=Import Backup
|
||||||
database.importIntoDatabaseSuccessed=Τελείωση εισαγωγής στη βάση δεδομένων.
|
database.importIntoDatabaseSuccessed=Import into database successed
|
||||||
database.fileNotFound=File not Found
|
database.fileNotFound=File not Found
|
||||||
database.fileNullOrEmpty=Το αρχείο δεν μπορεί να είναι τυχόν ή κενό.
|
database.fileNullOrEmpty=File must not be null or empty
|
||||||
database.failedImportFile=Failed Import File
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Η σεζώνη σας υπάρξει παραγωγή. Πατήστε για να ανανεώσετε το πλήρωμα και δοκιμάστε ξανά.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -259,7 +254,7 @@ home.searchBar=Αναζήτηση για χαρακτηριστικά...
|
|||||||
|
|
||||||
home.viewPdf.title=Εμφάνιση PDF
|
home.viewPdf.title=Εμφάνιση PDF
|
||||||
home.viewPdf.desc=Εμφάνιση, προσθήκη σχεδίου, προσθήκη κειμένου ή εικόνων
|
home.viewPdf.desc=Εμφάνιση, προσθήκη σχεδίου, προσθήκη κειμένου ή εικόνων
|
||||||
viewPdf.tags=ψηφιοποίηση,ανάγνωση,καταχώρηση,τεκτωνική,εικόνα
|
viewPdf.tags=view,read,annotate,text,image
|
||||||
|
|
||||||
home.multiTool.title=PDF Πολυεργαλείο
|
home.multiTool.title=PDF Πολυεργαλείο
|
||||||
home.multiTool.desc=Συγχώνευση, Περιστροφή, Αναδιάταξη και Κατάργηση σελίδων
|
home.multiTool.desc=Συγχώνευση, Περιστροφή, Αναδιάταξη και Κατάργηση σελίδων
|
||||||
@@ -267,11 +262,11 @@ multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,in
|
|||||||
|
|
||||||
home.merge.title=Συγχώνευση
|
home.merge.title=Συγχώνευση
|
||||||
home.merge.desc=Συγχώνευση πολλών PDF σε ένα με εύκολο τρόπο.
|
home.merge.desc=Συγχώνευση πολλών PDF σε ένα με εύκολο τρόπο.
|
||||||
merge.tags=συνδυασμός,λεπτομέρειες σελίδας,μετάβαση από το μέχρι το κράτος,κρήτερο χρώμα
|
merge.tags=merge,Page operations,Back end,server side
|
||||||
|
|
||||||
home.split.title=Διαχωρισμός
|
home.split.title=Διαχωρισμός
|
||||||
home.split.desc=Διαχωρισμός των PDF σε πολλά έγγραφα.
|
home.split.desc=Διαχωρισμός των PDF σε πολλά έγγραφα.
|
||||||
split.tags=λεπτομέρειες σελίδας,διαχωρισμός,πολυσελίδια,κοπή,κρήτερο χρώμα
|
split.tags=Page operations,divide,Multi Page,cut,server side
|
||||||
|
|
||||||
home.rotate.title=Περιστροφή
|
home.rotate.title=Περιστροφή
|
||||||
home.rotate.desc=Περιστροφή των PDF σας με εύκολο τρόπο.
|
home.rotate.desc=Περιστροφή των PDF σας με εύκολο τρόπο.
|
||||||
@@ -280,45 +275,45 @@ rotate.tags=από την πλευρά του server
|
|||||||
|
|
||||||
home.imageToPdf.title=Εικόνα σε PDF
|
home.imageToPdf.title=Εικόνα σε PDF
|
||||||
home.imageToPdf.desc=Μετατροπή εικόνας (PNG, JPEG, GIF) σε PDF.
|
home.imageToPdf.desc=Μετατροπή εικόνας (PNG, JPEG, GIF) σε PDF.
|
||||||
imageToPdf.tags=διαδοχική ψηφιοποίηση,εικόνα,jpg,πληροφορίας,φωτογραφία
|
imageToPdf.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
home.pdfToImage.title=PDF σε εικόνα
|
home.pdfToImage.title=PDF σε εικόνα
|
||||||
home.pdfToImage.desc=Μετατροπή ενός PDF σε μία εικόνα. (PNG, JPEG, GIF)
|
home.pdfToImage.desc=Μετατροπή ενός PDF σε μία εικόνα. (PNG, JPEG, GIF)
|
||||||
pdfToImage.tags=διαδοχική ψηφιοποίηση,εικόνα,jpg,πληροφορίας,φωτογραφία
|
pdfToImage.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
home.pdfOrganiser.title=Οργάνωση
|
home.pdfOrganiser.title=Οργάνωση
|
||||||
home.pdfOrganiser.desc=Αφαίρεση/Αναδιάταξη σελίδων με οποιαδήποτε σειρά
|
home.pdfOrganiser.desc=Αφαίρεση/Αναδιάταξη σελίδων με οποιαδήποτε σειρά
|
||||||
pdfOrganiser.tags=διπλώματος,ήρωαι,ίσοδες,κατάταξη,μέταβαση
|
pdfOrganiser.tags=duplex,even,odd,sort,move
|
||||||
|
|
||||||
|
|
||||||
home.addImage.title=Προσθήκη Εικόνας
|
home.addImage.title=Προσθήκη Εικόνας
|
||||||
home.addImage.desc=Προσθήκη μιας εικόνας σε μια καθορισμένη θέση στο PDF
|
home.addImage.desc=Προσθήκη μιας εικόνας σε μια καθορισμένη θέση στο PDF
|
||||||
addImage.tags=εικόνα,γούστιο,φωτογραφία
|
addImage.tags=img,jpg,picture,photo
|
||||||
|
|
||||||
home.watermark.title=Προσθήκη Υδατογραφήματος
|
home.watermark.title=Προσθήκη Υδατογραφήματος
|
||||||
home.watermark.desc=Προσθήκη ενός προσαρμοσμένου υδατογράφηματος στο έγγραφό PDF.
|
home.watermark.desc=Προσθήκη ενός προσαρμοσμένου υδατογράφηματος στο έγγραφό PDF.
|
||||||
watermark.tags=λέξη-πίνακας,τυπώμενο,ετικέτα,δικαιοχειρισμός,διόρθωση,εικόνα,γούστιο,φωτογραφία
|
watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo
|
||||||
|
|
||||||
home.permissions.title=Αλλαγή Δικαιωμάτων
|
home.permissions.title=Αλλαγή Δικαιωμάτων
|
||||||
home.permissions.desc=Αλλαγή των Δικαιωμάτων στο έγγραφο PDF
|
home.permissions.desc=Αλλαγή των Δικαιωμάτων στο έγγραφο PDF
|
||||||
permissions.tags=αποκοπή,γραμμακτής,μετάβαση
|
permissions.tags=read,write,edit,print
|
||||||
|
|
||||||
|
|
||||||
home.removePages.title=Αφαίρεση
|
home.removePages.title=Αφαίρεση
|
||||||
home.removePages.desc=Αφαίρεση μή επιθυμητών σελίδων απο το έγγραφο PDF.
|
home.removePages.desc=Αφαίρεση μή επιθυμητών σελίδων απο το έγγραφο PDF.
|
||||||
removePages.tags=απόθεση σελίδων,διαγραφή σελίδων
|
removePages.tags=Remove pages,delete pages
|
||||||
|
|
||||||
home.addPassword.title=Προσθήκη κωδικού
|
home.addPassword.title=Προσθήκη κωδικού
|
||||||
home.addPassword.desc=Κρυπτογράφηση - κλείδωμα του PDF αρχείου με έναν κωδικό.
|
home.addPassword.desc=Κρυπτογράφηση - κλείδωμα του PDF αρχείου με έναν κωδικό.
|
||||||
addPassword.tags=σφραγίστω,σφραγίζομαι
|
addPassword.tags=secure,security
|
||||||
|
|
||||||
home.removePassword.title=Αφαίρεση Κωδικού
|
home.removePassword.title=Αφαίρεση Κωδικού
|
||||||
home.removePassword.desc=Κατάργήση της προστασίας με κωδικό πρόσβασης από το έγγραφο PDF.
|
home.removePassword.desc=Κατάργήση της προστασίας με κωδικό πρόσβασης από το έγγραφο PDF.
|
||||||
removePassword.tags=σφραγίστω,παρακυμπνώ,σφραγική,διαγραφή σφραγίδας
|
removePassword.tags=secure,Decrypt,security,unpassword,delete password
|
||||||
|
|
||||||
home.compressPdfs.title=Συμπίεση
|
home.compressPdfs.title=Συμπίεση
|
||||||
home.compressPdfs.desc=Συμπίεση των αρχείων PDF για την μείωση του μεγέθους τους.
|
home.compressPdfs.desc=Συμπίεση των αρχείων PDF για την μείωση του μεγέθους τους.
|
||||||
compressPdfs.tags=ελαφρύνση,κοντά,τροχιές
|
compressPdfs.tags=squish,small,tiny
|
||||||
|
|
||||||
|
|
||||||
home.changeMetadata.title=Αλλαγή Μεταδεδομένων
|
home.changeMetadata.title=Αλλαγή Μεταδεδομένων
|
||||||
@@ -331,16 +326,16 @@ fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,offi
|
|||||||
|
|
||||||
home.ocr.title=οπτική αναγνώριση χαρακτήρων (OCR) / Σαρώσεις Cleanup
|
home.ocr.title=οπτική αναγνώριση χαρακτήρων (OCR) / Σαρώσεις Cleanup
|
||||||
home.ocr.desc=Το Cleanup σαρώνει και ανιχνεύει κείμενο από εικόνες μέσα σε ένα PDF και το προσθέτει ξανά ως κείμενο
|
home.ocr.desc=Το Cleanup σαρώνει και ανιχνεύει κείμενο από εικόνες μέσα σε ένα PDF και το προσθέτει ξανά ως κείμενο
|
||||||
ocr.tags=γνώση,λέξεις,εικόνα,ζήτημα,ανίδηση,δοχή,πρόγραμμα γνωστικής ανάγνωσης
|
ocr.tags=recognition,text,image,scan,read,identify,detection,editable
|
||||||
|
|
||||||
|
|
||||||
home.extractImages.title=Εξαγωγή εικόνων
|
home.extractImages.title=Εξαγωγή εικόνων
|
||||||
home.extractImages.desc=Εξαγωγή όλων των εικόνων απο ένα PDF και αποθήκευση αυτών σε zip
|
home.extractImages.desc=Εξαγωγή όλων των εικόνων απο ένα PDF και αποθήκευση αυτών σε zip
|
||||||
extractImages.tags=εικόνα,φωτογραφία,θυλώματος,αρχείο,γραφή,παράδειγμα
|
extractImages.tags=picture,photo,save,archive,zip,capture,grab
|
||||||
|
|
||||||
home.pdfToPDFA.title=PDF σε PDF/A
|
home.pdfToPDFA.title=PDF σε PDF/A
|
||||||
home.pdfToPDFA.desc=Μετατροπή PDF σε PDF/A για μακροχρόνια αποθήκευση
|
home.pdfToPDFA.desc=Μετατροπή PDF σε PDF/A για μακροχρόνια αποθήκευση
|
||||||
pdfToPDFA.tags=θυλώματος,σύνολο,κανόνες,παραστατικό,κράτηση
|
pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation
|
||||||
|
|
||||||
home.PDFToWord.title=PDF σε Word
|
home.PDFToWord.title=PDF σε Word
|
||||||
home.PDFToWord.desc=Μετατροπή του PDF σε Word αρχείο (DOC, DOCX and ODT)
|
home.PDFToWord.desc=Μετατροπή του PDF σε Word αρχείο (DOC, DOCX and ODT)
|
||||||
@@ -356,24 +351,24 @@ PDFToText.tags=richformat,richtextformat,rich text format
|
|||||||
|
|
||||||
home.PDFToHTML.title=PDF σε HTML
|
home.PDFToHTML.title=PDF σε HTML
|
||||||
home.PDFToHTML.desc=Μετατροπή του PDF σε μορφή HTML
|
home.PDFToHTML.desc=Μετατροπή του PDF σε μορφή HTML
|
||||||
PDFToHTML.tags=πρόσβαση σε πλαίσιο,περιεχόμενα για διαδικτυακά
|
PDFToHTML.tags=web content,browser friendly
|
||||||
|
|
||||||
|
|
||||||
home.PDFToXML.title=PDF σε XML
|
home.PDFToXML.title=PDF σε XML
|
||||||
home.PDFToXML.desc=Μετατροπή του PDF σε μορφή XML
|
home.PDFToXML.desc=Μετατροπή του PDF σε μορφή XML
|
||||||
PDFToXML.tags=ξυστώση-δεδομένων,σύνθεση στρογγυλών εξισώσεων,κοντινή κατάληψη,πρόσβαση
|
PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert
|
||||||
|
|
||||||
home.ScannerImageSplit.title=Ανίχνευση/Διαίρεση σαρωμένων φωτογραφιών
|
home.ScannerImageSplit.title=Ανίχνευση/Διαίρεση σαρωμένων φωτογραφιών
|
||||||
home.ScannerImageSplit.desc=Διαχωρίσμός πολλών φωτογραφίών μέσα από μια φωτογραφία/PDF
|
home.ScannerImageSplit.desc=Διαχωρίσμός πολλών φωτογραφίών μέσα από μια φωτογραφία/PDF
|
||||||
ScannerImageSplit.tags=διχοτομία,δικτυακή-δοκιμασία,σκάνερις,θέση πλατύων,διαχώριση
|
ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize
|
||||||
|
|
||||||
home.sign.title=Υπογραφή
|
home.sign.title=Υπογραφή
|
||||||
home.sign.desc=Προσθήκη υπογραφής στο PDF με σχέδιο, κείμενο ή εικόνα.
|
home.sign.desc=Προσθήκη υπογραφής στο PDF με σχέδιο, κείμενο ή εικόνα.
|
||||||
sign.tags=αποθωμάσια,κατογώρηση,φωνή,χαρακτεία-υπογραφής
|
sign.tags=authorize,initials,drawn-signature,text-sign,image-signature
|
||||||
|
|
||||||
home.flatten.title=Συνοπτικοποίηση
|
home.flatten.title=Flatten
|
||||||
home.flatten.desc=Κατάργηση όλων των διαδραστικών στοιχείων και φορμών από ένα PDF
|
home.flatten.desc=Κατάργηση όλων των διαδραστικών στοιχείων και φορμών από ένα PDF
|
||||||
flatten.tags=εγχώριο,απενεργοποιήση,αδρανής-ενεργό,προσαρμογές
|
flatten.tags=static,deactivate,non-interactive,streamline
|
||||||
|
|
||||||
home.repair.title=Επιδιόρθωση
|
home.repair.title=Επιδιόρθωση
|
||||||
home.repair.desc=Προσπάθεια επιδιόρθωσης ενός κατεστραμμένου PDF
|
home.repair.desc=Προσπάθεια επιδιόρθωσης ενός κατεστραμμένου PDF
|
||||||
@@ -381,87 +376,87 @@ repair.tags=fix,restore,correction,recover
|
|||||||
|
|
||||||
home.removeBlanks.title=Αφαίρεση κενών Σελίδων
|
home.removeBlanks.title=Αφαίρεση κενών Σελίδων
|
||||||
home.removeBlanks.desc=Ανίχευση και αφαίρεση κενών σελίδων από ένα έγγραφο
|
home.removeBlanks.desc=Ανίχευση και αφαίρεση κενών σελίδων από ένα έγγραφο
|
||||||
removeBlanks.tags=καθαρισμός,προσαρμογές,εξωφύλακτες-δεδομένων,διοργάνωση
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
home.removeAnnotations.title=Αποθήκευση Παρατηρήσεων
|
home.removeAnnotations.title=Remove Annotations
|
||||||
home.removeAnnotations.desc=Ελέγξτε και προστίθετε σχόλια/παρατηρήσεις από το PDF
|
home.removeAnnotations.desc=Removes all comments/annotations from a PDF
|
||||||
removeAnnotations.tags=σχόλια,κουμπί-ντεκτ,χαρτές,κάθετα-δεδομένα,αποβολή
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Σύγκριση
|
home.compare.title=Σύγκριση
|
||||||
home.compare.desc=Σύγκριση και εμφάνιση των διαφορών μεταξύ δύο PDF αρχείων
|
home.compare.desc=Σύγκριση και εμφάνιση των διαφορών μεταξύ δύο PDF αρχείων
|
||||||
compare.tags=διακρίσεις,σύγκριση,αλλαγές,πληθυσμική-ανάλυση
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
|
||||||
home.certSign.title=Υπογραφή με Πιστοποιητικό
|
home.certSign.title=Υπογραφή με Πιστοποιητικό
|
||||||
home.certSign.desc=Υπογραφή ενός PDF αρχείου με ένα Πιστοποιητικό/Κλειδί (PEM/P12)
|
home.certSign.desc=Υπογραφή ενός PDF αρχείου με ένα Πιστοποιητικό/Κλειδί (PEM/P12)
|
||||||
certSign.tags=υπογραφή,PEM,P12,δημοσίες,δεκτικότητα,κρυπτογράφηση
|
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||||
|
|
||||||
home.removeCertSign.title=Αποθήκευση Υπογραφής Τέλους
|
home.removeCertSign.title=Remove Certificate Sign
|
||||||
home.removeCertSign.desc=Εμβέλειες σημάδισης από το PDF
|
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||||
removeCertSign.tags=υπογραφή,PEM,P12,δημοσίες,κρυπτογράφηση-κλείσιμο
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Διάταξη πολλών σελίδων
|
home.pageLayout.title=Διάταξη πολλών σελίδων
|
||||||
home.pageLayout.desc=Συγχώνευση πολλαπλών σελίδων ενός εγγράφου PDF σε μία μόνο σελίδα
|
home.pageLayout.desc=Συγχώνευση πολλαπλών σελίδων ενός εγγράφου PDF σε μία μόνο σελίδα
|
||||||
pageLayout.tags=σύνθεση,ενωμένη,απλή-ροεπίσκαιρα,διοργάνωση
|
pageLayout.tags=merge,composite,single-view,organize
|
||||||
|
|
||||||
home.scalePages.title=Προσαρμογή του μεγέθους/κλίμακας σελίδας
|
home.scalePages.title=Προσαρμογή του μεγέθους/κλίμακας σελίδας
|
||||||
home.scalePages.desc=Αλλαγή του μεγέθους/κλίμακας μίας σελίδας και/η του περιεχομένου της.
|
home.scalePages.desc=Αλλαγή του μεγέθους/κλίμακας μίας σελίδας και/η του περιεχομένου της.
|
||||||
scalePages.tags=μεταβάλλοντας τους διάστασες,ενδιαφέροντες πόροι,παρακμή,μετατύχηση
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Για προχωρημένους)
|
home.pipeline.title=Pipeline (Για προχωρημένους)
|
||||||
home.pipeline.desc=Εκτέλεση πολλαπλών ενεργειών σε αρχεία PDF ορίζοντας pipeline scripts
|
home.pipeline.desc=Εκτέλεση πολλαπλών ενεργειών σε αρχεία PDF ορίζοντας pipeline scripts
|
||||||
pipeline.tags=αυτόματος κλωστήρας,σειρά,γραμμογραφική,αποδτομή-πακέτων
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
home.add-page-numbers.title=Προσθήκη αριθμών σε Σελίδες
|
home.add-page-numbers.title=Προσθήκη αριθμών σε Σελίδες
|
||||||
home.add-page-numbers.desc=Προσθήκη αριθμών σελίδων σε ένα έγγραφο σε μια καθορισμένη θέση
|
home.add-page-numbers.desc=Προσθήκη αριθμών σελίδων σε ένα έγγραφο σε μια καθορισμένη θέση
|
||||||
add-page-numbers.tags=αυτόματη αρίθμηση σελίδων,κατάταξη,διοργάνωση,παραγγελία
|
add-page-numbers.tags=paginate,label,organize,index
|
||||||
|
|
||||||
home.auto-rename.title=Αυτόματη μετονομασία αρχείου PDF
|
home.auto-rename.title=Αυτόματη μετονομασία αρχείου PDF
|
||||||
home.auto-rename.desc=Αυτόματη μετονομασία ενός αρχείου PDF με βάση την κεφαλίδα που έχει εντοπιστεί
|
home.auto-rename.desc=Αυτόματη μετονομασία ενός αρχείου PDF με βάση την κεφαλίδα που έχει εντοπιστεί
|
||||||
auto-rename.tags=αυτόματη αναδεικτική κατατάξη μέσω κεφαλαιών,διοργάνωση,καθαρισμός
|
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||||
|
|
||||||
home.adjust-contrast.title=Προσαρμογή χρωμάτων/Αντίθεση
|
home.adjust-contrast.title=Προσαρμογή χρωμάτων/Αντίθεση
|
||||||
home.adjust-contrast.desc=Προσαρμογή της αντίθεσης, του κορεσμού και της φωτεινότητας ενός PDF
|
home.adjust-contrast.desc=Προσαρμογή της αντίθεσης, του κορεσμού και της φωτεινότητας ενός PDF
|
||||||
adjust-contrast.tags=καταπολέμηση του συγκρούσεων,ενδιαφέροντας ελέγχο,μεταβάλλοντας για είδη,εξέλιξη
|
adjust-contrast.tags=color-correction,tune,modify,enhance
|
||||||
|
|
||||||
home.crop.title=Περικοπή PDF
|
home.crop.title=Περικοπή PDF
|
||||||
home.crop.desc=Περικοπή ενός PDF για να μειωθεί το μέγεθός του (διατηρεί το κείμενο!)
|
home.crop.desc=Περικοπή ενός PDF για να μειωθεί το μέγεθός του (διατηρεί το κείμενο!)
|
||||||
crop.tags=καταγραφή-καύσης,περικόπτωση,επεξεργασία,forma
|
crop.tags=trim,shrink,edit,shape
|
||||||
|
|
||||||
home.autoSplitPDF.title=Αυτόματος διαχωρισμός σελίδων
|
home.autoSplitPDF.title=Αυτόματος διαχωρισμός σελίδων
|
||||||
home.autoSplitPDF.desc=Αυτόματος διαχωρισμός σαρωμένου PDF με φυσικό σαρωμένο διαχωριστή σελίδων QR Code
|
home.autoSplitPDF.desc=Αυτόματος διαχωρισμός σαρωμένου PDF με φυσικό σαρωμένο διαχωριστή σελίδων QR Code
|
||||||
autoSplitPDF.tags=βάσει QR-code,διχοτομία,ελέγχο-διχοτομία,κλάδωση
|
autoSplitPDF.tags=QR-based,separate,scan-segment,organize
|
||||||
|
|
||||||
home.sanitizePdf.title=Απολύμανση
|
home.sanitizePdf.title=Απολύμανση
|
||||||
home.sanitizePdf.desc=Αφαίρεση σεναρίων και άλλων στοιχείων από αρχεία PDF
|
home.sanitizePdf.desc=Αφαίρεση σεναρίων και άλλων στοιχείων από αρχεία PDF
|
||||||
sanitizePdf.tags=καθαρισμός,διατηρητική,ασφάλεια,προσχώρηση-δυνατότητες
|
sanitizePdf.tags=clean,secure,safe,remove-threats
|
||||||
|
|
||||||
home.URLToPDF.title=URL/Ιστότοπος σε PDF
|
home.URLToPDF.title=URL/Ιστότοπος σε PDF
|
||||||
home.URLToPDF.desc=Μετατροπή οποιαδήποτε διεύθυνσης URL http(s) σε PDF
|
home.URLToPDF.desc=Μετατροπή οποιαδήποτε διεύθυνσης URL http(s) σε PDF
|
||||||
URLToPDF.tags=καταγραφή-Διαδικτύου,θηράω-σεγκένεια,Διαδικτυακό-σε-δοκίμιο,αποθήκευση
|
URLToPDF.tags=web-capture,save-page,web-to-doc,archive
|
||||||
|
|
||||||
home.HTMLToPDF.title=HTML σε PDF
|
home.HTMLToPDF.title=HTML σε PDF
|
||||||
home.HTMLToPDF.desc=Μετατροπή οποιουδήποτε αρχείου HTML ή zip σε PDF
|
home.HTMLToPDF.desc=Μετατροπή οποιουδήποτε αρχείου HTML ή zip σε PDF
|
||||||
HTMLToPDF.tags=μάρκαρι,χρηστήρια-Διαδικτύου,παρασταση,καλύτερο
|
HTMLToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
home.MarkdownToPDF.title=Markdown σε PDF
|
home.MarkdownToPDF.title=Markdown σε PDF
|
||||||
home.MarkdownToPDF.desc=Μετατροπή οποιουδήποτε αρχείου Markdown σε PDF
|
home.MarkdownToPDF.desc=Μετατροπή οποιουδήποτε αρχείου Markdown σε PDF
|
||||||
MarkdownToPDF.tags=μάρκαρι,χρηστήρια-Διαδικτύου,παρασταση,καλύτερο
|
MarkdownToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
home.getPdfInfo.title=Λήψη όλων των πληροφοριών από το PDF
|
home.getPdfInfo.title=Λήψη όλων των πληροφοριών από το PDF
|
||||||
home.getPdfInfo.desc=Λήψη όλων των πιθανών πληροφοριών από τα αρχεία PDF
|
home.getPdfInfo.desc=Λήψη όλων των πιθανών πληροφοριών από τα αρχεία PDF
|
||||||
getPdfInfo.tags=πληροφορίες,δεδομένα,στατιστικά,πληθυσμός
|
getPdfInfo.tags=infomation,data,stats,statistics
|
||||||
|
|
||||||
|
|
||||||
home.extractPage.title=Εξαγωγή σελίδων
|
home.extractPage.title=Εξαγωγή σελίδων
|
||||||
home.extractPage.desc=Εξαγωγή των επιλεγμένων σελίδων από ένα PDF
|
home.extractPage.desc=Εξαγωγή των επιλεγμένων σελίδων από ένα PDF
|
||||||
extractPage.tags=αποδιατύπωση
|
extractPage.tags=extract
|
||||||
|
|
||||||
|
|
||||||
home.PdfToSinglePage.title=PDF σε μία μεγάλη σελίδα
|
home.PdfToSinglePage.title=PDF σε μία μεγάλη σελίδα
|
||||||
home.PdfToSinglePage.desc=Συγχώνευση όλων των σελίδων PDF σε μια μεγάλη σελίδα
|
home.PdfToSinglePage.desc=Συγχώνευση όλων των σελίδων PDF σε μια μεγάλη σελίδα
|
||||||
PdfToSinglePage.tags=για-μία-σελίδα
|
PdfToSinglePage.tags=single page
|
||||||
|
|
||||||
|
|
||||||
home.showJS.title=Εμφάνιση Javascript
|
home.showJS.title=Εμφάνιση Javascript
|
||||||
@@ -470,29 +465,29 @@ showJS.tags=JS
|
|||||||
|
|
||||||
home.autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
home.autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
||||||
home.autoRedact.desc=Αυτόματη επεξεργασία (Μαύρισμα) κείμενου σε PDF με βάση το κείμενο εισαγωγής
|
home.autoRedact.desc=Αυτόματη επεξεργασία (Μαύρισμα) κείμενου σε PDF με βάση το κείμενο εισαγωγής
|
||||||
autoRedact.tags=ξεκράζω,φυλακίζω,εικόνο-κουπές,κρατήστε ασφαλείς,δημιουργήστε
|
autoRedact.tags=Redact,Hide,black out,black,marker,hidden
|
||||||
|
|
||||||
home.tableExtraxt.title=PDF σε CSV
|
home.tableExtraxt.title=PDF σε CSV
|
||||||
home.tableExtraxt.desc=Εξάγει πίνακες από PDF μετατρέποντάς το σε CSV
|
home.tableExtraxt.desc=Εξάγει πίνακες από PDF μετατρέποντάς το σε CSV
|
||||||
tableExtraxt.tags=CSV,καταθέσεις-τάβλες,αποδιατύπωση,μετατροπή
|
tableExtraxt.tags=CSV,Table Extraction,extract,convert
|
||||||
|
|
||||||
|
|
||||||
home.autoSizeSplitPDF.title=Αυτόματη διαίρεση κατά μέγεθος/πλήθος
|
home.autoSizeSplitPDF.title=Αυτόματη διαίρεση κατά μέγεθος/πλήθος
|
||||||
home.autoSizeSplitPDF.desc=Διαχωρίστε ένα μόνο PDF σε πολλά έγγραφα με βάση το μέγεθος, τον αριθμό σελίδων ή τον αριθμό εγγράφων
|
home.autoSizeSplitPDF.desc=Διαχωρίστε ένα μόνο PDF σε πολλά έγγραφα με βάση το μέγεθος, τον αριθμό σελίδων ή τον αριθμό εγγράφων
|
||||||
autoSizeSplitPDF.tags=pdf,διαχωρισμός,γραμμακτής,διορθώσεις-κάντε
|
autoSizeSplitPDF.tags=pdf,split,document,organization
|
||||||
|
|
||||||
|
|
||||||
home.overlay-pdfs.title=Επικάλυψη PDFs
|
home.overlay-pdfs.title=Επικάλυψη PDFs
|
||||||
home.overlay-pdfs.desc=Επικαλύπτει αρχεία PDF πάνω σε άλλο PDF
|
home.overlay-pdfs.desc=Επικαλύπτει αρχεία PDF πάνω σε άλλο PDF
|
||||||
overlay-pdfs.tags=πλέξη-πDFs
|
overlay-pdfs.tags=Overlay
|
||||||
|
|
||||||
home.split-by-sections.title=Διαχωρισμός PDF ανά ενότητες
|
home.split-by-sections.title=Διαχωρισμός PDF ανά ενότητες
|
||||||
home.split-by-sections.desc=Διαχωρίστε κάθε σελίδα ενός PDF σε μικρότερες οριζόντιες και κάθετες ενότητες
|
home.split-by-sections.desc=Διαχωρίστε κάθε σελίδα ενός PDF σε μικρότερες οριζόντιες και κάθετες ενότητες
|
||||||
split-by-sections.tags=διχοτομία-διεργασίες,διεργασίες,κρατήστε
|
split-by-sections.tags=Section Split, Divide, Customize
|
||||||
|
|
||||||
home.AddStampRequest.title=Προσθήκη σφραγίδας σε PDF
|
home.AddStampRequest.title=Προσθήκη σφραγίδας σε PDF
|
||||||
home.AddStampRequest.desc=Προσθέστε κείμενο ή προσθέστε σφραγίδες εικόνας σε καθορισμένες τοποθεσίες
|
home.AddStampRequest.desc=Προσθέστε κείμενο ή προσθέστε σφραγίδες εικόνας σε καθορισμένες τοποθεσίες
|
||||||
AddStampRequest.tags=κάντε-πίνακα,πρόσθεση-εικόνας,κέντρο-εικόνας,χαμυλόχατο,PDF,βάζω-δείγμα,κρατήστε
|
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDF σε Book
|
home.PDFToBook.title=PDF σε Book
|
||||||
@@ -503,33 +498,33 @@ home.BookToPDF.title=Book σε PDF
|
|||||||
home.BookToPDF.desc=Μετατρέπει τις μορφές Books/Comics σε PDF χρησιμοποιώντας calibre
|
home.BookToPDF.desc=Μετατρέπει τις μορφές Books/Comics σε PDF χρησιμοποιώντας calibre
|
||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||||
|
|
||||||
home.removeImagePdf.title=Αφαίρεση εικόνας
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=Αφαίρεση εικόνας από το PDF για να μειώσετε το μέγεθος του αρχείου
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Αφαίρεση-Εικόνας,Πράξεις-Σελίδας,ύπαρξη-τμήματος,πλην-δυναμική
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Διχοτομία PDF ανά Περιγραφές
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=διχοτομία,περιγραφές,κεφάλαια,συνορία
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Αντικατάσταση-Αντίστροφη Παθωμένη Πίντσουρ
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Replace and Invert Color
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Αντικατάσταση χρώματος για το πεδίο κειμένου και απόχρωμη έκθεση PDF για μείωση μεγέθους αρχείου
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Αντικατάσταση χρώματος, Διαδικασίες σελίδας, Μέσω πληροφόρησης, Από την εσωτερική
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Επιλογές αντικατάστασης-Αντίστροφης χρώματος
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Προεπιλογή (Χαράκτηρες υψηλής αντιδιαβάσιμότητας)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Εξαιρετικό (Προσωπικές επιλογές χρώματος)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Αντίστροφη Παθωμένη (Αντίστροφη αντικατάσταση όλων των πχώματος)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Επιλογές υψηλής αντιδιαβάσιμότητας
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Χρίστος το πεδίο κειμένου σε μαύρη οπτική
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Μαύρο το πεδίο κειμένου σε χλωρό οπτική
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Ευτυχισμένο το πεδίο κειμένου σε μαύρη οπτική
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Χλωροφαίνοντος το πεδίο κειμένου σε μαύρη οπτική
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Επιλογή χρώματος του κειμένου
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Επιλογή απόχρωμης οπτικής
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Αντικατάσταση
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -548,17 +543,18 @@ login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
|||||||
login.signinTitle=Παρακαλώ, συνδεθείτε
|
login.signinTitle=Παρακαλώ, συνδεθείτε
|
||||||
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
||||||
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
||||||
login.oauth2AdminBlockedUser=Η εγγραφή και το πληκτρολόγημα ανθρώπων με όχι εγγραμμένες λογισμικές αρχίζουν να είναι ορισμένες τοποθετήσεις. Παρακαλώ στείλτε μια ισχυρά παραίτηση στον διοικητή.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Απαιτήσεις πληροφόρησης χρήστη ανακαλύψτηκαν μη έχουν βρεθεί
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
login.oauth2InvalidUserInfoResponse=Απάντηση μεμονωμένων πληροφοριών χρήστη αποδείξτηκε άγνωστη
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
login.oauth2invalidRequest=Αρχαίως απαιτήσεις πληροφοριών χρήστη
|
login.oauth2invalidRequest=Invalid Request
|
||||||
login.oauth2AccessDenied=Πρόσβαση ορισμένες τοποθετήσεις
|
login.oauth2AccessDenied=Access Denied
|
||||||
login.oauth2InvalidTokenResponse=Απάντηση άρκετο ρόταμα
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
login.oauth2InvalidIdToken=Λιδός όρκου μη αρκετός
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
login.userIsDisabled=Ο χρήστης είναι δευτεροβαθμιακά διακωμένος, το σύστημα άλλαξε τον καθώς βρεθεί. Παρακαλείστε τους αρχηγούς να επιβεβαιώσουν τη συμπεριφορά χρήστη.
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
login.alreadyLoggedIn=Είστε ήδη σύνδεδες σε
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=κατοχόι. Παρακαλώ δυσκέντρωση τους και προσπαθήστε ξανά.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Έχετε πολλές εγγραφές σε βίτιο μέση
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
||||||
@@ -568,7 +564,7 @@ autoRedact.textsToRedactLabel=Κείμενο για μαύρισμα (διαχω
|
|||||||
autoRedact.textsToRedactPlaceholder=π.χ. \nΕμπιστευτικό \nΑκρώς απόρρητο
|
autoRedact.textsToRedactPlaceholder=π.χ. \nΕμπιστευτικό \nΑκρώς απόρρητο
|
||||||
autoRedact.useRegexLabel=Χρήση Regex
|
autoRedact.useRegexLabel=Χρήση Regex
|
||||||
autoRedact.wholeWordSearchLabel=Αναζήτηση ολόκληρης της λέξης
|
autoRedact.wholeWordSearchLabel=Αναζήτηση ολόκληρης της λέξης
|
||||||
autoRedact.customPaddingLabel=Εμπνευσμένη Προσθήκη Ανάκρισης
|
autoRedact.customPaddingLabel=Custom Extra Padding
|
||||||
autoRedact.convertPDFToImageLabel=Μετατροπή PDF σε PDF-Εικόνα (Χρησιμοποιείται για την αφαίρεση κειμένου πίσω από το πλαίσιο)
|
autoRedact.convertPDFToImageLabel=Μετατροπή PDF σε PDF-Εικόνα (Χρησιμοποιείται για την αφαίρεση κειμένου πίσω από το πλαίσιο)
|
||||||
autoRedact.submitButton=Υποβολή
|
autoRedact.submitButton=Υποβολή
|
||||||
|
|
||||||
@@ -634,7 +630,7 @@ HTMLToPDF.defaultHeader=Ενεργοποίηση προεπιλεγμένης κ
|
|||||||
HTMLToPDF.cssMediaType=Αλλάξτε τον τύπο μέσων (media type )CSS της σελίδας.
|
HTMLToPDF.cssMediaType=Αλλάξτε τον τύπο μέσων (media type )CSS της σελίδας.
|
||||||
HTMLToPDF.none=Κανένα
|
HTMLToPDF.none=Κανένα
|
||||||
HTMLToPDF.print=Εκτύπωσε
|
HTMLToPDF.print=Εκτύπωσε
|
||||||
HTMLToPDF.screen=Ορόσκοπος
|
HTMLToPDF.screen=Screen
|
||||||
|
|
||||||
|
|
||||||
#AddStampRequest
|
#AddStampRequest
|
||||||
@@ -733,7 +729,7 @@ pageLayout.submit=Υποβολή
|
|||||||
scalePages.title=Προσαρμογή κλίμακας σελίδας
|
scalePages.title=Προσαρμογή κλίμακας σελίδας
|
||||||
scalePages.header=Προσαρμογή κλίμακας σελίδας
|
scalePages.header=Προσαρμογή κλίμακας σελίδας
|
||||||
scalePages.pageSize=Μέγεθος μιας σελίδας του εγγράφου.
|
scalePages.pageSize=Μέγεθος μιας σελίδας του εγγράφου.
|
||||||
scalePages.keepPageSize=Τροποποίηση με ευθύγειες γραμμές
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=Επίπεδο ζουμ (περικοπή) σελίδας.
|
scalePages.scaleFactor=Επίπεδο ζουμ (περικοπή) σελίδας.
|
||||||
scalePages.submit=Υποβολή
|
scalePages.submit=Υποβολή
|
||||||
|
|
||||||
@@ -753,15 +749,14 @@ certSign.showSig=Εμφάνιση Υπογραφής
|
|||||||
certSign.reason=Αιτία
|
certSign.reason=Αιτία
|
||||||
certSign.location=Τοποθεσία
|
certSign.location=Τοποθεσία
|
||||||
certSign.name=Όνομα
|
certSign.name=Όνομα
|
||||||
certSign.showLogo=Προειδοποίηση Παρόχου
|
|
||||||
certSign.submit=Υπογραφή PDF
|
certSign.submit=Υπογραφή PDF
|
||||||
|
|
||||||
|
|
||||||
#removeCertSign
|
#removeCertSign
|
||||||
removeCertSign.title=Αφαίρεση Αντικειμένων Τυπογραφίας Τομβής
|
removeCertSign.title=Remove Certificate Signature
|
||||||
removeCertSign.header=Αφαίρεση της πληροφόρησης όρκου από το PDF
|
removeCertSign.header=Remove the digital certificate from the PDF
|
||||||
removeCertSign.selectPDF=Επιλέξτε ένα αρχείο PDF:
|
removeCertSign.selectPDF=Select a PDF file:
|
||||||
removeCertSign.submit=Αφαιρέστε την ψήφιση
|
removeCertSign.submit=Remove Signature
|
||||||
|
|
||||||
|
|
||||||
#removeBlanks
|
#removeBlanks
|
||||||
@@ -783,14 +778,11 @@ removeAnnotations.submit=Κατάργηση
|
|||||||
#compare
|
#compare
|
||||||
compare.title=Σύγκριση
|
compare.title=Σύγκριση
|
||||||
compare.header=Σύγκριση PDFs
|
compare.header=Σύγκριση PDFs
|
||||||
compare.highlightColor.1=Μάσκα Εμφανισμού 1:
|
compare.highlightColor.1=Highlight Color 1:
|
||||||
compare.highlightColor.2=Μάσκα Εμφανισμού 2:
|
compare.highlightColor.2=Highlight Color 2:
|
||||||
compare.document.1=Έγγραφο 1
|
compare.document.1=Έγγραφο 1
|
||||||
compare.document.2=Έγγραφο 2
|
compare.document.2=Έγγραφο 2
|
||||||
compare.submit=Σύγκριση
|
compare.submit=Σύγκριση
|
||||||
compare.complex.message=Ένα ή τα δίπλα αποδεκτής χαρτογράφημα είναι μεγάλο, η πραγματικότητα αναζήτησης μπορεί να εξαβλύσει τη συγκρίση
|
|
||||||
compare.large.file.message=Ένα ή τα δίπλα αποδεκτής χαρτογράφημα είναι πολύ μεγάλο για να εξυπηρετήσει
|
|
||||||
compare.no.text.message=ένα ή τα δίπλα αποδεκτής χαρτογράφημα δεν περιέχουν κείμενο. Παρακαλώ επιλέξτε PDF με κείμενο για σύγκριση.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Books και Comics σε PDF
|
BookToPDF.title=Books και Comics σε PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Σχεδίαση υπογραφής
|
|||||||
sign.text=Εισαγωγή κειμένου
|
sign.text=Εισαγωγή κειμένου
|
||||||
sign.clear=Καθάρισμα
|
sign.clear=Καθάρισμα
|
||||||
sign.add=Προσθήκη
|
sign.add=Προσθήκη
|
||||||
sign.saved=Αποθηκευμένες Αλιάσιδες
|
|
||||||
sign.save=Αποθήκευση Αλιάσης
|
|
||||||
sign.personalSigs=Προσωπικές Αλιάσεις
|
|
||||||
sign.sharedSigs=Μεταδότες Αλιάσεις
|
|
||||||
sign.noSavedSigs=Δεν βρέθηκαν αποθηκευμένες αλιάσεις
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -827,10 +814,10 @@ repair.submit=Επιδιόρθωση
|
|||||||
|
|
||||||
|
|
||||||
#flatten
|
#flatten
|
||||||
flatten.title=Φιλοξένηση
|
flatten.title=Flatten
|
||||||
flatten.header=Flatten PDFs
|
flatten.header=Flatten PDFs
|
||||||
flatten.flattenOnlyForms=Φιλοξενήστε μόνο τα φόρμαδα
|
flatten.flattenOnlyForms=Flatten only forms
|
||||||
flatten.submit=Φιλοξένηση
|
flatten.submit=Flatten
|
||||||
|
|
||||||
|
|
||||||
#ScannerImageSplit
|
#ScannerImageSplit
|
||||||
@@ -844,7 +831,7 @@ ScannerImageSplit.selectText.7=Ελάχιστη επιφάνεια περιγρ
|
|||||||
ScannerImageSplit.selectText.8=Ρυθμίζει το ελάχιστο όριο περιγράμματος για μια φωτογραφία
|
ScannerImageSplit.selectText.8=Ρυθμίζει το ελάχιστο όριο περιγράμματος για μια φωτογραφία
|
||||||
ScannerImageSplit.selectText.9=Μέγεθος περιγράμματος:
|
ScannerImageSplit.selectText.9=Μέγεθος περιγράμματος:
|
||||||
ScannerImageSplit.selectText.10=Ορίζει το μέγεθος του περιγράμματος που προστίθεται και αφαιρείται για να αποτρέπονται λευκά περιγράμματα στην έξοδο (προεπιλογή: 1).
|
ScannerImageSplit.selectText.10=Ορίζει το μέγεθος του περιγράμματος που προστίθεται και αφαιρείται για να αποτρέπονται λευκά περιγράμματα στην έξοδο (προεπιλογή: 1).
|
||||||
ScannerImageSplit.info=Δεν είναι ιστάμενος Python. Είναι απαιτήτων για τη λειτουργία.
|
ScannerImageSplit.info=Python is not installed. It is required to run.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -871,7 +858,7 @@ ocr.submit=Επεξεργασία PDF με OCR
|
|||||||
extractImages.title=Εξαγωγή Εικόνων
|
extractImages.title=Εξαγωγή Εικόνων
|
||||||
extractImages.header=Εξαγωγή Εικόνων
|
extractImages.header=Εξαγωγή Εικόνων
|
||||||
extractImages.selectText=Επιλέξτε μορφή εικόνας για να μετατρέψετε τις εξαγόμενες εικόνες
|
extractImages.selectText=Επιλέξτε μορφή εικόνας για να μετατρέψετε τις εξαγόμενες εικόνες
|
||||||
extractImages.allowDuplicates=Αποθήκευση εικόνων που μπορούν να είναι ίδιες
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=Εξαγωγή
|
extractImages.submit=Εξαγωγή
|
||||||
|
|
||||||
|
|
||||||
@@ -879,7 +866,7 @@ extractImages.submit=Εξαγωγή
|
|||||||
fileToPDF.title=Αρχείο σε PDF
|
fileToPDF.title=Αρχείο σε PDF
|
||||||
fileToPDF.header=Μετατροπή οποιουδήποτε αρχείου σε PDF
|
fileToPDF.header=Μετατροπή οποιουδήποτε αρχείου σε PDF
|
||||||
fileToPDF.credit=Αυτή η υπηρεσία χρησιμοποιεί LibreOffice και Unoconv για την μετατροπή των αρχείων.
|
fileToPDF.credit=Αυτή η υπηρεσία χρησιμοποιεί LibreOffice και Unoconv για την μετατροπή των αρχείων.
|
||||||
fileToPDF.supportedFileTypesInfo=Υποστηριζόμενα αρχεία
|
fileToPDF.supportedFileTypesInfo=Supported File types
|
||||||
fileToPDF.supportedFileTypes=Οι υποστηριζόμενοι τύποι αρχείων θα πρέπει να περιλαμβάνουν τα παρακάτω, ωστόσο, για μια πλήρη ενημερωμένη λίστα με τις υποστηριζόμενες μορφές, ανατρέξτε στην τεκμηρίωση του LibreOffice
|
fileToPDF.supportedFileTypes=Οι υποστηριζόμενοι τύποι αρχείων θα πρέπει να περιλαμβάνουν τα παρακάτω, ωστόσο, για μια πλήρη ενημερωμένη λίστα με τις υποστηριζόμενες μορφές, ανατρέξτε στην τεκμηρίωση του LibreOffice
|
||||||
fileToPDF.submit=Μετατροπή σε PDF
|
fileToPDF.submit=Μετατροπή σε PDF
|
||||||
|
|
||||||
@@ -909,7 +896,7 @@ merge.title=Συγχώνευση
|
|||||||
merge.header=Συγχώνευση πολλαπλών PDFs (2+)
|
merge.header=Συγχώνευση πολλαπλών PDFs (2+)
|
||||||
merge.sortByName=Ταξινόμηση με βάση το Όνομα
|
merge.sortByName=Ταξινόμηση με βάση το Όνομα
|
||||||
merge.sortByDate=Ταξινόμηση με βάση την Ημερομηνία
|
merge.sortByDate=Ταξινόμηση με βάση την Ημερομηνία
|
||||||
merge.removeCertSign=Επιλογή για τη μέριξη δικαιωμάτων σημείους σημάδους; Αφαίρεση ηλεκτρονικών πρόσβασης από το συγχέωνται αρχείο?
|
merge.removeCertSign=Remove digital signature in the merged file?
|
||||||
merge.submit=Συγχώνευση
|
merge.submit=Συγχώνευση
|
||||||
|
|
||||||
|
|
||||||
@@ -917,35 +904,24 @@ merge.submit=Συγχώνευση
|
|||||||
pdfOrganiser.title=Διοργανωτής σελίδας
|
pdfOrganiser.title=Διοργανωτής σελίδας
|
||||||
pdfOrganiser.header=Διοργανωτής σελίδας PDF
|
pdfOrganiser.header=Διοργανωτής σελίδας PDF
|
||||||
pdfOrganiser.submit=Αναδιάταξη σελίδων
|
pdfOrganiser.submit=Αναδιάταξη σελίδων
|
||||||
pdfOrganiser.mode=Τύπος
|
pdfOrganiser.mode=Mode
|
||||||
pdfOrganiser.mode.1=Προσαρμοσμένη Σειρά Σελίδας
|
pdfOrganiser.mode.1=Προσαρμοσμένη Σειρά Σελίδας
|
||||||
pdfOrganiser.mode.2=Αντίστροφη Σειρά
|
pdfOrganiser.mode.2=Αντίστροφη Σειρά
|
||||||
pdfOrganiser.mode.3=Ταξινόμηση Διπλής Όψης (Duplex Sort)
|
pdfOrganiser.mode.3=Ταξινόμηση Διπλής Όψης (Duplex Sort)
|
||||||
pdfOrganiser.mode.4=Ταξινόμηση Φυλλαδίου (Booklet Sort)
|
pdfOrganiser.mode.4=Ταξινόμηση Φυλλαδίου (Booklet Sort)
|
||||||
pdfOrganiser.mode.5=Πλάσμα Σεπτιμερή Λεφτοδυοκόσμου
|
pdfOrganiser.mode.5=Side Stitch Booklet Sort
|
||||||
pdfOrganiser.mode.6=Διαίρεση Μονού-Ζυγού
|
pdfOrganiser.mode.6=Διαίρεση Μονού-Ζυγού
|
||||||
pdfOrganiser.mode.7=Αφαίρεση Πρώτου
|
pdfOrganiser.mode.7=Αφαίρεση Πρώτου
|
||||||
pdfOrganiser.mode.8=Αφαίρεση Τελευταίου
|
pdfOrganiser.mode.8=Αφαίρεση Τελευταίου
|
||||||
pdfOrganiser.mode.9=Αφαίρεση Πρώτου και Τελευταίου
|
pdfOrganiser.mode.9=Αφαίρεση Πρώτου και Τελευταίου
|
||||||
pdfOrganiser.mode.10=Περίπτωση Τυχαίου Μέριξης
|
pdfOrganiser.mode.10=Odd-Even Merge
|
||||||
pdfOrganiser.placeholder=(π.χ. 1,3,2 ή 4-8,2,10-12 ή 2n-1)
|
pdfOrganiser.placeholder=(π.χ. 1,3,2 ή 4-8,2,10-12 ή 2n-1)
|
||||||
|
|
||||||
|
|
||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title=PDF Πολυεργαλείο
|
multiTool.title=PDF Πολυεργαλείο
|
||||||
multiTool.header=PDF Πολυεργαλείο
|
multiTool.header=PDF Πολυεργαλείο
|
||||||
multiTool.uploadPrompts=Όνομα αρχείου
|
multiTool.uploadPrompts=File Name
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=Προβολή PDF
|
viewPdf.title=Προβολή PDF
|
||||||
@@ -1007,7 +983,7 @@ pdfToImage.color=Χρώμα
|
|||||||
pdfToImage.grey=Κλίμακα του γκρι
|
pdfToImage.grey=Κλίμακα του γκρι
|
||||||
pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!)
|
pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!)
|
||||||
pdfToImage.submit=Μετατροπή
|
pdfToImage.submit=Μετατροπή
|
||||||
pdfToImage.info=Δεν είναι ιστάμενος Python. Είναι απαιτήτων για τη μετατροπή σε WebP.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1044,7 +1020,7 @@ watermark.selectText.6=heightSpacer (Κενό μεταξύ κάθε υδατογ
|
|||||||
watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
|
watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
|
||||||
watermark.selectText.8=Τύπος Υδατογραφήματος:
|
watermark.selectText.8=Τύπος Υδατογραφήματος:
|
||||||
watermark.selectText.9=Εικόνα Υδατογραφήματος:
|
watermark.selectText.9=Εικόνα Υδατογραφήματος:
|
||||||
watermark.selectText.10=Μετατροπή PDF σε PDF-Image
|
watermark.selectText.10=Convert PDF to PDF-Image
|
||||||
watermark.submit=Προσθήκη Υδατογραφήματος
|
watermark.submit=Προσθήκη Υδατογραφήματος
|
||||||
watermark.type.1=Κείμενο
|
watermark.type.1=Κείμενο
|
||||||
watermark.type.2=Εικόνα
|
watermark.type.2=Εικόνα
|
||||||
@@ -1088,7 +1064,7 @@ changeMetadata.keywords=Λέξεις-κλειδιά:
|
|||||||
changeMetadata.modDate=Ημερομηνία Τροποποίησης (yyyy/MM/dd HH:mm:ss):
|
changeMetadata.modDate=Ημερομηνία Τροποποίησης (yyyy/MM/dd HH:mm:ss):
|
||||||
changeMetadata.producer=Παραγωγός:
|
changeMetadata.producer=Παραγωγός:
|
||||||
changeMetadata.subject=Θέμα:
|
changeMetadata.subject=Θέμα:
|
||||||
changeMetadata.trapped=Τυχόν Περίκλειση:
|
changeMetadata.trapped=Trapped:
|
||||||
changeMetadata.selectText.4=Άλλα μεταδεδομένα:
|
changeMetadata.selectText.4=Άλλα μεταδεδομένα:
|
||||||
changeMetadata.selectText.5=Προσθήκη εγγραφής προσαρμοσμένων μεταδεδομένων
|
changeMetadata.selectText.5=Προσθήκη εγγραφής προσαρμοσμένων μεταδεδομένων
|
||||||
changeMetadata.submit=Αλλαγή
|
changeMetadata.submit=Αλλαγή
|
||||||
@@ -1100,8 +1076,8 @@ pdfToPDFA.header=PDF σε PDF/A
|
|||||||
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή
|
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή
|
||||||
pdfToPDFA.submit=Μετατροπή
|
pdfToPDFA.submit=Μετατροπή
|
||||||
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
||||||
pdfToPDFA.outputFormat=Εξόδος αναμορφώσεων
|
pdfToPDFA.outputFormat=Output format
|
||||||
pdfToPDFA.pdfWithDigitalSignature=Το PDF περιέχει ηλεκτρονική ψηφιακή υπογραφή. Αυτό θα αφαιρεθεί στο επόμενο βήμα.
|
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@@ -1170,8 +1146,8 @@ overlay-pdfs.mode.fixedRepeat=Διορθώθηκε η Επικάλυψη Επα
|
|||||||
overlay-pdfs.counts.label=Μετρήσεις επικάλυψης (για σταθερή λειτουργία επανάληψης)
|
overlay-pdfs.counts.label=Μετρήσεις επικάλυψης (για σταθερή λειτουργία επανάληψης)
|
||||||
overlay-pdfs.counts.placeholder=Εισαγάγετε μετρήσεις διαχωρισμένες με κόμματα (π.χ. 2,3,1)
|
overlay-pdfs.counts.placeholder=Εισαγάγετε μετρήσεις διαχωρισμένες με κόμματα (π.χ. 2,3,1)
|
||||||
overlay-pdfs.position.label=Επιλέξτε Θέση Επικάλυψης
|
overlay-pdfs.position.label=Επιλέξτε Θέση Επικάλυψης
|
||||||
overlay-pdfs.position.foreground=Προσκήνιο
|
overlay-pdfs.position.foreground=Foreground
|
||||||
overlay-pdfs.position.background=фондо
|
overlay-pdfs.position.background=Background
|
||||||
overlay-pdfs.submit=Υποβολή
|
overlay-pdfs.submit=Υποβολή
|
||||||
|
|
||||||
|
|
||||||
@@ -1187,31 +1163,31 @@ split-by-sections.merge=Συγχώνευση σε ένα PDF
|
|||||||
|
|
||||||
|
|
||||||
#printFile
|
#printFile
|
||||||
printFile.title=Εκτύπωση Αρχείου
|
printFile.title=Print File
|
||||||
printFile.header=Εκτύπωση Αρχείου σε Εκτυπωτή
|
printFile.header=Print File to Printer
|
||||||
printFile.selectText.1=Επιλογή Αρχείου για Εκτύπωση
|
printFile.selectText.1=Select File to Print
|
||||||
printFile.selectText.2=Εισάγετε το όνομα του εκτυπωτή
|
printFile.selectText.2=Enter Printer Name
|
||||||
printFile.submit=Εκτύπωση
|
printFile.submit=Print
|
||||||
|
|
||||||
|
|
||||||
#licenses
|
#licenses
|
||||||
licenses.nav=Άδειες
|
licenses.nav=Άδειες
|
||||||
licenses.title=3rd Party Άδειες
|
licenses.title=3rd Party Άδειες
|
||||||
licenses.header=3rd Party Άδειες
|
licenses.header=3rd Party Άδειες
|
||||||
licenses.module=Μόδουλο
|
licenses.module=Module
|
||||||
licenses.version=Εκδοχή
|
licenses.version=Εκδοχή
|
||||||
licenses.license=Άδεια
|
licenses.license=Άδεια
|
||||||
|
|
||||||
#survey
|
#survey
|
||||||
survey.nav=Έρευνα
|
survey.nav=Survey
|
||||||
survey.title=Περιεχόμενο Έρευνας Stirling-PDF
|
survey.title=Stirling-PDF Survey
|
||||||
survey.description=Η Stirling-PDF δεν επηρεάζεται από τη μετακίνηση. Ελπίζουμε πως θα συμμεριζόμαστε για να βελτιώσουμε τη Stirling-PDF!
|
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||||
survey.changes=Αφού κάποιες αλλαγές στην Stirling-PDF, θα πρόκειται να εξετάσουμε με τρόπου πιο συγκεκριμένο αυτά. Αναχαίτιζε την ελέγχωσή τους!
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Με αυτές τις αλλαγές, συμπεριλαμβάνονται και οι δομοφόνια χρηματοδότησης και εκπομπής
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Please consider taking our survey!
|
survey.please=Please consider taking our survey!
|
||||||
survey.disabled=(Το παράθυρο ερεύνησης θα απενεργοποιηθεί σε μελλοντικές ενημερώσεις, αλλά θα παρουσιάζεται στο τέλος της διαδικασίας)
|
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||||
survey.button=Παίρνω μερίδα στην ερεύνα
|
survey.button=Take Survey
|
||||||
survey.dontShowAgain=Δεν απεικονίζεται ξανά
|
survey.dontShowAgain=Don't show again
|
||||||
|
|
||||||
|
|
||||||
#error
|
#error
|
||||||
@@ -1229,19 +1205,21 @@ error.discordSubmit=Discord - Υποβάλετε ένα Support post
|
|||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=Αφαίρεση εικόνας
|
removeImage.title=Remove image
|
||||||
removeImage.header=Αφαίρεση εικόνας
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=Αφαίρεση εικόνας
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=Αποθέτει την εικόνα
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
|
splitByChapters.title=Split PDF by Chapters
|
||||||
|
splitByChapters.header=Split PDF by Chapters
|
||||||
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Χωρίστε το PDF με βάση τα κεφάλαια
|
|
||||||
splitByChapters.header=Χωρίστε το PDF με βάση τα κεφάλαια
|
|
||||||
splitByChapters.bookmarkLevel=Επίπεδο Σήμανσης Σκέψης
|
|
||||||
splitByChapters.includeMetadata=Πρόσθεση Metadata
|
|
||||||
splitByChapters.allowDuplicates=Διάλυση Παρόντων Τίτλων Επιπέδου
|
|
||||||
splitByChapters.desc.1=Αυτή η εργαλείο χωρίζει το PDF αρχείο σε πολλά PDF βάση της καθορισμένης δομής του κεφαλαιώδους.
|
|
||||||
splitByChapters.desc.2=Επίπεδο Σήμανσης Σκέψης: Επιλέξτε το επίπεδο σήμανσης σκέψης που να χρησιμοποιηθεί για τη διάλυση (0 για το κύριο επίπεδο, 1 για το δεύτερο επίπεδο κλπ.).
|
|
||||||
splitByChapters.desc.3=Πρόσθεση Metadata: Αν επεξεργαστείται, οι αρχικές metadata του PDF θα προστεθούν σε κάθε διαλυμένο PDF.
|
|
||||||
splitByChapters.desc.4=Διάλυση Παρόντων Τίτλων Επιπέδου: Αν επεξεργαστείται, επιτρέπει τη δημιουργία αποκοπών PDF με βάση πλήρως καθορισμένους σήμαντες έδρας.
|
|
||||||
splitByChapters.submit=Διαλύστε το PDF
|
|
||||||
|
|||||||
@@ -79,9 +79,6 @@ info=Info
|
|||||||
pro=Pro
|
pro=Pro
|
||||||
page=Page
|
page=Page
|
||||||
pages=Pages
|
pages=Pages
|
||||||
loading=Loading...
|
|
||||||
addToDoc=Add to Document
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -142,7 +139,6 @@ navbar.language=Languages
|
|||||||
navbar.settings=Settings
|
navbar.settings=Settings
|
||||||
navbar.allTools=Tools
|
navbar.allTools=Tools
|
||||||
navbar.multiTool=Multi Tool
|
navbar.multiTool=Multi Tool
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Organize
|
navbar.sections.organize=Organize
|
||||||
navbar.sections.convertTo=Convert to PDF
|
navbar.sections.convertTo=Convert to PDF
|
||||||
navbar.sections.convertFrom=Convert from PDF
|
navbar.sections.convertFrom=Convert from PDF
|
||||||
@@ -248,7 +244,6 @@ database.fileNullOrEmpty=File must not be null or empty
|
|||||||
database.failedImportFile=Failed to import file
|
database.failedImportFile=Failed to import file
|
||||||
|
|
||||||
session.expired=Your session has expired. Please refresh the page and try again.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -559,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=Show Signature
|
|||||||
certSign.reason=Reason
|
certSign.reason=Reason
|
||||||
certSign.location=Location
|
certSign.location=Location
|
||||||
certSign.name=Name
|
certSign.name=Name
|
||||||
certSign.showLogo=Show Logo
|
|
||||||
certSign.submit=Sign PDF
|
certSign.submit=Sign PDF
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=Highlight Color 2:
|
|||||||
compare.document.1=Document 1
|
compare.document.1=Document 1
|
||||||
compare.document.2=Document 2
|
compare.document.2=Document 2
|
||||||
compare.submit=Compare
|
compare.submit=Compare
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Books and Comics to PDF
|
BookToPDF.title=Books and Comics to PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Draw Signature
|
|||||||
sign.text=Text Input
|
sign.text=Text Input
|
||||||
sign.clear=Clear
|
sign.clear=Clear
|
||||||
sign.add=Add
|
sign.add=Add
|
||||||
sign.saved=Saved Signatures
|
|
||||||
sign.save=Save Signature
|
|
||||||
sign.personalSigs=Personal Signatures
|
|
||||||
sign.sharedSigs=Shared Signatures
|
|
||||||
sign.noSavedSigs=No saved signatures found
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
|||||||
multiTool.title=PDF Multi Tool
|
multiTool.title=PDF Multi Tool
|
||||||
multiTool.header=PDF Multi Tool
|
multiTool.header=PDF Multi Tool
|
||||||
multiTool.uploadPrompts=File Name
|
multiTool.uploadPrompts=File Name
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=View PDF
|
viewPdf.title=View PDF
|
||||||
@@ -1245,3 +1221,5 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -79,9 +79,6 @@ info=Info
|
|||||||
pro=Pro
|
pro=Pro
|
||||||
page=Page
|
page=Page
|
||||||
pages=Pages
|
pages=Pages
|
||||||
loading=Loading...
|
|
||||||
addToDoc=Add to Document
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -142,7 +139,6 @@ navbar.language=Languages
|
|||||||
navbar.settings=Settings
|
navbar.settings=Settings
|
||||||
navbar.allTools=Tools
|
navbar.allTools=Tools
|
||||||
navbar.multiTool=Multi Tool
|
navbar.multiTool=Multi Tool
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Organize
|
navbar.sections.organize=Organize
|
||||||
navbar.sections.convertTo=Convert to PDF
|
navbar.sections.convertTo=Convert to PDF
|
||||||
navbar.sections.convertFrom=Convert from PDF
|
navbar.sections.convertFrom=Convert from PDF
|
||||||
@@ -248,7 +244,6 @@ database.fileNullOrEmpty=File must not be null or empty
|
|||||||
database.failedImportFile=Failed Import File
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Your session has expired. Please refresh the page and try again.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -559,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=Show Signature
|
|||||||
certSign.reason=Reason
|
certSign.reason=Reason
|
||||||
certSign.location=Location
|
certSign.location=Location
|
||||||
certSign.name=Name
|
certSign.name=Name
|
||||||
certSign.showLogo=Show Logo
|
|
||||||
certSign.submit=Sign PDF
|
certSign.submit=Sign PDF
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=Highlight Color 2:
|
|||||||
compare.document.1=Document 1
|
compare.document.1=Document 1
|
||||||
compare.document.2=Document 2
|
compare.document.2=Document 2
|
||||||
compare.submit=Compare
|
compare.submit=Compare
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Books and Comics to PDF
|
BookToPDF.title=Books and Comics to PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Draw Signature
|
|||||||
sign.text=Text Input
|
sign.text=Text Input
|
||||||
sign.clear=Clear
|
sign.clear=Clear
|
||||||
sign.add=Add
|
sign.add=Add
|
||||||
sign.saved=Saved Signatures
|
|
||||||
sign.save=Save Signature
|
|
||||||
sign.personalSigs=Personal Signatures
|
|
||||||
sign.sharedSigs=Shared Signatures
|
|
||||||
sign.noSavedSigs=No saved signatures found
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
|||||||
multiTool.title=PDF Multi Tool
|
multiTool.title=PDF Multi Tool
|
||||||
multiTool.header=PDF Multi Tool
|
multiTool.header=PDF Multi Tool
|
||||||
multiTool.uploadPrompts=File Name
|
multiTool.uploadPrompts=File Name
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=View PDF
|
viewPdf.title=View PDF
|
||||||
@@ -1245,3 +1221,5 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Tamaño de Letra
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Nombre de Letra
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=Seleccionar PDF(s)
|
pdfPrompt=Seleccionar PDF(s)
|
||||||
multiPdfPrompt=Seleccionar PDFs (2+)
|
multiPdfPrompt=Seleccionar PDFs (2+)
|
||||||
multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera
|
multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera
|
||||||
@@ -50,7 +50,7 @@ WorkInProgess=Tarea en progreso, puede no funcionar o ralentizarse; ¡por favor,
|
|||||||
poweredBy=Desarrollado por
|
poweredBy=Desarrollado por
|
||||||
yes=Sí
|
yes=Sí
|
||||||
no=No
|
no=No
|
||||||
changedCredsMessage=¡Se cambiaron las credenciales!
|
changedCredsMessage=Se cambiaron las credenciales!
|
||||||
notAuthenticatedMessage=Usuario no autentificado.
|
notAuthenticatedMessage=Usuario no autentificado.
|
||||||
userNotFoundMessage=Usuario no encontrado.
|
userNotFoundMessage=Usuario no encontrado.
|
||||||
incorrectPasswordMessage=La contraseña actual no es correcta.
|
incorrectPasswordMessage=La contraseña actual no es correcta.
|
||||||
@@ -75,19 +75,16 @@ visitGithub=Visitar Repositorio de Github
|
|||||||
donate=Donar
|
donate=Donar
|
||||||
color=Color
|
color=Color
|
||||||
sponsor=Patrocinador
|
sponsor=Patrocinador
|
||||||
info=Información
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Página
|
page=Page
|
||||||
pages=Páginas
|
pages=Pages
|
||||||
loading=Cargando...
|
|
||||||
addToDoc=Agregar al Documento
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Política de Privacidad
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Términos y Condiciones
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Accesibilidad
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Política de Cookies
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Impresión
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -117,21 +114,21 @@ pipelineOptions.validateButton=Validar
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Actualiza a Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Esta característica está únicamente disponible para usuarios Pro.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro soporta configuración de ficheros YAML y otras características SSO.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=¿Busca más funciones de administración de usuarios? Consulte Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=¿Quieres mejorar Stirling PDF?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF ha optado por analíticas para ayudarnos a mejorar el producto. No rastreamos ninguna información personal ni contenido de archivos.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Considere habilitar analíticas para ayudar a Stirling-PDF a crecer y permitirnos comprender mejor a nuestros usuarios.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Habilitar analíticas
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Deshabilitar analíticas
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Puede cambiar la configuración de analíticas en el archivo config/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -142,14 +139,13 @@ navbar.language=Idiomas
|
|||||||
navbar.settings=Configuración
|
navbar.settings=Configuración
|
||||||
navbar.allTools=Herramientas
|
navbar.allTools=Herramientas
|
||||||
navbar.multiTool=Multi herramientas
|
navbar.multiTool=Multi herramientas
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Organizar
|
navbar.sections.organize=Organizar
|
||||||
navbar.sections.convertTo=Convertir a PDF
|
navbar.sections.convertTo=Convertir a PDF
|
||||||
navbar.sections.convertFrom=Convertir desde PDF
|
navbar.sections.convertFrom=Convertir desde PDF
|
||||||
navbar.sections.security=Señalización y seguridad
|
navbar.sections.security=Señalización y seguridad
|
||||||
navbar.sections.advance=Avanzado
|
navbar.sections.advance=Avanzado
|
||||||
navbar.sections.edit=Ver y Editar
|
navbar.sections.edit=Ver y Editar
|
||||||
navbar.sections.popular=Populares
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -247,8 +243,7 @@ database.fileNotFound=Archivo no encontrado
|
|||||||
database.fileNullOrEmpty=El archivo no debe ser nulo o vacío.
|
database.fileNullOrEmpty=El archivo no debe ser nulo o vacío.
|
||||||
database.failedImportFile=Archivo de importación fallido
|
database.failedImportFile=Archivo de importación fallido
|
||||||
|
|
||||||
session.expired=Tu sesión ha caducado. Actualice la página e inténtelo de nuevo.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -397,7 +392,7 @@ certSign.tags=autentificar,PEM,P12,oficial,encriptar
|
|||||||
|
|
||||||
home.removeCertSign.title=Quitar signo de certificado
|
home.removeCertSign.title=Quitar signo de certificado
|
||||||
home.removeCertSign.desc=Eliminar firma de certificado de PDF
|
home.removeCertSign.desc=Eliminar firma de certificado de PDF
|
||||||
removeCertSign.tags=autenticar,PEM,P12,oficial,desencriptar
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Diseño de varias páginas
|
home.pageLayout.title=Diseño de varias páginas
|
||||||
home.pageLayout.desc=Unir varias páginas de un documento PDF en una sola página
|
home.pageLayout.desc=Unir varias páginas de un documento PDF en una sola página
|
||||||
@@ -508,28 +503,28 @@ home.removeImagePdf.desc=Eliminar imagen del PDF> para reducir el tamaño de arc
|
|||||||
removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor
|
removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Dividir PDF por capítulos
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Divida un PDF en varios archivos según su estructura de capítulos.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=dividir,capítulos,marcadores,organizar
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Reemplazar-Invertir-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Reemplazar-Invertir Color en PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Reemplazar e Invertir Color
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Reemplaza el color del texto y el fondo en el PDF e invierte el color completo del PDF para reducir el tamaño del archivo
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Reemplazar Color,Operaciones de Página,Back end,Lado del servidor
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Opciones para Reemplazar o Invertir color
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Predeterminado (Colores de alto contraste predeterminados)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Personalizado (Colores personalizados)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Invertir Completo (Invertir todos los colores)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Opciones de color de alto contraste
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Texto blanco sobre fondo negro
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Texto negro sobre fondo blanco
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Texto amarillo sobre fondo negro
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Texto verde sobre fondo negro
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Elegir Color de Texto
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Elegir Color de Fondo
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Reemplazar
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -548,7 +543,7 @@ login.locked=Su cuenta se ha bloqueado.
|
|||||||
login.signinTitle=Por favor, inicie sesión
|
login.signinTitle=Por favor, inicie sesión
|
||||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
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.oauth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO
|
||||||
login.oauth2AdminBlockedUser=El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, contáctese con el administrador.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Solicitud de autorización no encontrada
|
login.oauth2RequestNotFound=Solicitud de autorización no encontrada
|
||||||
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
|
||||||
login.oauth2invalidRequest=Solicitud no válida
|
login.oauth2invalidRequest=Solicitud no válida
|
||||||
@@ -556,9 +551,10 @@ login.oauth2AccessDenied=Acceso denegado
|
|||||||
login.oauth2InvalidTokenResponse=Respuesta de token no válida
|
login.oauth2InvalidTokenResponse=Respuesta de token no válida
|
||||||
login.oauth2InvalidIdToken=Token de identificación no válido
|
login.oauth2InvalidIdToken=Token de identificación no válido
|
||||||
login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador.
|
login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador.
|
||||||
login.alreadyLoggedIn=Ya has iniciado sesión en
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Tienes demasiadas sesiones activas
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redactar
|
autoRedact.title=Auto Redactar
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=Mostrar firma
|
|||||||
certSign.reason=Razón
|
certSign.reason=Razón
|
||||||
certSign.location=Ubicación
|
certSign.location=Ubicación
|
||||||
certSign.name=Nombre
|
certSign.name=Nombre
|
||||||
certSign.showLogo=Mostrar Logotipo
|
|
||||||
certSign.submit=Firmar PDF
|
certSign.submit=Firmar PDF
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=Color resaltado 2:
|
|||||||
compare.document.1=Documento 1
|
compare.document.1=Documento 1
|
||||||
compare.document.2=Documento 2
|
compare.document.2=Documento 2
|
||||||
compare.submit=Comparar
|
compare.submit=Comparar
|
||||||
compare.complex.message=Uno o ambos de los documentos proporcionados son archivos grandes; la precisión de la comparación puede disminuir.
|
|
||||||
compare.large.file.message=Uno o ambos de los documentos proporcionados son demasiado grandes para procesarse.
|
|
||||||
compare.no.text.message=Uno o ambos de los PDF seleccionados no contienen contenido de texto. Por favor, elija PDFs con texto para la comparación.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Libros y Cómics a PDF
|
BookToPDF.title=Libros y Cómics a PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Dibujar firma
|
|||||||
sign.text=Entrada de texto
|
sign.text=Entrada de texto
|
||||||
sign.clear=Borrar
|
sign.clear=Borrar
|
||||||
sign.add=Agregar
|
sign.add=Agregar
|
||||||
sign.saved=firmas guardadas
|
|
||||||
sign.save=Guardar Firma
|
|
||||||
sign.personalSigs=Firmas Personales
|
|
||||||
sign.sharedSigs=Firmas compartidas
|
|
||||||
sign.noSavedSigs=No se encontraron firmas guardadas
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -901,7 +888,7 @@ addImage.title=Añadir imagen
|
|||||||
addImage.header=Añadir imagen de PDF
|
addImage.header=Añadir imagen de PDF
|
||||||
addImage.everyPage=¿Todas las páginas?
|
addImage.everyPage=¿Todas las páginas?
|
||||||
addImage.upload=Añadir imagen
|
addImage.upload=Añadir imagen
|
||||||
addImage.submit=Enviar imagen
|
addImage.submit=Añadir imagen
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(por ej., 1,3,2 o 4-8,2,10-12 o 2n-1)
|
|||||||
multiTool.title=Multi-herramienta PDF
|
multiTool.title=Multi-herramienta PDF
|
||||||
multiTool.header=Multi-herramienta PDF
|
multiTool.header=Multi-herramienta PDF
|
||||||
multiTool.uploadPrompts=Nombre del archivo
|
multiTool.uploadPrompts=Nombre del archivo
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=Ver PDF
|
viewPdf.title=Ver PDF
|
||||||
@@ -1148,7 +1124,7 @@ PDFToCSV.prompt=Elija una página para extraer la tabla
|
|||||||
PDFToCSV.submit=Extraer
|
PDFToCSV.submit=Extraer
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
split-by-size-or-count.title=Dividir PDF por tamaño o cantidad
|
split-by-size-or-count.title=Split PDF by Size or Count
|
||||||
split-by-size-or-count.header=Dividir PDF por tamaño o número
|
split-by-size-or-count.header=Dividir PDF por tamaño o número
|
||||||
split-by-size-or-count.type.label=Seleccionar tipo de división
|
split-by-size-or-count.type.label=Seleccionar tipo de división
|
||||||
split-by-size-or-count.type.size=Por tamaño
|
split-by-size-or-count.type.size=Por tamaño
|
||||||
@@ -1206,8 +1182,8 @@ licenses.license=Licencia
|
|||||||
survey.nav=Encuesta
|
survey.nav=Encuesta
|
||||||
survey.title=Encuesta Stirling-PDF
|
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.description=Stirling-PDF no tiene seguimiento, por lo que queremos escuchar a nuestros usuarios para mejorar Stirling-PDF.
|
||||||
survey.changes=Stirling-PDF ha cambiado desde la última encuesta! Para obtener más información, revise nuestro artículo de blog aquí:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Con estos cambios estamos obteniendo apoyo y financiamiento empresarial
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=¡Considere realizar nuestra encuesta!
|
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.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.button=Realizar encuesta
|
||||||
@@ -1235,13 +1211,15 @@ removeImage.removeImage=Eliminar imagen
|
|||||||
removeImage.submit=Eliminar imagen
|
removeImage.submit=Eliminar imagen
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Dividir PDF por Capítulos
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=Dividir PDF por Capítulos
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Nivel de Marcador
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Incluir Metadatos
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Permitir Duplicados
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Esta herramienta divide un archivo PDF en múltiples archivos PDF según su estructura de capítulos.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Nivel de Marcador: Elige el nivel de marcadores para dividir (0 para el nivel superior, 1 para el segundo nivel, etc.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Incluir Metadatos: Si está seleccionado, los metadatos del PDF original se incluirán en cada PDF dividido.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Permitir Duplicados: Si está seleccionado, permite que múltiples marcadores en la misma página creen archivos PDF separados.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Dividir PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -79,9 +79,6 @@ info=Info
|
|||||||
pro=Pro
|
pro=Pro
|
||||||
page=Page
|
page=Page
|
||||||
pages=Pages
|
pages=Pages
|
||||||
loading=Loading...
|
|
||||||
addToDoc=Add to Document
|
|
||||||
reset=Reset
|
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -142,7 +139,6 @@ navbar.language=Languages
|
|||||||
navbar.settings=Ezarpenak
|
navbar.settings=Ezarpenak
|
||||||
navbar.allTools=Tools
|
navbar.allTools=Tools
|
||||||
navbar.multiTool=Multi Tools
|
navbar.multiTool=Multi Tools
|
||||||
navbar.search=Search
|
|
||||||
navbar.sections.organize=Organize
|
navbar.sections.organize=Organize
|
||||||
navbar.sections.convertTo=Convert to PDF
|
navbar.sections.convertTo=Convert to PDF
|
||||||
navbar.sections.convertFrom=Convert from PDF
|
navbar.sections.convertFrom=Convert from PDF
|
||||||
@@ -248,7 +244,6 @@ database.fileNullOrEmpty=File must not be null or empty
|
|||||||
database.failedImportFile=Failed Import File
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Your session has expired. Please refresh the page and try again.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
session.refreshPage=Refresh Page
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -559,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Idatzi
|
autoRedact.title=Auto Idatzi
|
||||||
@@ -753,7 +749,6 @@ certSign.showSig=Erakutsi sinadura
|
|||||||
certSign.reason=Arrazoia
|
certSign.reason=Arrazoia
|
||||||
certSign.location=Kokalekua
|
certSign.location=Kokalekua
|
||||||
certSign.name=Izena
|
certSign.name=Izena
|
||||||
certSign.showLogo=Show Logo
|
|
||||||
certSign.submit=Sinatu PDFa
|
certSign.submit=Sinatu PDFa
|
||||||
|
|
||||||
|
|
||||||
@@ -788,9 +783,6 @@ compare.highlightColor.2=Highlight Color 2:
|
|||||||
compare.document.1=1. dokumentua
|
compare.document.1=1. dokumentua
|
||||||
compare.document.2=2. dokumentua
|
compare.document.2=2. dokumentua
|
||||||
compare.submit=Konparatu
|
compare.submit=Konparatu
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Books and Comics to PDF
|
BookToPDF.title=Books and Comics to PDF
|
||||||
@@ -813,11 +805,6 @@ sign.draw=Marraztu sinadura
|
|||||||
sign.text=Testua sartzea
|
sign.text=Testua sartzea
|
||||||
sign.clear=Garbitu
|
sign.clear=Garbitu
|
||||||
sign.add=Gehitu
|
sign.add=Gehitu
|
||||||
sign.saved=Saved Signatures
|
|
||||||
sign.save=Save Signature
|
|
||||||
sign.personalSigs=Personal Signatures
|
|
||||||
sign.sharedSigs=Shared Signatures
|
|
||||||
sign.noSavedSigs=No saved signatures found
|
|
||||||
|
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
@@ -935,17 +922,6 @@ pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
|||||||
multiTool.title=PDF erabilera anitzeko tresna
|
multiTool.title=PDF erabilera anitzeko tresna
|
||||||
multiTool.header=PDF erabilera anitzeko tresna
|
multiTool.header=PDF erabilera anitzeko tresna
|
||||||
multiTool.uploadPrompts=File Name
|
multiTool.uploadPrompts=File Name
|
||||||
multiTool.selectAll=Select All
|
|
||||||
multiTool.deselectAll=Deselect All
|
|
||||||
multiTool.selectPages=Page Select
|
|
||||||
multiTool.selectedPages=Selected Pages
|
|
||||||
multiTool.page=Page
|
|
||||||
multiTool.deleteSelected=Delete Selected
|
|
||||||
multiTool.downloadAll=Export
|
|
||||||
multiTool.downloadSelected=Export Selected
|
|
||||||
|
|
||||||
#multiTool-advert
|
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=View PDF
|
viewPdf.title=View PDF
|
||||||
@@ -1245,3 +1221,5 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for
|
|||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user