Compare commits

..

2 Commits

Author SHA1 Message Date
Anthony Stirling
2c1e68d3ee fix 2024-12-30 13:43:58 +00:00
Anthony Stirling
a7df318a54 move docs to website 2024-12-30 13:42:19 +00:00
105 changed files with 748 additions and 2129 deletions

View File

@@ -49,17 +49,12 @@ Documentation:
Docker: Docker:
- changed-files: - changed-files:
- any-glob-to-any-file: '.github/workflows/build.yml'
- any-glob-to-any-file: '.github/workflows/push-docker.yml'
- 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/download-security-jar.sh'
- any-glob-to-any-file: 'scripts/init.sh' - any-glob-to-any-file: 'scripts/init.sh'
- any-glob-to-any-file: 'scripts/init-without-ocr.sh' - any-glob-to-any-file: 'scripts/init-without-ocr.sh'
- any-glob-to-any-file: 'scripts/installFonts.sh' - any-glob-to-any-file: 'scripts/installFonts.sh'
- any-glob-to-any-file: 'test.sh'
- any-glob-to-any-file: 'test2.sh'
Test: Test:
- changed-files: - changed-files:

View File

@@ -4,15 +4,9 @@ on:
issue_comment: issue_comment:
types: [created] types: [created]
permissions:
contents: read
jobs: jobs:
check-comment: check-comment:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: read
issues: read
if: | if: |
github.event.issue.pull_request && github.event.issue.pull_request &&
( (
@@ -74,9 +68,6 @@ jobs:
deploy-pr: deploy-pr:
needs: check-comment needs: check-comment
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps: steps:
- name: Harden Runner - name: Harden Runner
@@ -107,9 +98,7 @@ jobs:
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: | run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0

View File

@@ -4,8 +4,7 @@ on:
pull_request: pull_request:
types: [opened, synchronize, reopened, closed] types: [opened, synchronize, reopened, closed]
permissions: permissions: read-all
contents: read
env: env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets

View File

@@ -3,8 +3,7 @@ on:
pull_request_target: pull_request_target:
types: [opened, synchronize] types: [opened, synchronize]
permissions: permissions: read-all
contents: read
jobs: jobs:
labeler: labeler:

View File

@@ -6,15 +6,13 @@ on:
pull_request: pull_request:
branches: ["main"] branches: ["main"]
permissions: permissions: read-all
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
actions: read
security-events: write security-events: write
strategy: strategy:
@@ -47,17 +45,6 @@ jobs:
env: env:
DOCKER_ENABLE_SECURITY: true DOCKER_ENABLE_SECURITY: true
- name: Upload Test Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports-jdk-${{ matrix.jdk-version }}
path: |
build/reports/tests/
build/test-results/
build/reports/problems/
retention-days: 3
docker-compose-tests: docker-compose-tests:
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' || # if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' && # (github.event_name == 'pull_request' &&
@@ -95,7 +82,7 @@ jobs:
- name: Install Docker Compose - name: Install Docker Compose
run: | run: |
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.32.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
- name: Set up Python - name: Set up Python

View File

@@ -8,14 +8,12 @@ on:
permissions: permissions:
contents: read # Allow read access to repository content contents: read # Allow read access to repository content
issues: write # Allow posting comments on issues/PRs
jobs: jobs:
check-files: check-files:
if: github.event_name == 'pull_request_target' if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
issues: write # Allow posting comments on issues/PRs
pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
@@ -30,141 +28,61 @@ jobs:
with: with:
python-version: "3.x" python-version: "3.x"
- name: Get PR data
id: get-pr-data
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const prNumber = context.payload.pull_request.number;
const repoOwner = context.payload.repository.owner.login;
const repoName = context.payload.repository.name;
const branch = context.payload.pull_request.head.ref;
console.log(`PR Number: ${prNumber}`);
console.log(`Repo Owner: ${repoOwner}`);
console.log(`Repo Name: ${repoName}`);
console.log(`Branch: ${branch}`);
core.setOutput("pr_number", prNumber);
core.setOutput("repo_owner", repoOwner);
core.setOutput("repo_name", repoName);
core.setOutput("branch", branch);
continue-on-error: true
- name: Fetch PR changed files - name: Fetch PR changed files
id: fetch-pr-changes id: fetch-pr-changes
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
echo "Fetching PR changed files..." echo "Fetching PR changed files..."
gh repo set-default ${{ github.event.pull_request.head.repo.full_name }} # Set the fork repository as default
# Fetch the list of changed files in the PR
echo "Getting list of changed files from PR..." echo "Getting list of changed files from PR..."
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > changed_files.txt # Filter only matching property files
- name: Determine reference file test - name: Determine reference file
id: determine-file id: determine-file
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 env:
with: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
script: | run: |
const fs = require("fs"); echo "Determining reference file..."
const path = require("path"); REPO_OWNER=$(gh pr view ${{ github.event.pull_request.number }} --json author -q '.author.login') # Get PR author's username
REPO_NAME=$(gh pr view ${{ github.event.pull_request.number }} --json headRepository -q '.headRepository.name') # Get PR repository name
BRANCH=$(gh pr view ${{ github.event.pull_request.number }} --json headRefName -q '.headRefName') # Get PR branch name
const prNumber = ${{ steps.get-pr-data.outputs.pr_number }}; mkdir -p pr-branch # Create a directory for PR files
const repoOwner = "${{ steps.get-pr-data.outputs.repo_owner }}";
const repoName = "${{ steps.get-pr-data.outputs.repo_name }}";
const prRepoOwner = "${{ github.event.pull_request.head.repo.owner.login }}"; # Download the content of each changed file
const prRepoName = "${{ github.event.pull_request.head.repo.name }}"; while IFS= read -r file; do
const branch = "${{ steps.get-pr-data.outputs.branch }}"; mkdir -p "pr-branch/$(dirname "$file")" # Create directories for files
gh api repos/$REPO_OWNER/$REPO_NAME/contents/$file?ref=$BRANCH --jq '.content' | base64 -d > "pr-branch/src/main/resources/$(basename "$file")" # Save decoded file content
done < changed_files.txt
console.log(`Determining reference file for PR #${prNumber}`); # Generate a list of files without the "pr-branch/" prefix
find pr-branch/ -type f | awk -F'pr-branch/' '{print $2}' > file_list.txt
// Validate inputs mapfile -t FILES_LIST < file_list.txt # Read the file list into an array
const validateInput = (input, regex, name) => { FILES_LIST_STR="${FILES_LIST[*]}" # Join array into a space-separated string
if (!regex.test(input)) { echo "FILES_LIST=${FILES_LIST_STR}" >> $GITHUB_ENV # Export the file list to the environment
throw new Error(`Invalid ${name}: ${input}`); echo "Changed files: ${FILES_LIST_STR}"
}
};
validateInput(repoOwner, /^[a-zA-Z0-9_-]+$/, "repository owner"); cat file_list.txt # Display the file list
validateInput(repoName, /^[a-zA-Z0-9._-]+$/, "repository name");
validateInput(branch, /^[a-zA-Z0-9._/-]+$/, "branch name");
// Get the list of changed files in the PR # Determine which reference file to use
const { data: files } = await github.rest.pulls.listFiles({ if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; then
owner: repoOwner, echo "Using PR branch reference file"
repo: repoName, REFERENCE_FILE="pr-branch-messages_en_GB.properties"
pull_number: prNumber, gh api repos/$REPO_OWNER/$REPO_NAME/contents/src/main/resources/messages_en_GB.properties?ref=${{ github.event.pull_request.head.ref }} \
}); --jq '.content' | base64 -d > $REFERENCE_FILE # Save PR branch reference file
else
echo "Using main branch reference file"
REFERENCE_FILE="main-branch-messages_en_GB.properties"
gh api repos/Ludy87/test_java/contents/src/main/resources/messages_en_GB.properties?ref=main \
--jq '.content' | base64 -d > $REFERENCE_FILE # Save main branch reference file
fi
// Filter for relevant files based on the PR changes echo "REFERENCE_FILE=$REFERENCE_FILE" >> $GITHUB_ENV # Export reference file path to the environment
const changedFiles = files
.map(file => file.filename)
.filter(file => /^src\/main\/resources\/messages_[a-zA-Z_]+\.properties$/.test(file));
console.log("Changed files:", changedFiles);
// Create a temporary directory for PR files
const tempDir = "pr-branch";
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
// Download and save each changed file
for (const file of changedFiles) {
const { data: fileContent } = await github.rest.repos.getContent({
owner: prRepoOwner,
repo: prRepoName,
path: file,
ref: branch,
});
const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
const filePath = path.join(tempDir, file);
const dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
fs.writeFileSync(filePath, content);
console.log(`Saved file: ${filePath}`);
}
// Output the list of changed files for further processing
const fileList = changedFiles.join(" ");
core.exportVariable("FILES_LIST", fileList);
console.log("Files saved and listed in FILES_LIST.");
// Determine reference file
let referenceFilePath;
if (changedFiles.includes("src/main/resources/messages_en_GB.properties")) {
console.log("Using PR branch reference file.");
const { data: fileContent } = await github.rest.repos.getContent({
owner: prRepoOwner,
repo: prRepoName,
path: "src/main/resources/messages_en_GB.properties",
ref: branch,
});
referenceFilePath = "pr-branch-messages_en_GB.properties";
const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
fs.writeFileSync(referenceFilePath, content);
} else {
console.log("Using main branch reference file.");
const { data: fileContent } = await github.rest.repos.getContent({
owner: repoOwner,
repo: repoName,
path: "src/main/resources/messages_en_GB.properties",
ref: "main",
});
referenceFilePath = "main-branch-messages_en_GB.properties";
const content = Buffer.from(fileContent.content, "base64").toString("utf-8");
fs.writeFileSync(referenceFilePath, content);
}
console.log(`Reference file path: ${referenceFilePath}`);
core.exportVariable("REFERENCE_FILE", referenceFilePath);
- name: Run Python script to check files - name: Run Python script to check files
id: run-check id: run-check
@@ -174,8 +92,7 @@ jobs:
--actor ${{ github.event.pull_request.user.login }} \ --actor ${{ github.event.pull_request.user.login }} \
--reference-file "${REFERENCE_FILE}" \ --reference-file "${REFERENCE_FILE}" \
--branch "pr-branch" \ --branch "pr-branch" \
--files "${FILES_LIST[@]}" > result.txt --files "${FILES_LIST[@]}" > result.txt || true
continue-on-error: true # Continue the job even if this step fails
- name: Capture output - name: Capture output
id: capture-output id: capture-output
@@ -207,13 +124,13 @@ jobs:
script: | script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env; const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/'); const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const issueNumber = context.issue.number; const prNumber = context.issue.number;
// Find existing comment // Find existing comment
const comments = await github.rest.issues.listComments({ const comments = await github.rest.issues.listComments({
owner: repoOwner, owner: repoOwner,
repo: repoName, repo: repoName,
issue_number: issueNumber issue_number: prNumber
}); });
const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary")); const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary"));
@@ -235,7 +152,7 @@ jobs:
await github.rest.issues.createComment({ await github.rest.issues.createComment({
owner: repoOwner, owner: repoOwner,
repo: repoName, repo: repoName,
issue_number: issueNumber, issue_number: prNumber,
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
}); });
console.log("Created new comment."); console.log("Created new comment.");

View File

@@ -7,8 +7,7 @@ on:
paths: paths:
- "build.gradle" - "build.gradle"
permissions: permissions: read-all
contents: read
jobs: jobs:
generate-license-report: generate-license-report:
@@ -53,7 +52,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
if: env.CHANGES_DETECTED == 'true' if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses" commit-message: "Update 3rd Party Licenses"

View File

@@ -4,8 +4,7 @@ on:
schedule: schedule:
- cron: "30 20 * * *" - cron: "30 20 * * *"
permissions: permissions: read-all
contents: read
jobs: jobs:
labeler: labeler:

View File

@@ -5,8 +5,7 @@ on:
release: release:
types: [created] types: [created]
permissions: permissions: read-all
contents: read
jobs: jobs:
build-installers: build-installers:
@@ -43,7 +42,7 @@ jobs:
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.7
# Install Windows dependencies # Install Windows dependencies
- name: Install WiX Toolset - name: Install WiX Toolset

View File

@@ -4,8 +4,7 @@ on:
push: push:
branches: [main] branches: [main]
permissions: permissions: read-all
contents: read
jobs: jobs:
update: update:
@@ -20,7 +19,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 uses: actions/setup-python@v5
with: with:
python-version: 3.12 python-version: 3.12
- name: Run Pre-Commit Hooks - name: Run Pre-Commit Hooks
@@ -36,7 +35,7 @@ jobs:
git diff --staged --quiet || git commit -m ":file_folder: pre-commit git diff --staged --quiet || git commit -m ":file_folder: pre-commit
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes" > Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "ci: 🤖 format everything with pre-commit" commit-message: "ci: 🤖 format everything with pre-commit"

View File

@@ -9,13 +9,14 @@ on:
permissions: permissions:
contents: read contents: read
packages: write
id-token: write
jobs: jobs:
push: push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
packages: write packages: write
id-token: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
@@ -32,7 +33,7 @@ jobs:
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.7
- name: Run Gradle Command - name: Run Gradle Command
run: ./gradlew clean build run: ./gradlew clean build
@@ -41,7 +42,7 @@ jobs:
- name: Install cosign - name: Install cosign
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 uses: sigstore/cosign-installer@v3.7.0
with: with:
cosign-release: 'v2.4.1' cosign-release: 'v2.4.1'

View File

@@ -5,8 +5,7 @@ on:
release: release:
types: [created] types: [created]
permissions: permissions: read-all
contents: read
jobs: jobs:
push: push:
@@ -38,7 +37,7 @@ jobs:
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2 - uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with: with:
gradle-version: 8.12 gradle-version: 8.7
- name: Generate jar (With Security=${{ matrix.enable_security }}) - name: Generate jar (With Security=${{ matrix.enable_security }})
run: ./gradlew clean createExe run: ./gradlew clean createExe

View File

@@ -5,8 +5,7 @@ on:
- cron: "30 0 * * *" - cron: "30 0 * * *"
workflow_dispatch: workflow_dispatch:
permissions: permissions: read-all
contents: read
jobs: jobs:
stale: stale:

View File

@@ -6,8 +6,7 @@ on:
branches: branches:
- master - master
permissions: permissions: read-all
contents: read
jobs: jobs:
push: push:

View File

@@ -9,8 +9,7 @@ on:
- "src/main/resources/messages_*.properties" - "src/main/resources/messages_*.properties"
- "scripts/ignore_translation.toml" - "scripts/ignore_translation.toml"
permissions: permissions: read-all
contents: read
jobs: jobs:
sync-readme: sync-readme:
@@ -43,7 +42,7 @@ jobs:
git diff --staged --quiet || git commit -m ":memo: Sync README git diff --staged --quiet || git commit -m ":memo: Sync README
> Made via sync_files.yml" || echo "no changes" > Made via sync_files.yml" || echo "no changes"
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update files commit-message: Update files

View File

@@ -6,8 +6,7 @@ on:
paths: paths:
- "src/main/resources/messages_en_GB.properties" - "src/main/resources/messages_en_GB.properties"
permissions: permissions: read-all
contents: read
jobs: jobs:
update-translations-main: update-translations-main:
@@ -51,7 +50,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
if: env.CHANGES_DETECTED == 'true' if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files" commit-message: "Update translation files"

20
.gitignore vendored
View File

@@ -146,34 +146,16 @@ out/
# cucumber # cucumber
/cucumber/reports/** /cucumber/reports/**
# Certs and Security Files # Certs
*.p12 *.p12
*.pk8
*.pem *.pem
*.crt *.crt
*.cer *.cer
*.cert
*.der *.der
*.key *.key
*.csr *.csr
*.kdbx
*.jks
*.asc
# SSH Keys
*.pub
*.priv
id_rsa
id_rsa.pub
id_ecdsa
id_ecdsa.pub
id_ed25519
id_ed25519.pub
.ssh/
*ssh
# cache # cache
.cache
.ruff_cache .ruff_cache
.mypy_cache .mypy_cache
.pytest_cache .pytest_cache

View File

@@ -1,5 +1,5 @@
# Build the application # Build the application
FROM gradle:8.12-jdk17 AS build FROM gradle:8.11-jdk17 AS build
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app

View File

@@ -4,9 +4,9 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) [![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU) [![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU)
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Stirling-Tools/Stirling-PDF/badge)](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
<a href="https://www.producthunt.com/posts/stirling-pdf?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-stirling&#0045;pdf" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=641239&theme=light" alt="Stirling&#0032;PDF - Open&#0032;source&#0032;locally&#0032;hosted&#0032;web&#0032;PDF&#0032;editor | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/stirling-pdf?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-stirling&#0045;pdf" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=641239&theme=light" alt="Stirling&#0032;PDF - Open&#0032;source&#0032;locally&#0032;hosted&#0032;web&#0032;PDF&#0032;editor | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af) [![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
@@ -116,43 +116,43 @@ Stirling-PDF currently supports 38 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![93%](https://geps.dev/progress/93) | | Arabic (العربية) (ar_AR) | ![94%](https://geps.dev/progress/94) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![92%](https://geps.dev/progress/92) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![92%](https://geps.dev/progress/92) |
| Basque (Euskara) (eu_ES) | ![53%](https://geps.dev/progress/53) | | Basque (Euskara) (eu_ES) | ![53%](https://geps.dev/progress/53) |
| Bulgarian (Български) (bg_BG) | ![89%](https://geps.dev/progress/89) | | Bulgarian (Български) (bg_BG) | ![89%](https://geps.dev/progress/89) |
| Catalan (Català) (ca_CA) | ![84%](https://geps.dev/progress/84) | | Catalan (Català) (ca_CA) | ![84%](https://geps.dev/progress/84) |
| Croatian (Hrvatski) (hr_HR) | ![90%](https://geps.dev/progress/90) | | Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) |
| Czech (Česky) (cs_CZ) | ![90%](https://geps.dev/progress/90) | | Czech (Česky) (cs_CZ) | ![90%](https://geps.dev/progress/90) |
| Danish (Dansk) (da_DK) | ![89%](https://geps.dev/progress/89) | | Danish (Dansk) (da_DK) | ![89%](https://geps.dev/progress/89) |
| Dutch (Nederlands) (nl_NL) | ![89%](https://geps.dev/progress/89) | | Dutch (Nederlands) (nl_NL) | ![89%](https://geps.dev/progress/89) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) | | French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![90%](https://geps.dev/progress/90) | | Greek (Ελληνικά) (el_GR) | ![90%](https://geps.dev/progress/90) |
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) | | Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) |
| Hungarian (Magyar) (hu_HU) | ![90%](https://geps.dev/progress/90) | | Hungarian (Magyar) (hu_HU) | ![91%](https://geps.dev/progress/91) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![90%](https://geps.dev/progress/90) | | Indonesian (Bahasa Indonesia) (id_ID) | ![90%](https://geps.dev/progress/90) |
| Irish (Gaeilge) (ga_IE) | ![82%](https://geps.dev/progress/82) | | Irish (Gaeilge) (ga_IE) | ![82%](https://geps.dev/progress/82) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) | | Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![89%](https://geps.dev/progress/89) | | Korean (한국어) (ko_KR) | ![89%](https://geps.dev/progress/89) |
| Norwegian (Norsk) (no_NB) | ![82%](https://geps.dev/progress/82) | | Norwegian (Norsk) (no_NB) | ![82%](https://geps.dev/progress/82) |
| Persian (فارسی) (fa_IR) | ![98%](https://geps.dev/progress/98) | | Persian (فارسی) (fa_IR) | ![99%](https://geps.dev/progress/99) |
| Polish (Polski) (pl_PL) | ![89%](https://geps.dev/progress/89) | | Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) |
| Portuguese (Português) (pt_PT) | ![90%](https://geps.dev/progress/90) | | Portuguese (Português) (pt_PT) | ![90%](https://geps.dev/progress/90) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) | | Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![84%](https://geps.dev/progress/84) | | Romanian (Română) (ro_RO) | ![84%](https://geps.dev/progress/84) |
| Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) | | Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![66%](https://geps.dev/progress/66) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![67%](https://geps.dev/progress/67) |
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) | | Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![77%](https://geps.dev/progress/77) | | Slovakian (Slovensky) (sk_SK) | ![78%](https://geps.dev/progress/78) |
| Spanish (Español) (es_ES) | ![90%](https://geps.dev/progress/90) | | Spanish (Español) (es_ES) | ![91%](https://geps.dev/progress/91) |
| Swedish (Svenska) (sv_SE) | ![90%](https://geps.dev/progress/90) | | Swedish (Svenska) (sv_SE) | ![90%](https://geps.dev/progress/90) |
| Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) | | Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) | | Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) | | Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) |
| Ukrainian (Українська) (uk_UA) | ![75%](https://geps.dev/progress/75) | | Ukrainian (Українська) (uk_UA) | ![76%](https://geps.dev/progress/76) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![83%](https://geps.dev/progress/83) | | Vietnamese (Tiếng Việt) (vi_VN) | ![83%](https://geps.dev/progress/83) |

View File

@@ -27,7 +27,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.36.6" version = "0.36.5"
java { java {
@@ -123,13 +123,11 @@ jpackage {
windows { windows {
launcherAsService = false launcherAsService = false
appVersion = project.version appVersion = project.version
winConsole = false
winConsole = false winDirChooser = true
winMenu = true // Creates start menu entry winMenu = true
winShortcut = true // Creates desktop shortcut winShortcut = true
winShortcutPrompt = true // Lets user choose whether to create shortcuts winPerUserInstall = true
winDirChooser = true // Allows users to choose installation directory
winPerUserInstall = false
winMenuGroup = "Stirling Software" winMenuGroup = "Stirling Software"
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF" winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
@@ -259,7 +257,7 @@ spotless {
// rules=['unused-dependency'] // rules=['unused-dependency']
// } // }
tasks.wrapper { tasks.wrapper {
gradleVersion = "8.12" gradleVersion = "8.7"
} }
//tasks.withType(JavaCompile) { //tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation" // options.compilerArgs << "-Xlint:deprecation"

View File

@@ -66,7 +66,7 @@ Feature: API Validation
| pageNumbers | file_count | | pageNumbers | file_count |
| 1,3,5-9 | 8 | | 1,3,5-9 | 8 |
| all | 20 | | all | 20 |
| 2n+1 | 10 | | 2n+1 | 11 |
| 3n | 7 | | 3n | 7 |

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -25,7 +24,6 @@ import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.UI.WebBrowser; import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@SpringBootApplication @SpringBootApplication
@@ -78,27 +76,15 @@ public class SPdfApplication {
props.put("spring.main.web-application-type", "servlet"); props.put("spring.main.web-application-type", "servlet");
} }
app.setAdditionalProfiles("default"); app.setAdditionalProfiles("default");
app.addInitializers(new ConfigInitializer());
ConfigInitializer initializer = new ConfigInitializer();
try {
initializer.ensureConfigExists();
} catch (IOException | URISyntaxException e) {
log.error("Error initialising configuration", e);
}
Map<String, String> propertyFiles = new HashMap<>(); Map<String, String> propertyFiles = new HashMap<>();
// External config files // External config files
log.info("Settings file: {}", InstallationPathConfig.getSettingsPath()); if (Files.exists(Paths.get("configs/settings.yml"))) {
if (Files.exists(Paths.get(InstallationPathConfig.getSettingsPath()))) { propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
propertyFiles.put(
"spring.config.additional-location",
"file:" + InstallationPathConfig.getSettingsPath());
} else { } else {
log.warn( log.warn("External configuration file 'configs/settings.yml' does not exist.");
"External configuration file '{}' does not exist.",
InstallationPathConfig.getSettingsPath());
} }
if (Files.exists(Paths.get(InstallationPathConfig.getCustomSettingsPath()))) { if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
String existingLocation = String existingLocation =
propertyFiles.getOrDefault("spring.config.additional-location", ""); propertyFiles.getOrDefault("spring.config.additional-location", "");
if (!existingLocation.isEmpty()) { if (!existingLocation.isEmpty()) {
@@ -106,11 +92,9 @@ public class SPdfApplication {
} }
propertyFiles.put( propertyFiles.put(
"spring.config.additional-location", "spring.config.additional-location",
existingLocation + "file:" + InstallationPathConfig.getCustomSettingsPath()); existingLocation + "file:configs/custom_settings.yml");
} else { } else {
log.warn( log.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
"Custom configuration file '{}' does not exist.",
InstallationPathConfig.getCustomSettingsPath());
} }
Properties finalProps = new Properties(); Properties finalProps = new Properties();
if (!propertyFiles.isEmpty()) { if (!propertyFiles.isEmpty()) {
@@ -126,8 +110,8 @@ public class SPdfApplication {
app.run(args); app.run(args);
// Ensure directories are created // Ensure directories are created
try { try {
Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath())); Files.createDirectories(Path.of("customFiles/static/"));
Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath())); Files.createDirectories(Path.of("customFiles/templates/"));
} catch (Exception e) { } catch (Exception e) {
log.error("Error creating directories: {}", e.getMessage()); log.error("Error creating directories: {}", e.getMessage());
} }

View File

@@ -40,7 +40,6 @@ import me.friwi.jcefmaven.EnumProgress;
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter; import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler; import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
import stirling.software.SPDF.UI.WebBrowser; import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.SPDF.config.InstallationPathConfig;
@Component @Component
@Slf4j @Slf4j
@@ -73,8 +72,7 @@ public class DesktopBrowser implements WebBrowser {
CefAppBuilder builder = new CefAppBuilder(); CefAppBuilder builder = new CefAppBuilder();
configureCefSettings(builder); configureCefSettings(builder);
builder.setProgressHandler(createProgressHandler()); builder.setProgressHandler(createProgressHandler());
builder.setInstallDir(
new File(InstallationPathConfig.getClientWebUIPath()));
// Build and initialize CEF // Build and initialize CEF
cefApp = builder.build(); cefApp = builder.build();
client = cefApp.createClient(); client = cefApp.createClient();
@@ -101,16 +99,8 @@ public class DesktopBrowser implements WebBrowser {
private void configureCefSettings(CefAppBuilder builder) { private void configureCefSettings(CefAppBuilder builder) {
CefSettings settings = builder.getCefSettings(); CefSettings settings = builder.getCefSettings();
String basePath = InstallationPathConfig.getClientWebUIPath(); settings.cache_path = new File("jcef-bundle").getAbsolutePath();
log.info("basePath " + basePath); settings.root_cache_path = new File("jcef-bundle").getAbsolutePath();
settings.cache_path = new File(basePath + "cache").getAbsolutePath();
settings.root_cache_path = new File(basePath + "root_cache").getAbsolutePath();
// settings.browser_subprocess_path = new File(basePath +
// "subprocess").getAbsolutePath();
// settings.resources_dir_path = new File(basePath + "resources").getAbsolutePath();
// settings.locales_dir_path = new File(basePath + "locales").getAbsolutePath();
settings.log_file = new File(basePath, "debug.log").getAbsolutePath();
settings.persist_session_cookies = true; settings.persist_session_cookies = true;
settings.windowless_rendering_enabled = false; settings.windowless_rendering_enabled = false;
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO; settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
@@ -222,9 +212,6 @@ public class DesktopBrowser implements WebBrowser {
} }
private void setupLoadHandler() { private void setupLoadHandler() {
final long initStartTime = System.currentTimeMillis();
log.info("Setting up load handler at: {}", initStartTime);
client.addLoadHandler( client.addLoadHandler(
new CefLoadHandlerAdapter() { new CefLoadHandlerAdapter() {
@Override @Override
@@ -233,77 +220,32 @@ public class DesktopBrowser implements WebBrowser {
boolean isLoading, boolean isLoading,
boolean canGoBack, boolean canGoBack,
boolean canGoForward) { boolean canGoForward) {
log.debug(
"Loading state change - isLoading: {}, canGoBack: {}, canGoForward: {}, "
+ "browserInitialized: {}, Time elapsed: {}ms",
isLoading,
canGoBack,
canGoForward,
browserInitialized,
System.currentTimeMillis() - initStartTime);
if (!isLoading && !browserInitialized) { if (!isLoading && !browserInitialized) {
log.info(
"Browser finished loading, preparing to initialize UI components");
browserInitialized = true; browserInitialized = true;
SwingUtilities.invokeLater( SwingUtilities.invokeLater(
() -> { () -> {
try { if (loadingWindow != null) {
if (loadingWindow != null) { Timer timer =
log.info("Starting UI initialization sequence"); new Timer(
500,
e -> {
loadingWindow.dispose();
loadingWindow = null;
// Close loading window first frame.dispose();
loadingWindow.setVisible(false); frame.setOpacity(1.0f);
loadingWindow.dispose(); frame.setUndecorated(false);
loadingWindow = null; frame.pack();
log.info("Loading window disposed"); frame.setSize(1280, 800);
frame.setLocationRelativeTo(null);
// Then setup the main frame frame.setVisible(true);
frame.setVisible(false); frame.requestFocus();
frame.dispose(); frame.toFront();
frame.setOpacity(1.0f); browser.getUIComponent()
frame.setUndecorated(false); .requestFocus();
frame.pack(); });
frame.setSize(1280, 800); timer.setRepeats(false);
frame.setLocationRelativeTo(null); timer.start();
log.debug("Frame reconfigured");
// Show the main frame
frame.setVisible(true);
frame.requestFocus();
frame.toFront();
log.info("Main frame displayed and focused");
// Focus the browser component
Timer focusTimer =
new Timer(
100,
e -> {
try {
browser.getUIComponent()
.requestFocus();
log.info(
"Browser component focused");
} catch (Exception ex) {
log.error(
"Error focusing browser",
ex);
}
});
focusTimer.setRepeats(false);
focusTimer.start();
}
} catch (Exception e) {
log.error("Error during UI initialization", e);
// Attempt cleanup on error
if (loadingWindow != null) {
loadingWindow.dispose();
loadingWindow = null;
}
if (frame != null) {
frame.setVisible(true);
frame.requestFocus();
}
} }
}); });
} }

View File

@@ -14,12 +14,9 @@ public class LoadingWindow extends JDialog {
private final JLabel statusLabel; private final JLabel statusLabel;
private final JPanel mainPanel; private final JPanel mainPanel;
private final JLabel brandLabel; private final JLabel brandLabel;
private long startTime;
public LoadingWindow(Frame parent, String initialUrl) { public LoadingWindow(Frame parent, String initialUrl) {
super(parent, "Initializing Stirling-PDF", true); super(parent, "Initializing Stirling-PDF", true);
startTime = System.currentTimeMillis();
log.info("Creating LoadingWindow - initialization started at: {}", startTime);
// Initialize components // Initialize components
mainPanel = new JPanel(); mainPanel = new JPanel();
@@ -32,8 +29,8 @@ public class LoadingWindow extends JDialog {
gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL; gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5); gbc.insets = new Insets(5, 5, 5, 5);
gbc.weightx = 1.0; gbc.weightx = 1.0; // Add horizontal weight
gbc.weighty = 0.0; gbc.weighty = 0.0; // Add vertical weight
// Add icon // Add icon
try { try {
@@ -46,14 +43,12 @@ public class LoadingWindow extends JDialog {
iconLabel.setHorizontalAlignment(SwingConstants.CENTER); iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
gbc.gridy = 0; gbc.gridy = 0;
mainPanel.add(iconLabel, gbc); mainPanel.add(iconLabel, gbc);
log.debug("Icon loaded and scaled successfully");
} }
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to load icon", e); log.error("Failed to load icon", e);
} }
// URL Label with explicit size // URL Label with explicit size
brandLabel = new JLabel(initialUrl); brandLabel = new JLabel(initialUrl);
brandLabel.setHorizontalAlignment(SwingConstants.CENTER); brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
@@ -68,7 +63,6 @@ public class LoadingWindow extends JDialog {
statusLabel.setPreferredSize(new Dimension(300, 25)); statusLabel.setPreferredSize(new Dimension(300, 25));
gbc.gridy = 2; gbc.gridy = 2;
mainPanel.add(statusLabel, gbc); mainPanel.add(statusLabel, gbc);
// Progress bar with explicit size // Progress bar with explicit size
progressBar = new JProgressBar(0, 100); progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true); progressBar.setStringPainted(true);
@@ -88,78 +82,33 @@ public class LoadingWindow extends JDialog {
setAlwaysOnTop(true); setAlwaysOnTop(true);
setProgress(0); setProgress(0);
setStatus("Starting..."); setStatus("Starting...");
log.info(
"LoadingWindow initialization completed in {}ms",
System.currentTimeMillis() - startTime);
} }
public void setProgress(final int progress) { public void setProgress(final int progress) {
SwingUtilities.invokeLater( SwingUtilities.invokeLater(
() -> { () -> {
try { try {
int validProgress = Math.min(Math.max(progress, 0), 100); progressBar.setValue(Math.min(Math.max(progress, 0), 100));
log.info( progressBar.setString(progress + "%");
"Setting progress to {}% at {}ms since start",
validProgress, System.currentTimeMillis() - startTime);
// Log additional details when near 90%
if (validProgress >= 85 && validProgress <= 95) {
log.info(
"Near 90% progress - Current status: {}, Window visible: {}, "
+ "Progress bar responding: {}, Memory usage: {}MB",
statusLabel.getText(),
isVisible(),
progressBar.isEnabled(),
Runtime.getRuntime().totalMemory() / (1024 * 1024));
// Add thread state logging
Thread currentThread = Thread.currentThread();
log.debug(
"Current thread state - Name: {}, State: {}, Priority: {}",
currentThread.getName(),
currentThread.getState(),
currentThread.getPriority());
}
progressBar.setValue(validProgress);
progressBar.setString(validProgress + "%");
mainPanel.revalidate(); mainPanel.revalidate();
mainPanel.repaint(); mainPanel.repaint();
} catch (Exception e) { } catch (Exception e) {
log.error("Error updating progress to " + progress, e); log.error("Error updating progress", e);
} }
}); });
} }
public void setStatus(final String status) { public void setStatus(final String status) {
log.info( log.info(status);
"Status update at {}ms - Setting status to: {}",
System.currentTimeMillis() - startTime,
status);
SwingUtilities.invokeLater( SwingUtilities.invokeLater(
() -> { () -> {
try { try {
String validStatus = status != null ? status : ""; statusLabel.setText(status != null ? status : "");
statusLabel.setText(validStatus);
// Log UI state when status changes
log.debug(
"UI State - Window visible: {}, Progress: {}%, Status: {}",
isVisible(), progressBar.getValue(), validStatus);
mainPanel.revalidate(); mainPanel.revalidate();
mainPanel.repaint(); mainPanel.repaint();
} catch (Exception e) { } catch (Exception e) {
log.error("Error updating status to: " + status, e); log.error("Error updating status", e);
} }
}); });
} }
@Override
public void dispose() {
log.info("LoadingWindow disposing after {}ms", System.currentTimeMillis() - startTime);
super.dispose();
}
} }

View File

@@ -136,6 +136,16 @@ public class AppConfig {
return false; return false;
} }
@Bean(name = "watchedFoldersDir")
public String watchedFoldersDir() {
return "./pipeline/watchedFolders/";
}
@Bean(name = "finishedFoldersDir")
public String finishedFoldersDir() {
return "./pipeline/finishedFolders/";
}
@Bean(name = "directoryFilter") @Bean(name = "directoryFilter")
public Predicate<Path> processOnlyFiles() { public Predicate<Path> processOnlyFiles() {
return path -> { return path -> {

View File

@@ -16,15 +16,27 @@ import org.simpleyaml.configuration.comments.CommentType;
import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation; import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions; import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class ConfigInitializer { public class ConfigInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
ensureConfigExists();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize application configuration", e);
}
}
public void ensureConfigExists() throws IOException, URISyntaxException { public void ensureConfigExists() throws IOException, URISyntaxException {
// Define the path to the external config directory // Define the path to the external config directory
Path destPath = Paths.get(InstallationPathConfig.getSettingsPath()); Path destPath = Paths.get("configs", "settings.yml");
// Check if the file already exists // Check if the file already exists
if (Files.notExists(destPath)) { if (Files.notExists(destPath)) {
@@ -41,11 +53,10 @@ public class ConfigInitializer {
"Resource file not found: settings.yml.template"); "Resource file not found: settings.yml.template");
} }
} }
log.info("Created settings file from template");
} else { } else {
// Define the path to the config settings file // Define the path to the config settings file
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath()); Path settingsPath = Paths.get("configs", "settings.yml");
// Load the template resource // Load the template resource
URL settingsTemplateResource = URL settingsTemplateResource =
getClass().getClassLoader().getResource("settings.yml.template"); getClass().getClassLoader().getResource("settings.yml.template");
@@ -109,7 +120,7 @@ public class ConfigInitializer {
} }
// Create custom settings file if it doesn't exist // Create custom settings file if it doesn't exist
Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath()); Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
if (!Files.exists(customSettingsPath)) { if (!Files.exists(customSettingsPath)) {
Files.createFile(customSettingsPath); Files.createFile(customSettingsPath);
} }

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -266,26 +265,20 @@ public class EndpointConfiguration {
} }
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {
if (applicationProperties != null && applicationProperties.getEndpoints() != null) { List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); if (!bookAndHtmlFormatsInstalled) {
groupsToRemove.add("Calibre");
if (!bookAndHtmlFormatsInstalled) { }
if (groupsToRemove == null) { if (endpointsToRemove != null) {
groupsToRemove = new ArrayList<>(); for (String endpoint : endpointsToRemove) {
} disableEndpoint(endpoint.trim());
groupsToRemove.add("Calibre");
}
if (endpointsToRemove != null) {
for (String endpoint : endpointsToRemove) {
disableEndpoint(endpoint.trim());
}
} }
}
if (groupsToRemove != null) { if (groupsToRemove != null) {
for (String group : groupsToRemove) { for (String group : groupsToRemove) {
disableGroup(group.trim()); disableGroup(group.trim());
}
} }
} }
} }

View File

@@ -33,8 +33,7 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe
String characterEncoding, String characterEncoding,
Map<String, Object> templateResolutionAttributes) { Map<String, Object> templateResolutionAttributes) {
Resource resource = Resource resource =
resourceLoader.getResource( resourceLoader.getResource("file:./customFiles/templates/" + resourceName);
"file:" + InstallationPathConfig.getTemplatesPath() + resourceName);
try { try {
if (resource.exists() && resource.isReadable()) { if (resource.exists() && resource.isReadable()) {
return new FileTemplateResource(resource.getFile().getPath(), characterEncoding); return new FileTemplateResource(resource.getFile().getPath(), characterEncoding);

View File

@@ -1,132 +0,0 @@
package stirling.software.SPDF.config;
import java.io.File;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class InstallationPathConfig {
private static final String BASE_PATH;
// Root paths
private static final String LOG_PATH;
private static final String CONFIG_PATH;
private static final String PIPELINE_PATH;
private static final String CUSTOM_FILES_PATH;
private static final String CLIENT_WEBUI_PATH;
// Config paths
private static final String SETTINGS_PATH;
private static final String CUSTOM_SETTINGS_PATH;
// Pipeline paths
private static final String PIPELINE_WATCHED_FOLDERS_PATH;
private static final String PIPELINE_FINISHED_FOLDERS_PATH;
// Custom file paths
private static final String STATIC_PATH;
private static final String TEMPLATES_PATH;
private static final String SIGNATURES_PATH;
static {
BASE_PATH = initializeBasePath();
// Initialize root paths
LOG_PATH = BASE_PATH + "logs" + File.separator;
CONFIG_PATH = BASE_PATH + "configs" + File.separator;
PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator;
CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator;
CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator;
// Initialize config paths
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
// Initialize pipeline paths
PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + File.separator;
PIPELINE_FINISHED_FOLDERS_PATH = PIPELINE_PATH + "finishedFolders" + File.separator;
// Initialize custom file paths
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
}
private static String initializeBasePath() {
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return System.getenv("APPDATA") + File.separator + "Stirling-PDF" + File.separator;
} else if (os.contains("mac")) {
return System.getProperty("user.home")
+ File.separator
+ "Library"
+ File.separator
+ "Application Support"
+ File.separator
+ "Stirling-PDF"
+ File.separator;
} else {
return System.getProperty("user.home")
+ File.separator
+ ".config"
+ File.separator
+ "Stirling-PDF"
+ File.separator;
}
}
return "./";
}
public static String getPath() {
return BASE_PATH;
}
public static String getLogPath() {
return LOG_PATH;
}
public static String getConfigPath() {
return CONFIG_PATH;
}
public static String getPipelinePath() {
return PIPELINE_PATH;
}
public static String getCustomFilesPath() {
return CUSTOM_FILES_PATH;
}
public static String getClientWebUIPath() {
return CLIENT_WEBUI_PATH;
}
public static String getSettingsPath() {
return SETTINGS_PATH;
}
public static String getCustomSettingsPath() {
return CUSTOM_SETTINGS_PATH;
}
public static String getPipelineWatchedFoldersDir() {
return PIPELINE_WATCHED_FOLDERS_PATH;
}
public static String getPipelineFinishedFoldersDir() {
return PIPELINE_FINISHED_FOLDERS_PATH;
}
public static String getStaticPath() {
return STATIC_PATH;
}
public static String getTemplatesPath() {
return TEMPLATES_PATH;
}
public static String getSignaturesPath() {
return SIGNATURES_PATH;
}
}

View File

@@ -1,10 +0,0 @@
package stirling.software.SPDF.config;
import ch.qos.logback.core.PropertyDefinerBase;
public class LogbackPropertyLoader extends PropertyDefinerBase {
@Override
public String getPropertyValue() {
return InstallationPathConfig.getLogPath();
}
}

View File

@@ -23,8 +23,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Handler for external static resources // Handler for external static resources
registry.addResourceHandler("/**") registry.addResourceHandler("/**")
.addResourceLocations( .addResourceLocations("file:customFiles/static/", "classpath:/static/");
"file:" + InstallationPathConfig.getStaticPath(), "classpath:/static/");
// .setCachePeriod(0); // Optional: disable caching // .setCachePeriod(0); // Optional: disable caching
} }
} }

View File

@@ -16,6 +16,7 @@ public class YamlPropertySourceFactory implements PropertySourceFactory {
throws IOException { throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource()); factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject(); Properties properties = factory.getObject();
return new PropertiesPropertySource( return new PropertiesPropertySource(

View File

@@ -22,31 +22,22 @@ 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 lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; 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.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
@Slf4j
@Component @Component
public class UserAuthenticationFilter extends OncePerRequestFilter { public class UserAuthenticationFilter extends OncePerRequestFilter {
private final ApplicationProperties applicationProperties;
private final UserService userService; private final UserService userService;
private final SessionPersistentRegistry sessionPersistentRegistry; private final SessionPersistentRegistry sessionPersistentRegistry;
private final boolean loginEnabledValue; private final boolean loginEnabledValue;
public UserAuthenticationFilter( public UserAuthenticationFilter(
@Lazy ApplicationProperties applicationProperties,
@Lazy UserService userService, @Lazy UserService userService,
SessionPersistentRegistry sessionPersistentRegistry, SessionPersistentRegistry sessionPersistentRegistry,
@Qualifier("loginEnabled") boolean loginEnabledValue) { @Qualifier("loginEnabled") boolean loginEnabledValue) {
this.applicationProperties = applicationProperties;
this.userService = userService; this.userService = userService;
this.sessionPersistentRegistry = sessionPersistentRegistry; this.sessionPersistentRegistry = sessionPersistentRegistry;
this.loginEnabledValue = loginEnabledValue; this.loginEnabledValue = loginEnabledValue;
@@ -130,67 +121,33 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// Check if the authenticated user is disabled and invalidate their session if so // Check if the authenticated user is disabled and invalidate their session if so
if (authentication != null && authentication.isAuthenticated()) { if (authentication != null && authentication.isAuthenticated()) {
Security securityProp = applicationProperties.getSecurity();
LoginMethod loginMethod = LoginMethod.UNKNOWN;
boolean blockRegistration = false;
// Extract username and determine the login method
Object principal = authentication.getPrincipal(); Object principal = authentication.getPrincipal();
String username = null; String username = null;
if (principal instanceof UserDetails) { if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername(); username = ((UserDetails) principal).getUsername();
loginMethod = LoginMethod.USERDETAILS;
} else if (principal instanceof OAuth2User) { } else if (principal instanceof OAuth2User) {
username = ((OAuth2User) principal).getName(); username = ((OAuth2User) principal).getName();
loginMethod = LoginMethod.OAUTH2USER;
OAUTH2 oAuth = securityProp.getOauth2();
blockRegistration = oAuth != null && oAuth.getBlockRegistration();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
loginMethod = LoginMethod.SAML2USER;
SAML2 saml2 = securityProp.getSaml2();
blockRegistration = saml2 != null && saml2.getBlockRegistration();
} else if (principal instanceof String) { } else if (principal instanceof String) {
username = (String) principal; username = (String) principal;
loginMethod = LoginMethod.STRINGUSER;
} }
// Retrieve all active sessions for the user
List<SessionInformation> sessionsInformations = List<SessionInformation> sessionsInformations =
sessionPersistentRegistry.getAllSessions(principal, false); sessionPersistentRegistry.getAllSessions(principal, false);
// Check if the user exists, is disabled, or needs session invalidation
if (username != null) { if (username != null) {
log.debug("Validating user: {}", username);
boolean isUserExists = userService.usernameExistsIgnoreCase(username); boolean isUserExists = userService.usernameExistsIgnoreCase(username);
boolean isUserDisabled = userService.isUserDisabled(username); boolean isUserDisabled = userService.isUserDisabled(username);
boolean notSsoLogin =
!loginMethod.equals(LoginMethod.OAUTH2USER)
&& !loginMethod.equals(LoginMethod.SAML2USER);
// Block user registration if not allowed by configuration
if (blockRegistration && !isUserExists) {
log.warn("Blocked registration for OAuth2/SAML user: {}", username);
response.sendRedirect(
request.getContextPath() + "/logout?oauth2_admin_blocked_user=true");
return;
}
// Expire sessions and logout if the user does not exist or is disabled
if (!isUserExists || isUserDisabled) { if (!isUserExists || isUserDisabled) {
log.info(
"Invalidating session for disabled or non-existent user: {}", username);
for (SessionInformation sessionsInformation : sessionsInformations) { for (SessionInformation sessionsInformation : sessionsInformations) {
sessionsInformation.expireNow(); sessionsInformation.expireNow();
sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId()); sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId());
} }
} }
// Redirect to logout if credentials are invalid if (!isUserExists) {
if (!isUserExists && notSsoLogin) {
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true"); response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
return; return;
} }
@@ -204,25 +161,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
private enum LoginMethod {
USERDETAILS("UserDetails"),
OAUTH2USER("OAuth2User"),
STRINGUSER("StringUser"),
UNKNOWN("Unknown"),
SAML2USER("Saml2User");
private String method;
LoginMethod(String method) {
this.method = method;
}
@Override
public String toString() {
return method;
}
}
@Override @Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String uri = request.getRequestURI(); String uri = request.getRequestURI();

View File

@@ -11,11 +11,13 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.authority.SimpleGrantedAuthority; 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.OpenSaml4AuthenticationProvider.ResponseToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.stereotype.Component;
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.model.User; import stirling.software.SPDF.model.User;
@Component
@Slf4j @Slf4j
public class CustomSaml2ResponseAuthenticationConverter public class CustomSaml2ResponseAuthenticationConverter
implements Converter<ResponseToken, Saml2Authentication> { implements Converter<ResponseToken, Saml2Authentication> {

View File

@@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
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.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
@@ -34,8 +33,7 @@ public class SettingsController {
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) { if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED) return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
.body( .body(
"Setting has already been set, To adjust please edit " "Setting has already been set, To adjust please edit /config/settings.yml");
+ InstallationPathConfig.getSettingsPath());
} }
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));

View File

@@ -13,9 +13,6 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -34,8 +31,11 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest; import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest; import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.*; import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@@ -62,20 +62,14 @@ public class ConvertImgPDFController {
String singleOrMultiple = request.getSingleOrMultiple(); String singleOrMultiple = request.getSingleOrMultiple();
String colorType = request.getColorType(); String colorType = request.getColorType();
String dpi = request.getDpi(); String dpi = request.getDpi();
String pageNumbers = request.getPageNumbers();
Path tempFile = null; Path tempFile = null;
Path tempOutputDir = null; Path tempOutputDir = null;
Path tempPdfPath = null; Path tempPdfPath = null;
byte[] result = null; byte[] result = null;
String[] pageOrderArr =
(pageNumbers != null && !pageNumbers.trim().isEmpty())
? pageNumbers.split(",")
: new String[] {"all"};
;
try {
// Load the input PDF
byte[] newPdfBytes = rearrangePdfPages(file.getBytes(), pageOrderArr);
try {
byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB; ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) { if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY; colorTypeResult = ImageType.GRAY;
@@ -90,7 +84,7 @@ public class ConvertImgPDFController {
result = result =
PdfUtils.convertFromPdf( PdfUtils.convertFromPdf(
newPdfBytes, pdfBytes,
"webp".equalsIgnoreCase(imageFormat) "webp".equalsIgnoreCase(imageFormat)
? "png" ? "png"
: imageFormat.toUpperCase(), : imageFormat.toUpperCase(),
@@ -233,46 +227,4 @@ public class ConvertImgPDFController {
String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat); String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat);
return "null".equals(mimeType) ? "application/octet-stream" : mimeType; return "null".equals(mimeType) ? "application/octet-stream" : mimeType;
} }
/**
* Rearranges the pages of the given PDF document based on the specified page order.
*
* @param pdfBytes The byte array of the original PDF file.
* @param pageOrderArr An array of page numbers indicating the new order.
* @return A byte array of the rearranged PDF.
* @throws IOException If an error occurs while processing the PDF.
*/
private byte[] rearrangePdfPages(byte[] pdfBytes, String[] pageOrderArr) throws IOException {
// Load the input PDF
PDDocument document = Loader.loadPDF(pdfBytes);
int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
// Create a new list to hold the pages in the new order
List<PDPage> newPages = new ArrayList<>();
for (int pageIndex : newPageOrder) {
newPages.add(document.getPage(pageIndex));
}
// Remove all the pages from the original document
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
document.removePage(i);
}
// Add the pages in the new order
for (PDPage page : newPages) {
document.addPage(page);
}
// Convert PDDocument to byte array
byte[] newPdfBytes;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
document.save(baos);
newPdfBytes = baos.toByteArray();
} finally {
document.close();
}
return newPdfBytes;
}
} }

View File

@@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.api.HandleDataRequest; import stirling.software.SPDF.model.api.HandleDataRequest;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -34,12 +35,22 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Pipeline", description = "Pipeline APIs") @Tag(name = "Pipeline", description = "Pipeline APIs")
public class PipelineController { public class PipelineController {
final String watchedFoldersDir = "./pipeline/watchedFolders/";
final String finishedFoldersDir = "./pipeline/finishedFolders/";
private final PipelineProcessor processor; private final PipelineProcessor processor;
private final ApplicationProperties applicationProperties;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public PipelineController(PipelineProcessor processor, ObjectMapper objectMapper) { public PipelineController(
PipelineProcessor processor,
ApplicationProperties applicationProperties,
ObjectMapper objectMapper) {
this.processor = processor; this.processor = processor;
this.applicationProperties = applicationProperties;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }

View File

@@ -16,6 +16,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
@@ -24,7 +25,6 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.utils.FileMonitor; import stirling.software.SPDF.utils.FileMonitor;
@@ -48,12 +48,14 @@ public class PipelineDirectoryProcessor {
public PipelineDirectoryProcessor( public PipelineDirectoryProcessor(
ObjectMapper objectMapper, ObjectMapper objectMapper,
ApiDocService apiDocService, ApiDocService apiDocService,
@Qualifier("watchedFoldersDir") String watchedFoldersDir,
@Qualifier("finishedFoldersDir") String finishedFoldersDir,
PipelineProcessor processor, PipelineProcessor processor,
FileMonitor fileMonitor) { FileMonitor fileMonitor) {
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.apiDocService = apiDocService; this.apiDocService = apiDocService;
this.watchedFoldersDir = InstallationPathConfig.getPipelineWatchedFoldersDir(); this.watchedFoldersDir = watchedFoldersDir;
this.finishedFoldersDir = InstallationPathConfig.getPipelineFinishedFoldersDir(); this.finishedFoldersDir = finishedFoldersDir;
this.processor = processor; this.processor = processor;
this.fileMonitor = fileMonitor; this.fileMonitor = fileMonitor;
} }

View File

@@ -163,9 +163,6 @@ public class AccountWebController {
case "invalid_destination": case "invalid_destination":
erroroauth = "login.invalid_destination"; erroroauth = "login.invalid_destination";
break; break;
case "relying_party_registration_not_found":
erroroauth = "login.relyingPartyRegistrationNotFound";
break;
// Valid InResponseTo was not available from the validation context, unable to // Valid InResponseTo was not available from the validation context, unable to
// evaluate // evaluate
case "invalid_in_response_to": case "invalid_in_response_to":

View File

@@ -25,7 +25,6 @@ import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.SignatureFile; import stirling.software.SPDF.model.SignatureFile;
import stirling.software.SPDF.service.SignatureService; import stirling.software.SPDF.service.SignatureService;
@@ -35,6 +34,8 @@ import stirling.software.SPDF.service.SignatureService;
@Slf4j @Slf4j
public class GeneralWebController { public class GeneralWebController {
private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
private static final String ALL_USERS_FOLDER = "ALL_USERS";
private final SignatureService signatureService; private final SignatureService signatureService;
private final UserServiceInterface userService; private final UserServiceInterface userService;
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
@@ -222,9 +223,7 @@ public class GeneralWebController {
// Extract font names from classpath // Extract font names from classpath
fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2")); fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2"));
// Extract font names from external directory // Extract font names from external directory
fontNames.addAll( fontNames.addAll(getFontNamesFromLocation("file:customFiles/static/fonts/*"));
getFontNamesFromLocation(
"file:" + InstallationPathConfig.getStaticPath() + "fonts/*"));
return fontNames; return fontNames;
} }

View File

@@ -1,7 +1,5 @@
package stirling.software.SPDF.model; package stirling.software.SPDF.model;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@@ -15,23 +13,18 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
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;
import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.GoogleProvider;
@@ -40,37 +33,11 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
@Configuration @Configuration
@ConfigurationProperties(prefix = "") @ConfigurationProperties(prefix = "")
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
@Data @Data
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class ApplicationProperties { public class ApplicationProperties {
@Bean
public PropertySource<?> dynamicYamlPropertySource(ConfigurableEnvironment environment)
throws IOException {
String configPath = InstallationPathConfig.getSettingsPath();
log.debug("Attempting to load settings from: " + configPath);
File file = new File(configPath);
if (!file.exists()) {
log.error("Warning: Settings file does not exist at: " + configPath);
}
Resource resource = new FileSystemResource(configPath);
if (!resource.exists()) {
throw new FileNotFoundException("Settings file not found at: " + configPath);
}
EncodedResource encodedResource = new EncodedResource(resource);
PropertySource<?> propertySource =
new YamlPropertySourceFactory().createPropertySource(null, encodedResource);
environment.getPropertySources().addFirst(propertySource);
log.debug("Loaded properties: " + propertySource.getSource());
return propertySource;
}
private Legal legal = new Legal(); private Legal legal = new Legal();
private Security security = new Security(); private Security security = new Security();
private System system = new System(); private System system = new System();
@@ -186,7 +153,6 @@ public class ApplicationProperties {
} }
public Resource getSpCert() { public Resource getSpCert() {
if (spCert == null) return null;
if (spCert.startsWith("classpath:")) { if (spCert.startsWith("classpath:")) {
return new ClassPathResource(spCert.substring("classpath:".length())); return new ClassPathResource(spCert.substring("classpath:".length()));
} else { } else {
@@ -195,7 +161,6 @@ public class ApplicationProperties {
} }
public Resource getidpCert() { public Resource getidpCert() {
if (idpCert == null) return null;
if (idpCert.startsWith("classpath:")) { if (idpCert.startsWith("classpath:")) {
return new ClassPathResource(idpCert.substring("classpath:".length())); return new ClassPathResource(idpCert.substring("classpath:".length()));
} else { } else {

View File

@@ -21,11 +21,6 @@ public class ConvertToImageRequest extends PDFFile {
allowableValues = {"single", "multiple"}) allowableValues = {"single", "multiple"})
private String singleOrMultiple; private String singleOrMultiple;
@Schema(
description =
"The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"")
private String pageNumbers;
@Schema( @Schema(
description = "The color type of the output image(s)", description = "The color type of the output image(s)",
allowableValues = {"color", "greyscale", "blackwhite"}) allowableValues = {"color", "greyscale", "blackwhite"})

View File

@@ -13,19 +13,14 @@ import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils; import org.thymeleaf.util.StringUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.SignatureFile; import stirling.software.SPDF.model.SignatureFile;
@Service @Service
@Slf4j @Slf4j
public class SignatureService { public class SignatureService {
private final String SIGNATURE_BASE_PATH; private static final String SIGNATURE_BASE_PATH = "customFiles/signatures/";
private final String ALL_USERS_FOLDER = "ALL_USERS"; private static final String ALL_USERS_FOLDER = "ALL_USERS";
public SignatureService() {
SIGNATURE_BASE_PATH = InstallationPathConfig.getSignaturesPath();
}
public boolean hasAccessToFile(String username, String fileName) throws IOException { public boolean hasAccessToFile(String username, String fileName) throws IOException {
validateFileName(fileName); validateFileName(fileName);

View File

@@ -15,7 +15,6 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
@Component @Component
@Slf4j @Slf4j
@@ -35,7 +34,9 @@ public class FileMonitor {
* monitored, false otherwise * monitored, false otherwise
*/ */
@Autowired @Autowired
public FileMonitor(@Qualifier("directoryFilter") Predicate<Path> pathFilter) public FileMonitor(
@Qualifier("watchedFoldersDir") String rootDirectory,
@Qualifier("directoryFilter") Predicate<Path> pathFilter)
throws IOException { throws IOException {
this.newlyDiscoveredFiles = new HashSet<>(); this.newlyDiscoveredFiles = new HashSet<>();
this.path2KeyMapping = new HashMap<>(); this.path2KeyMapping = new HashMap<>();
@@ -43,7 +44,7 @@ public class FileMonitor {
this.pathFilter = pathFilter; this.pathFilter = pathFilter;
this.readyForProcessingFiles = ConcurrentHashMap.newKeySet(); this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();
this.watchService = FileSystems.getDefault().newWatchService(); this.watchService = FileSystems.getDefault().newWatchService();
this.rootDir = Path.of(InstallationPathConfig.getPipelineWatchedFoldersDir()); this.rootDir = Path.of(rootDirectory);
} }
private boolean shouldNotProcess(Path path) { private boolean shouldNotProcess(Path path) {

View File

@@ -26,7 +26,6 @@ import io.github.pixee.security.HostValidator;
import io.github.pixee.security.Urls; import io.github.pixee.security.Urls;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
@Slf4j @Slf4j
public class GeneralUtils { public class GeneralUtils {
@@ -84,7 +83,7 @@ public class GeneralUtils {
// Allow only http and https protocols // Allow only http and https protocols
String protocol = url.getProtocol(); String protocol = url.getProtocol();
if (!"http".equals(protocol) && !"https".equals(protocol)) { if (!protocol.equals("http") && !protocol.equals("https")) {
return false; // Disallow other protocols return false; // Disallow other protocols
} }
@@ -221,54 +220,32 @@ public class GeneralUtils {
throw new IllegalArgumentException("Invalid expression"); throw new IllegalArgumentException("Invalid expression");
} }
for (int n = 1; n <= maxValue; n++) { int n = 0;
while (true) {
// Replace 'n' with the current value of n, correctly handling numbers before // Replace 'n' with the current value of n, correctly handling numbers before
// 'n' // 'n'
String sanitizedExpression = sanitizeNFunction(expression, n); String sanitizedExpression = insertMultiplicationBeforeN(expression, n);
Double result = evaluator.evaluate(sanitizedExpression); Double result = evaluator.evaluate(sanitizedExpression);
// Check if the result is null or not within bounds // Check if the result is null or not within bounds
if (result == null) break; if (result == null || result <= 0 || result.intValue() > maxValue) {
if (n != 0) break;
if (result.intValue() > 0 && result.intValue() <= maxValue) } else {
results.add(result.intValue()); results.add(result.intValue());
}
n++;
} }
return results; return results;
} }
private static String sanitizeNFunction(String expression, int nValue) {
String sanitizedExpression = expression.replace(" ", "");
String multiplyByOpeningRoundBracketPattern =
"([0-9n)])\\("; // example: n(n-1), 9(n-1), (n-1)(n-2)
sanitizedExpression =
sanitizedExpression.replaceAll(multiplyByOpeningRoundBracketPattern, "$1*(");
String multiplyByClosingRoundBracketPattern =
"\\)([0-9n)])"; // example: (n-1)n, (n-1)9, (n-1)(n-2)
sanitizedExpression =
sanitizedExpression.replaceAll(multiplyByClosingRoundBracketPattern, ")*$1");
sanitizedExpression = insertMultiplicationBeforeN(sanitizedExpression, nValue);
return sanitizedExpression;
}
private static String insertMultiplicationBeforeN(String expression, int nValue) { private static String insertMultiplicationBeforeN(String expression, int nValue) {
// Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n") // Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n")
String withMultiplication = expression.replaceAll("(\\d)n", "$1*n"); String withMultiplication = expression.replaceAll("(\\d)n", "$1*n");
withMultiplication = formatConsecutiveNsForNFunction(withMultiplication);
// Now replace 'n' with its current value // Now replace 'n' with its current value
return withMultiplication.replace("n", String.valueOf(nValue)); return withMultiplication.replace("n", String.valueOf(nValue));
} }
private static String formatConsecutiveNsForNFunction(String expression) {
String text = expression;
while (text.matches(".*n{2,}.*")) {
text = text.replaceAll("(?<!n)n{2}", "n*n");
}
return text;
}
private static List<Integer> handlePart(String part, int totalPages, int offset) { private static List<Integer> handlePart(String part, int totalPages, int offset) {
List<Integer> partResult = new ArrayList<>(); List<Integer> partResult = new ArrayList<>();
@@ -343,10 +320,7 @@ public class GeneralUtils {
public static void saveKeyToConfig(String id, String key, boolean autoGenerated) public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
throws IOException { throws IOException {
Path path = Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
Paths.get(
InstallationPathConfig
.getSettingsPath()); // Target the configs/settings.yml
final YamlFile settingsYml = new YamlFile(path.toFile()); final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml = DumperOptions yamlOptionssettingsYml =
@@ -364,7 +338,7 @@ public class GeneralUtils {
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated) public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
throws IOException { throws IOException {
Path path = Paths.get(InstallationPathConfig.getSettingsPath()); Path path = Paths.get("configs", "settings.yml");
final YamlFile settingsYml = new YamlFile(path.toFile()); final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml = DumperOptions yamlOptionssettingsYml =

View File

@@ -0,0 +1,3 @@
package stirling.software.SPDF.utils;
public class PDFManipulationUtils {}

View File

@@ -9,7 +9,6 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@@ -65,7 +64,7 @@ public class PDFToFile {
.runCommandWithOutputHandling(command, tempOutputDir.toFile()); .runCommandWithOutputHandling(command, tempOutputDir.toFile());
// Get output files // Get output files
File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles()); List<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
// Return output files in a ZIP archive // Return output files in a ZIP archive
fileName = pdfBaseName + "ToHtml.zip"; fileName = pdfBaseName + "ToHtml.zip";

View File

@@ -22,11 +22,9 @@ import org.apache.pdfbox.text.TextPosition;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination; import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert; import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
@Slf4j
public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy { public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
private String textColor; private String textColor;
@@ -95,17 +93,17 @@ public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
try { try {
font = PDFontFactory.createFont(text.getFont().getCOSObject()); font = PDFontFactory.createFont(text.getFont().getCOSObject());
} catch (IOException io) { } catch (IOException io) {
log.info("Primary font not found, using fallback font."); System.out.println("Primary font not found, using fallback font.");
font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
} }
// if a character is not supported by font, then look for supported font // if a character is not supported by font, then look for supported font
try { try {
byte[] bytes = font.encode(unicodeText); byte[] bytes = font.encode(unicodeText);
} catch (IOException io) { } catch (IOException io) {
log.info("text could not be encoded "); System.out.println("text could not be encoded ");
font = checkSupportedFontForCharacter(unicodeText); font = checkSupportedFontForCharacter(unicodeText);
} catch (IllegalArgumentException ie) { } catch (IllegalArgumentException ie) {
log.info("text not supported by font "); System.out.println("text not supported by font ");
font = checkSupportedFontForCharacter(unicodeText); font = checkSupportedFontForCharacter(unicodeText);
} finally { } finally {
// if any other font is not supported, then replace default character * // if any other font is not supported, then replace default character *
@@ -159,9 +157,9 @@ public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
byte[] bytes = currentFont.encode(unicodeText); byte[] bytes = currentFont.encode(unicodeText);
return currentFont; return currentFont;
} catch (IOException io) { } catch (IOException io) {
log.info("text could not be encoded "); System.out.println("text could not be encoded ");
} catch (IllegalArgumentException ie) { } catch (IllegalArgumentException ie) {
log.info("text not supported by font "); System.out.println("text not supported by font ");
} }
} }
return null; return null;

View File

@@ -27,7 +27,8 @@ spring.devtools.restart.exclude=stirling.software.SPDF.config.security/**
spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.encoding=UTF-8
spring.web.resources.mime-mappings.webmanifest=application/manifest+json spring.web.resources.mime-mappings.webmanifest=application/manifest+json
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000} spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
#spring.thymeleaf.cache=false
spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa spring.datasource.username=sa

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<define name="LOG_PATH" class="stirling.software.SPDF.config.LogbackPropertyLoader" />
<!-- Console Appender --> <!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@@ -9,30 +7,35 @@
</encoder> </encoder>
</appender> </appender>
<!-- Rolling File Appender for Auth Logs --> <!-- Rolling File Appender -->
<appender name="AUTHLOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="AUTHLOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/invalid-auths.log</file> <file>logs/invalid-auths.log</file>
<encoder> <encoder>
<pattern>%d %p %c{1} [%thread] %m%n</pattern> <pattern>%d %p %c{1} [%thread] %m%n</pattern>
</encoder> </encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/auth-%d{yyyy-MM-dd}.log</fileNamePattern> <!-- daily rollover and keep 7 days' worth of history -->
<fileNamePattern>logs/auth-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>1</maxHistory> <maxHistory>1</maxHistory>
</rollingPolicy> </rollingPolicy>
</appender> </appender>
<!-- Rolling File Appender for General Logs --> <!-- Rolling File Appender -->
<appender name="GENERAL" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="GENERAL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/info.log</file> <file>logs/info.log</file>
<encoder> <encoder>
<pattern>%d %p %c{1} [%thread] %m%n</pattern> <pattern>%d %p %c{1} [%thread] %m%n</pattern>
</encoder> </encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info-%d{yyyy-MM-dd}.log</fileNamePattern> <!-- daily rollover and keep 7 days' worth of history -->
<fileNamePattern>logs/info-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>1</maxHistory> <maxHistory>1</maxHistory>
</rollingPolicy> </rollingPolicy>
</appender> </appender>
<!-- Root Logger --> <!-- Root Logger -->
<root level="INFO"> <root level="INFO">
<appender-ref ref="CONSOLE"/> <appender-ref ref="CONSOLE"/>
@@ -40,9 +43,10 @@
</root> </root>
<!-- Specific Logger --> <!-- Specific Logger -->
<logger name="stirling.software.SPDF.config.security.CustomAuthenticationFailureHandler" <logger name="stirling.software.SPDF.config.security.CustomAuthenticationFailureHandler" level="ERROR"
level="ERROR" additivity="false"> additivity="false">
<appender-ref ref="CONSOLE"/> <appender-ref ref="CONSOLE"/>
<appender-ref ref="AUTHLOG"/> <appender-ref ref="AUTHLOG"/>
</logger> </logger>
</configuration> </configuration>

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=طلب غير صالح
login.oauth2AccessDenied=تم رفض الوصول login.oauth2AccessDenied=تم رفض الوصول
login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة
login.oauth2InvalidIdToken=رمز الهوية غير صالح login.oauth2InvalidIdToken=رمز الهوية غير صالح
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول. login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول.
login.alreadyLoggedIn=لقد تسجل دخولًا إلى login.alreadyLoggedIn=لقد تسجل دخولًا إلى
login.alreadyLoggedIn2=أجهزة أخرى. يرجى تسجيل الخروج من الأجهزة وحاول مرة أخرى. login.alreadyLoggedIn2=أجهزة أخرى. يرجى تسجيل الخروج من الأجهزة وحاول مرة أخرى.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=إصلاح repair.title=إصلاح
repair.header=إصلاح ملفات PDF repair.header=إصلاح ملفات PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=تدرج الرمادي
pdfToImage.blackwhite=أبيض وأسود (قد يفقد البيانات!) pdfToImage.blackwhite=أبيض وأسود (قد يفقد البيانات!)
pdfToImage.submit=تحويل pdfToImage.submit=تحويل
pdfToImage.info=Python غير مثبت. مطلوب لتحويل WebP. pdfToImage.info=Python غير مثبت. مطلوب لتحويل WebP.
pdfToImage.placeholder=(مثال: 1,2,8 أو 4,7,12-16 أو 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=تقطيع ملف PDF
fileChooser.click=انقر هنا fileChooser.click=انقر هنا
fileChooser.or=أو fileChooser.or=أو
fileChooser.dragAndDrop=قم بسحب الملفات وإفلاتها fileChooser.dragAndDrop=قم بسحب الملفات وإفلاتها
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=قم بسحب المفات وإفلاتها هنا fileChooser.hoveredDragAndDrop=قم بسحب المفات وإفلاتها هنا
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Etibarsız Sorğu
login.oauth2AccessDenied=Giriş rədd edildi login.oauth2AccessDenied=Giriş rədd edildi
login.oauth2InvalidTokenResponse=Etibarsız Token Cavabı login.oauth2InvalidTokenResponse=Etibarsız Token Cavabı
login.oauth2InvalidIdToken=Etibarsız Id Token login.oauth2InvalidIdToken=Etibarsız Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=İstifadəçi deaktivləşdirilmişdir, bu istifadəçi adı ilə giriş hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın. login.userIsDisabled=İstifadəçi deaktivləşdirilmişdir, bu istifadəçi adı ilə giriş hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın.
login.alreadyLoggedIn=Siz artıq daxil olmusunuz login.alreadyLoggedIn=Siz artıq daxil olmusunuz
login.alreadyLoggedIn2=cihazlar. Zəhmət olmasa, cihazlardan çıxış edin və yenidən cəhd edin. login.alreadyLoggedIn2=cihazlar. Zəhmət olmasa, cihazlardan çıxış edin və yenidən cəhd edin.
@@ -831,7 +830,7 @@ sign.first=İlk səhifə
sign.last=Son səhifə sign.last=Son səhifə
sign.next=Növbəti səhifə sign.next=Növbəti səhifə
sign.previous=Əvvəlki səhifə sign.previous=Əvvəlki səhifə
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Bərpa Et repair.title=Bərpa Et
repair.header=PDFləri Bərpa Et repair.header=PDFləri Bərpa Et
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Boz Tonlama
pdfToImage.blackwhite=Qara və Ağ (Data İtə Bilər) pdfToImage.blackwhite=Qara və Ağ (Data İtə Bilər)
pdfToImage.submit=Çevir pdfToImage.submit=Çevir
pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir
pdfToImage.placeholder=(məsələn, 1,2,8 və ya 4,7,12-16 və ya 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDF-i Ayır
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Невалидна заявка
login.oauth2AccessDenied=Отказан достъп login.oauth2AccessDenied=Отказан достъп
login.oauth2InvalidTokenResponse=Невалиден отговор на токена login.oauth2InvalidTokenResponse=Невалиден отговор на токена
login.oauth2InvalidIdToken=Невалиден токен за идентификатор login.oauth2InvalidIdToken=Невалиден токен за идентификатор
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Потребителят е деактивиран, влизането в момента е блокирано с това потребителско име. Моля, свържете се с администратора. login.userIsDisabled=Потребителят е деактивиран, влизането в момента е блокирано с това потребителско име. Моля, свържете се с администратора.
login.alreadyLoggedIn=Вече сте влезли в login.alreadyLoggedIn=Вече сте влезли в
login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново. login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Поправи repair.title=Поправи
repair.header=Поправи PDF-и repair.header=Поправи PDF-и
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Скала на сивото
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!) pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
pdfToImage.submit=Преобразуване pdfToImage.submit=Преобразуване
pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP. pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP.
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Разделяне на PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Sol·licitud no vàlida
login.oauth2AccessDenied=Accés denegat login.oauth2AccessDenied=Accés denegat
login.oauth2InvalidTokenResponse=Resposta de token no vàlida login.oauth2InvalidTokenResponse=Resposta de token no vàlida
login.oauth2InvalidIdToken=ID Token no vàlid login.oauth2InvalidIdToken=ID Token no vàlid
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=L'usuari està desactivat, l'inici de sessió està actualment bloquejat amb aquest nom d'usuari. Si us plau, contacta amb l'administrador. login.userIsDisabled=L'usuari està desactivat, l'inici de sessió està actualment bloquejat amb aquest nom d'usuari. Si us plau, contacta amb l'administrador.
login.alreadyLoggedIn=Ja has iniciat sessió a login.alreadyLoggedIn=Ja has iniciat sessió a
login.alreadyLoggedIn2=dispositius. Si us plau, tanca la sessió en els dispositius i torna-ho a intentar. login.alreadyLoggedIn2=dispositius. Si us plau, tanca la sessió en els dispositius i torna-ho a intentar.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparar repair.title=Reparar
repair.header=Repara els PDF repair.header=Repara els PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Escala de Grisos
pdfToImage.blackwhite=Blanc i Negre (Pot perdre dades!) pdfToImage.blackwhite=Blanc i Negre (Pot perdre dades!)
pdfToImage.submit=Converteix pdfToImage.submit=Converteix
pdfToImage.info=Python no està instal·lat. És necessari per a la conversió a WebP. pdfToImage.info=Python no està instal·lat. És necessari per a la conversió a WebP.
pdfToImage.placeholder=(p. ex. 1,2,8 o 4,7,12-16 o 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Divideix PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Neplatný požadavek
login.oauth2AccessDenied=Přístup zazobán login.oauth2AccessDenied=Přístup zazobán
login.oauth2InvalidTokenResponse=Neplatná odpověď tokenu login.oauth2InvalidTokenResponse=Neplatná odpověď tokenu
login.oauth2InvalidIdToken=Neplatný identifikační token login.oauth2InvalidIdToken=Neplatný identifikační token
login.relyingPartyRegistrationNotFound=No relying party registration found
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=Uživatel je deaktivován, přihlášení aktuálně pro tuto uživatelskou jmena je zakázáno. Kontaktujte správce.
login.alreadyLoggedIn=Jste již přihlášeni na login.alreadyLoggedIn=Jste již přihlášeni na
login.alreadyLoggedIn2=zariadení. Odhlašujte se z těchto zařízení a zkuste to znovu. login.alreadyLoggedIn2=zariadení. Odhlašujte se z těchto zařízení a zkuste to znovu.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Opravit repair.title=Opravit
repair.header=Opravit PDF repair.header=Opravit PDF
@@ -1042,7 +1041,6 @@ 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 není nainstalován. Potřebuje se pro konverzi do WebP.
pdfToImage.placeholder=(např. 1,2,8 nebo 4,7,12-16 nebo 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Podělit se PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Ugyldig Anmodning
login.oauth2AccessDenied=Adgang Nægtet 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.relyingPartyRegistrationNotFound=No relying party registration found
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=Du er allerede logget ind på
login.alreadyLoggedIn2=enheder. Log ud af disse enheder og prøv igen. login.alreadyLoggedIn2=enheder. Log ud af disse enheder og prøv igen.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparér repair.title=Reparér
repair.header=Reparér PDF'er repair.header=Reparér PDF'er
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Gråtone
pdfToImage.blackwhite=Sort og Hvid (Kan miste data!) pdfToImage.blackwhite=Sort og Hvid (Kan miste data!)
pdfToImage.submit=Konvertér pdfToImage.submit=Konvertér
pdfToImage.info=Python er ikke installeret. Påkrævet for WebP-konvertering. pdfToImage.info=Python er ikke installeret. Påkrævet for WebP-konvertering.
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Splitter PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=ungültige Anfrage
login.oauth2AccessDenied=Zugriff abgelehnt 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.relyingPartyRegistrationNotFound=Keine Relying-Party-Registrierung gefunden
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=Sie sind bereits an
login.alreadyLoggedIn2=Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut. login.alreadyLoggedIn2=Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut.
@@ -831,7 +830,7 @@ sign.first=Erste Seite
sign.last=Letzte Seite sign.last=Letzte Seite
sign.next=Nächste Seite sign.next=Nächste Seite
sign.previous=Vorherige Seite sign.previous=Vorherige Seite
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparieren repair.title=Reparieren
repair.header=PDFs reparieren repair.header=PDFs reparieren
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Graustufen
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!) pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
pdfToImage.submit=Umwandeln pdfToImage.submit=Umwandeln
pdfToImage.info=Python ist nicht installiert. Erforderlich für die WebP-Konvertierung. pdfToImage.info=Python ist nicht installiert. Erforderlich für die WebP-Konvertierung.
pdfToImage.placeholder=(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDF teilen
fileChooser.click=Klicken fileChooser.click=Klicken
fileChooser.or=oder fileChooser.or=oder
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Datei(en) hierhin Ziehen & Fallenlassen fileChooser.hoveredDragAndDrop=Datei(en) hierhin Ziehen & Fallenlassen
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Αρχαίως απαιτήσεις πληροφορ
login.oauth2AccessDenied=Πρόσβαση ορισμένες τοποθετήσεις login.oauth2AccessDenied=Πρόσβαση ορισμένες τοποθετήσεις
login.oauth2InvalidTokenResponse=Απάντηση άρκετο ρόταμα login.oauth2InvalidTokenResponse=Απάντηση άρκετο ρόταμα
login.oauth2InvalidIdToken=Λιδός όρκου μη αρκετός login.oauth2InvalidIdToken=Λιδός όρκου μη αρκετός
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Ο χρήστης είναι δευτεροβαθμιακά διακωμένος, το σύστημα άλλαξε τον καθώς βρεθεί. Παρακαλείστε τους αρχηγούς να επιβεβαιώσουν τη συμπεριφορά χρήστη. login.userIsDisabled=Ο χρήστης είναι δευτεροβαθμιακά διακωμένος, το σύστημα άλλαξε τον καθώς βρεθεί. Παρακαλείστε τους αρχηγούς να επιβεβαιώσουν τη συμπεριφορά χρήστη.
login.alreadyLoggedIn=Είστε ήδη σύνδεδες σε login.alreadyLoggedIn=Είστε ήδη σύνδεδες σε
login.alreadyLoggedIn2=κατοχόι. Παρακαλώ δυσκέντρωση τους και προσπαθήστε ξανά. login.alreadyLoggedIn2=κατοχόι. Παρακαλώ δυσκέντρωση τους και προσπαθήστε ξανά.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Επιδιόρθωση repair.title=Επιδιόρθωση
repair.header=Επιδιόρθωση PDFs repair.header=Επιδιόρθωση PDFs
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Κλίμακα του γκρι
pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!) pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!)
pdfToImage.submit=Μετατροπή pdfToImage.submit=Μετατροπή
pdfToImage.info=Δεν είναι ιστάμενος Python. Είναι απαιτήτων για τη μετατροπή σε WebP. pdfToImage.info=Δεν είναι ιστάμενος Python. Είναι απαιτήτων για τη μετατροπή σε WebP.
pdfToImage.placeholder=(π.χ. 1,2,8 ή 4,7,12-16 ή 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Διαλύστε το PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token login.oauth2InvalidIdToken=Invalid Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Repair repair.title=Repair
repair.header=Repair PDFs repair.header=Repair PDFs
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Greyscale
pdfToImage.blackwhite=Black and White (May lose data!) pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert pdfToImage.submit=Convert
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token login.oauth2InvalidIdToken=Invalid Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Repair repair.title=Repair
repair.header=Repair PDFs repair.header=Repair PDFs
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Grayscale
pdfToImage.blackwhite=Black and White (May lose data!) pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert pdfToImage.submit=Convert
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Solicitud no válida
login.oauth2AccessDenied=Acceso denegado 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.relyingPartyRegistrationNotFound=No relying party registration found
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=Ya has iniciado sesión en
login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo. login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparar repair.title=Reparar
repair.header=Reparar archivos PDF repair.header=Reparar archivos PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Escala de grises
pdfToImage.blackwhite=Blanco y Negro (¡Puede perder datos!) pdfToImage.blackwhite=Blanco y Negro (¡Puede perder datos!)
pdfToImage.submit=Convertir pdfToImage.submit=Convertir
pdfToImage.info=Python no está instalado. Se requiere para la conversión WebP. pdfToImage.info=Python no está instalado. Se requiere para la conversión WebP.
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Dividir PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token login.oauth2InvalidIdToken=Invalid Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Konpondu repair.title=Konpondu
repair.header=Konpondu PDF fitxategiak repair.header=Konpondu PDF fitxategiak
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Gris-eskala
pdfToImage.blackwhite=Zuria eta Beltza (Datuak galdu ditzake!) pdfToImage.blackwhite=Zuria eta Beltza (Datuak galdu ditzake!)
pdfToImage.submit=Bihurtu pdfToImage.submit=Bihurtu
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=درخواست نامعتبر
login.oauth2AccessDenied=دسترسی ممنوع login.oauth2AccessDenied=دسترسی ممنوع
login.oauth2InvalidTokenResponse=پاسخ توکن نامعتبر است login.oauth2InvalidTokenResponse=پاسخ توکن نامعتبر است
login.oauth2InvalidIdToken=توکن شناسه نامعتبر است login.oauth2InvalidIdToken=توکن شناسه نامعتبر است
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=کاربر غیرفعال شده است، ورود با این نام کاربری در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید. login.userIsDisabled=کاربر غیرفعال شده است، ورود با این نام کاربری در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید.
login.alreadyLoggedIn=شما قبلاً وارد شده‌اید در login.alreadyLoggedIn=شما قبلاً وارد شده‌اید در
login.alreadyLoggedIn2=دستگاه‌ها. لطفاً از دستگاه‌ها خارج شده و دوباره تلاش کنید. login.alreadyLoggedIn2=دستگاه‌ها. لطفاً از دستگاه‌ها خارج شده و دوباره تلاش کنید.
@@ -831,7 +830,7 @@ sign.first=صفحه اول
sign.last=صفحه آخر sign.last=صفحه آخر
sign.next=صفحه بعدی sign.next=صفحه بعدی
sign.previous=صفحه قبلی sign.previous=صفحه قبلی
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=تعمیر repair.title=تعمیر
repair.header=تعمیر PDFها repair.header=تعمیر PDFها
@@ -1042,7 +1041,6 @@ pdfToImage.grey=خاکستری
pdfToImage.blackwhite=سیاه و سفید (ممکن است اطلاعات از دست برود!) pdfToImage.blackwhite=سیاه و سفید (ممکن است اطلاعات از دست برود!)
pdfToImage.submit=تبدیل pdfToImage.submit=تبدیل
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است. pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
pdfToImage.placeholder=(مثال: 1,2,8 یا 4,7,12-16 یا 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=تقسیم PDF
fileChooser.click=کلیک کنید fileChooser.click=کلیک کنید
fileChooser.or=یا fileChooser.or=یا
fileChooser.dragAndDrop=بکشید و رها کنید fileChooser.dragAndDrop=بکشید و رها کنید
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Requête invalide
login.oauth2AccessDenied=Accès refusé login.oauth2AccessDenied=Accès refusé
login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide
login.oauth2InvalidIdToken=Jeton d'identification invalide login.oauth2InvalidIdToken=Jeton d'identification invalide
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=L'utilisateur est désactivé, la connexion est actuellement bloquée avec ce nom d'utilisateur. Veuillez contacter l'administrateur. login.userIsDisabled=L'utilisateur est désactivé, la connexion est actuellement bloquée avec ce nom d'utilisateur. Veuillez contacter l'administrateur.
login.alreadyLoggedIn=Vous êtes déjà connecté sur login.alreadyLoggedIn=Vous êtes déjà connecté sur
login.alreadyLoggedIn2=appareils. Veuillez vous déconnecter des appareils et réessayer. login.alreadyLoggedIn2=appareils. Veuillez vous déconnecter des appareils et réessayer.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Réparer repair.title=Réparer
repair.header=Réparer repair.header=Réparer
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Niveaux de gris
pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données !) pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données !)
pdfToImage.submit=Convertir pdfToImage.submit=Convertir
pdfToImage.info=Python nest pas installé. Nécessaire pour la conversion WebP. pdfToImage.info=Python nest pas installé. Nécessaire pour la conversion WebP.
pdfToImage.placeholder=(par exemple : 1,2,8 ou 4,7,12-16 ou 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Diviser le PDF
fileChooser.click=Cliquez fileChooser.click=Cliquez
fileChooser.or=ou fileChooser.or=ou
fileChooser.dragAndDrop=Glisser & Déposer fileChooser.dragAndDrop=Glisser & Déposer
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Glisser & Déposer le(s) fichier(s) ici fileChooser.hoveredDragAndDrop=Glisser & Déposer le(s) fichier(s) ici
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Iarratas Neamhbhailí
login.oauth2AccessDenied=Rochtain Diúltaithe login.oauth2AccessDenied=Rochtain Diúltaithe
login.oauth2InvalidTokenResponse=Freagra Comhartha Neamhbhailí login.oauth2InvalidTokenResponse=Freagra Comhartha Neamhbhailí
login.oauth2InvalidIdToken=Comhartha Aitheantais Neamhbhailí login.oauth2InvalidIdToken=Comhartha Aitheantais Neamhbhailí
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Deisiúchán repair.title=Deisiúchán
repair.header=PDF a dheisiú repair.header=PDF a dheisiú
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Scála Liath
pdfToImage.blackwhite=Dubh agus Bán (Dfhéadfadh sonraí a chailleadh!) pdfToImage.blackwhite=Dubh agus Bán (Dfhéadfadh sonraí a chailleadh!)
pdfToImage.submit=Tiontaigh pdfToImage.submit=Tiontaigh
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(m.sh. 1,2,8 nó 4,7,12-16 nó 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=गलत याचना
login.oauth2AccessDenied=इनपुट उम्मीदवार डिसकार login.oauth2AccessDenied=इनपुट उम्मीदवार डिसकार
login.oauth2InvalidTokenResponse=अमान्तरित सिक्वेंस जवाब कैसे है login.oauth2InvalidTokenResponse=अमान्तरित सिक्वेंस जवाब कैसे है
login.oauth2InvalidIdToken=गलत इड टोकन login.oauth2InvalidIdToken=गलत इड टोकन
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=उपयोगकर्ता डिसबाल, यह वर्षा सभी उपयोगकर्ता जूझे वर्षाकरण बारा मिल गई है। कृपया संबवादक से संपर्क करें. login.userIsDisabled=उपयोगकर्ता डिसबाल, यह वर्षा सभी उपयोगकर्ता जूझे वर्षाकरण बारा मिल गई है। कृपया संबवादक से संपर्क करें.
login.alreadyLoggedIn=आप पहले से ही login.alreadyLoggedIn=आप पहले से ही
login.alreadyLoggedIn2=सुनिश्चित करने वाले डिवाइस्स पर लॉग-इन हैं। कृपया इन डिवाइस से लॉगआउट करें और पुनः प्रयास करें login.alreadyLoggedIn2=सुनिश्चित करने वाले डिवाइस्स पर लॉग-इन हैं। कृपया इन डिवाइस से लॉगआउट करें और पुनः प्रयास करें
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=मरम्मत repair.title=मरम्मत
repair.header=पीडीएफ़ मरम्मत करें repair.header=पीडीएफ़ मरम्मत करें
@@ -1042,7 +1041,6 @@ pdfToImage.grey=ग्रे स्केल
pdfToImage.blackwhite=काला और सफेद (डेटा खो सकता है!) pdfToImage.blackwhite=काला और सफेद (डेटा खो सकता है!)
pdfToImage.submit=परिवर्तित करें pdfToImage.submit=परिवर्तित करें
pdfToImage.info=पायथन नहीं अनिस्तारित है। वेबP परिवर्तन के लिए आवश्यक है। pdfToImage.info=पायथन नहीं अनिस्तारित है। वेबP परिवर्तन के लिए आवश्यक है।
pdfToImage.placeholder=(उदाहरण के लिए 1,2,8 या 4,7,12-16 या 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDF विभाजित
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Neispravan zahtjev
login.oauth2AccessDenied=Pristup odbijen login.oauth2AccessDenied=Pristup odbijen
login.oauth2InvalidTokenResponse=Nevažeći odgovor tokena login.oauth2InvalidTokenResponse=Nevažeći odgovor tokena
login.oauth2InvalidIdToken=Nevažeći ID token login.oauth2InvalidIdToken=Nevažeći ID token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Korisnik je deaktiviran, prijava sa ovim korisničkim imenom je trenutno zakazana. Molimo Vas da kontaktirate administratorske osobe. login.userIsDisabled=Korisnik je deaktiviran, prijava sa ovim korisničkim imenom je trenutno zakazana. Molimo Vas da kontaktirate administratorske osobe.
login.alreadyLoggedIn=Već ste se prijavili na login.alreadyLoggedIn=Već ste se prijavili na
login.alreadyLoggedIn2=ure. Odjavite se s ure i pokušajte ponovo. login.alreadyLoggedIn2=ure. Odjavite se s ure i pokušajte ponovo.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Popravi repair.title=Popravi
repair.header=Popravi PDF datoteku repair.header=Popravi PDF datoteku
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Sivi tonovi
pdfToImage.blackwhite=Crno-bijelo (mogu se izgubiti podaci!) pdfToImage.blackwhite=Crno-bijelo (mogu se izgubiti podaci!)
pdfToImage.submit=Pretvori pdfToImage.submit=Pretvori
pdfToImage.info=Python nije instaliran. Treba je za konverziju na WebP. pdfToImage.info=Python nije instaliran. Treba je za konverziju na WebP.
pdfToImage.placeholder=(t.j. 1,2,8 ili 4,7,12-16 ili 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Podijeli PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Érvénytelen kérelem
login.oauth2AccessDenied=Hozzáférés megtagadva login.oauth2AccessDenied=Hozzáférés megtagadva
login.oauth2InvalidTokenResponse=Érvénytelen token-válasz login.oauth2InvalidTokenResponse=Érvénytelen token-válasz
login.oauth2InvalidIdToken=Érvénytelen azonosító token login.oauth2InvalidIdToken=Érvénytelen azonosító token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=A felhasználó deaktivált, a bejelentkezés jelenleg megszakítva ezzel a felhasználónévvel. Kérjen segítséget a rendszergazdától. login.userIsDisabled=A felhasználó deaktivált, a bejelentkezés jelenleg megszakítva ezzel a felhasználónévvel. Kérjen segítséget a rendszergazdától.
login.alreadyLoggedIn=Már be van jelentkezve az login.alreadyLoggedIn=Már be van jelentkezve az
login.alreadyLoggedIn2=eszközökre. Kijelentkezzen ezekből a eszközökből, majd próbálja újra bejelentkezni. login.alreadyLoggedIn2=eszközökre. Kijelentkezzen ezekből a eszközökből, majd próbálja újra bejelentkezni.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Javítás repair.title=Javítás
repair.header=PDF-ek javítása repair.header=PDF-ek javítása
@@ -1042,7 +1041,6 @@ pdfToImage.grey=szürkeárnyalatos
pdfToImage.blackwhite=fekete-fehér (adatvesztéssel járhat!) pdfToImage.blackwhite=fekete-fehér (adatvesztéssel járhat!)
pdfToImage.submit=Átalakítás pdfToImage.submit=Átalakítás
pdfToImage.info=Nincs telepítve a Python. Szükséges a WebP konverzióhoz. pdfToImage.info=Nincs telepítve a Python. Szükséges a WebP konverzióhoz.
pdfToImage.placeholder=(pl. 1,2,8 vagy 4,7,12-16 vagy 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDF osztás
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Permintaan Tidak Valid
login.oauth2AccessDenied=Akses Ditolak login.oauth2AccessDenied=Akses Ditolak
login.oauth2InvalidTokenResponse=Respons Token Tidak Valid login.oauth2InvalidTokenResponse=Respons Token Tidak Valid
login.oauth2InvalidIdToken=Token ID Tidak Valid login.oauth2InvalidIdToken=Token ID Tidak Valid
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Pengguna dinonaktifkan, login saat ini diblokir dengan nama pengguna ini. Silakan hubungi administrator. login.userIsDisabled=Pengguna dinonaktifkan, login saat ini diblokir dengan nama pengguna ini. Silakan hubungi administrator.
login.alreadyLoggedIn=Anda sudah login ke login.alreadyLoggedIn=Anda sudah login ke
login.alreadyLoggedIn2=perangkat. Silakan keluar dari perangkat dan coba lagi. login.alreadyLoggedIn2=perangkat. Silakan keluar dari perangkat dan coba lagi.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Perbaiki repair.title=Perbaiki
repair.header=Perbaiki PDF repair.header=Perbaiki PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Skala abu-abu
pdfToImage.blackwhite=Black and White (Bisa kehilangan data!) pdfToImage.blackwhite=Black and White (Bisa kehilangan data!)
pdfToImage.submit=Konversi pdfToImage.submit=Konversi
pdfToImage.info=Python tidak terinstal. Diperlukan untuk konversi WebP. pdfToImage.info=Python tidak terinstal. Diperlukan untuk konversi WebP.
pdfToImage.placeholder=(misalnya 1,2,8 atau 4,7,12-16 atau 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Pecah PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Richiesta non valida
login.oauth2AccessDenied=Accesso negato login.oauth2AccessDenied=Accesso negato
login.oauth2InvalidTokenResponse=Risposta token non valida login.oauth2InvalidTokenResponse=Risposta token non valida
login.oauth2InvalidIdToken=Id Token non valido login.oauth2InvalidIdToken=Id Token non valido
login.relyingPartyRegistrationNotFound=Nessuna registrazione di parte affidabile trovata
login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore. login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore.
login.alreadyLoggedIn=Hai già effettuato l'accesso a login.alreadyLoggedIn=Hai già effettuato l'accesso a
login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova. login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova.
@@ -831,7 +830,7 @@ sign.first=Prima pagina
sign.last=Ultima pagina sign.last=Ultima pagina
sign.next=Prossima pagina sign.next=Prossima pagina
sign.previous=Pagina precedente sign.previous=Pagina precedente
sign.maintainRatio=Attiva il mantenimento delle proporzioni
#repair #repair
repair.title=Ripara repair.title=Ripara
repair.header=Ripara PDF repair.header=Ripara PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Scala di grigi
pdfToImage.blackwhite=Bianco e Nero (potresti perdere dettagli!) pdfToImage.blackwhite=Bianco e Nero (potresti perdere dettagli!)
pdfToImage.submit=Converti pdfToImage.submit=Converti
pdfToImage.info=Python non è installato.È richiesto per la conversione WebP. pdfToImage.info=Python non è installato.È richiesto per la conversione WebP.
pdfToImage.placeholder=(es. 1,2,8 o 4,7,12-16 o 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Dividi PDF
fileChooser.click=Clicca fileChooser.click=Clicca
fileChooser.or=o fileChooser.or=o
fileChooser.dragAndDrop=Trascina & Rilascia fileChooser.dragAndDrop=Trascina & Rilascia
fileChooser.dragAndDropPDF=Trascina & rilascia il file PDF
fileChooser.dragAndDropImage=Trascina & rilascia il file immagine
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=無効なリクエスト
login.oauth2AccessDenied=アクセス拒否 login.oauth2AccessDenied=アクセス拒否
login.oauth2InvalidTokenResponse=無効なトークン応答 login.oauth2InvalidTokenResponse=無効なトークン応答
login.oauth2InvalidIdToken=無効なIDトークン login.oauth2InvalidIdToken=無効なIDトークン
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。 login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
login.alreadyLoggedIn=すでにログインしています login.alreadyLoggedIn=すでにログインしています
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。 login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
@@ -831,7 +830,7 @@ sign.first=最初のページ
sign.last=最後のページ sign.last=最後のページ
sign.next=次のページ sign.next=次のページ
sign.previous=前のページ sign.previous=前のページ
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=修復 repair.title=修復
repair.header=PDFを修復 repair.header=PDFを修復
@@ -1042,7 +1041,6 @@ pdfToImage.grey=グレースケール
pdfToImage.blackwhite=白黒 (データが失われる可能性があります!) pdfToImage.blackwhite=白黒 (データが失われる可能性があります!)
pdfToImage.submit=変換 pdfToImage.submit=変換
pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。 pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。
pdfToImage.placeholder=(例:1,2,8、4,7,12-16、2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDFを分割
fileChooser.click=クリック fileChooser.click=クリック
fileChooser.or=または fileChooser.or=または
fileChooser.dragAndDrop=ドラッグ&ドロップ fileChooser.dragAndDrop=ドラッグ&ドロップ
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token login.oauth2InvalidIdToken=Invalid Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=복구 repair.title=복구
repair.header=PDF 복구 repair.header=PDF 복구
@@ -1042,7 +1041,6 @@ pdfToImage.grey=그레이스케일
pdfToImage.blackwhite=흑백 (데이터 손실 가능성 있음!) pdfToImage.blackwhite=흑백 (데이터 손실 가능성 있음!)
pdfToImage.submit=변환 pdfToImage.submit=변환
pdfToImage.info=Python이 설치되어 있지 않습니다. WebP 변환에 필요합니다. pdfToImage.info=Python이 설치되어 있지 않습니다. WebP 변환에 필요합니다.
pdfToImage.placeholder=(예: 1,2,8 또는 4,7,12-16 또는 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDF 분할
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Ongeldig verzoek
login.oauth2AccessDenied=Toegang geweigerd login.oauth2AccessDenied=Toegang geweigerd
login.oauth2InvalidTokenResponse=Ongeldige tokenreactie login.oauth2InvalidTokenResponse=Ongeldige tokenreactie
login.oauth2InvalidIdToken=Ongeldige ID token login.oauth2InvalidIdToken=Ongeldige ID token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=De gebruiker is gedesactiveerd, inloggen is momenteel geblokkeerd voor deze gebruikersnaam. Neem contact op met de beheerder. login.userIsDisabled=De gebruiker is gedesactiveerd, inloggen is momenteel geblokkeerd voor deze gebruikersnaam. Neem contact op met de beheerder.
login.alreadyLoggedIn=U zit reeds ingelogd bij login.alreadyLoggedIn=U zit reeds ingelogd bij
login.alreadyLoggedIn2=apparaten. U moet u a.u.b. uitloggen van de apparaten en opnieuw proberen. login.alreadyLoggedIn2=apparaten. U moet u a.u.b. uitloggen van de apparaten en opnieuw proberen.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Repareren repair.title=Repareren
repair.header=PDF's repareren repair.header=PDF's repareren
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Grijstinten
pdfToImage.blackwhite=Zwart en wit (kan data verliezen!) pdfToImage.blackwhite=Zwart en wit (kan data verliezen!)
pdfToImage.submit=Omzetten pdfToImage.submit=Omzetten
pdfToImage.info=Python is niet geïnstalleerd. Vereist voor WebP-conversie. pdfToImage.info=Python is niet geïnstalleerd. Vereist voor WebP-conversie.
pdfToImage.placeholder=(bijv. 1,2,8 of 4,7,12-16 of 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=PDF splitsen
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Ugyldig forespørsel
login.oauth2AccessDenied=Tilgang nektet login.oauth2AccessDenied=Tilgang nektet
login.oauth2InvalidTokenResponse=Ugyldig tokenrespons login.oauth2InvalidTokenResponse=Ugyldig tokenrespons
login.oauth2InvalidIdToken=Ugyldig Id Token login.oauth2InvalidIdToken=Ugyldig Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparer repair.title=Reparer
repair.header=Reparer PDF-er repair.header=Reparer PDF-er
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Gråtone
pdfToImage.blackwhite=Svart-hvitt (kan miste data!) pdfToImage.blackwhite=Svart-hvitt (kan miste data!)
pdfToImage.submit=Konverter pdfToImage.submit=Konverter
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Nieprawidłowe żądanie
login.oauth2AccessDenied=Brak dostępu login.oauth2AccessDenied=Brak dostępu
login.oauth2InvalidTokenResponse=Nieprawidłowa odpowiedź na token login.oauth2InvalidTokenResponse=Nieprawidłowa odpowiedź na token
login.oauth2InvalidIdToken=Nieprawidłowa wartość tokenu login.oauth2InvalidIdToken=Nieprawidłowa wartość tokenu
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Użytkownik jest nieaktywny, logowanie przy użyciu tej nazwy użytkownika jest obecnie zablokowane. Prosimy o kontakt z administratorem. login.userIsDisabled=Użytkownik jest nieaktywny, logowanie przy użyciu tej nazwy użytkownika jest obecnie zablokowane. Prosimy o kontakt z administratorem.
login.alreadyLoggedIn=Jesteś już zalogowany na login.alreadyLoggedIn=Jesteś już zalogowany na
login.alreadyLoggedIn2=urządzeniach. Wyloguj się z tych urządzeń i spróbuj ponownie. login.alreadyLoggedIn2=urządzeniach. Wyloguj się z tych urządzeń i spróbuj ponownie.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Napraw repair.title=Napraw
repair.header=Napraw dokument(y) PDF repair.header=Napraw dokument(y) PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Odcień szarości
pdfToImage.blackwhite=Czarno-biały (może spowodować utratę danych!) pdfToImage.blackwhite=Czarno-biały (może spowodować utratę danych!)
pdfToImage.submit=Konwertuj pdfToImage.submit=Konwertuj
pdfToImage.info=Python nie został zainstalowany. Jest wymagany do konwersji WebP. pdfToImage.info=Python nie został zainstalowany. Jest wymagany do konwersji WebP.
pdfToImage.placeholder=(przykład 1,2,8 lub 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Podziel PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Requisição Inválida
login.oauth2AccessDenied=Acesso Negado login.oauth2AccessDenied=Acesso Negado
login.oauth2InvalidTokenResponse=Resposta de Token Inválida login.oauth2InvalidTokenResponse=Resposta de Token Inválida
login.oauth2InvalidIdToken=Id de Token Inválido login.oauth2InvalidIdToken=Id de Token Inválido
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador. login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador.
login.alreadyLoggedIn=Você já está conectado em login.alreadyLoggedIn=Você já está conectado em
login.alreadyLoggedIn2=aparelhos. Por favor saia dos aparelhos e tente novamente. login.alreadyLoggedIn2=aparelhos. Por favor saia dos aparelhos e tente novamente.
@@ -831,7 +830,7 @@ sign.first=Primeira página
sign.last=Última página sign.last=Última página
sign.next=Próxima página sign.next=Próxima página
sign.previous=Página anterior sign.previous=Página anterior
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparar repair.title=Reparar
repair.header=Reparar repair.header=Reparar
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Escala de Cinza
pdfToImage.blackwhite=Preto e Branco (pode perder informações!) pdfToImage.blackwhite=Preto e Branco (pode perder informações!)
pdfToImage.submit=Converter pdfToImage.submit=Converter
pdfToImage.info=Python não está instalado. Necessário para conversão WebP. pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
pdfToImage.placeholder=(por exemplo 1,2,8 or 4,7,12-16 ou 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Dividir
fileChooser.click=Clique fileChooser.click=Clique
fileChooser.or=ou fileChooser.or=ou
fileChooser.dragAndDrop=Arraste & Solte fileChooser.dragAndDrop=Arraste & Solte
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Arraste & Solte arquivo(s) aqui fileChooser.hoveredDragAndDrop=Arraste & Solte arquivo(s) aqui
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Requisito inválido
login.oauth2AccessDenied=Acesso negado login.oauth2AccessDenied=Acesso negado
login.oauth2InvalidTokenResponse=Resposta de token inválida login.oauth2InvalidTokenResponse=Resposta de token inválida
login.oauth2InvalidIdToken=Token de identificação inválido login.oauth2InvalidIdToken=Token de identificação inválido
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=O utilizador foi desativado, o login está atualmente bloqueado com esta conta. Por favor, contacte o administrador. login.userIsDisabled=O utilizador foi desativado, o login está atualmente bloqueado com esta conta. Por favor, contacte o administrador.
login.alreadyLoggedIn=Já está logado em login.alreadyLoggedIn=Já está logado em
login.alreadyLoggedIn2=dispositivos. Por favor, faça logout nos dispositivos e tente novamente. login.alreadyLoggedIn2=dispositivos. Por favor, faça logout nos dispositivos e tente novamente.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparar repair.title=Reparar
repair.header=Reparar PDFs repair.header=Reparar PDFs
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Escala de Cinza
pdfToImage.blackwhite=Preto e Branco (pode resultar em perda de dados!) pdfToImage.blackwhite=Preto e Branco (pode resultar em perda de dados!)
pdfToImage.submit=Converter pdfToImage.submit=Converter
pdfToImage.info=O Python não está instalado. Necessário para a conversão de WebP. pdfToImage.info=O Python não está instalado. Necessário para a conversão de WebP.
pdfToImage.placeholder=(ex: 1,2,8 ou 4,7,12-16 ou 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Dividir o PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Cerere Invalidă
login.oauth2AccessDenied=Acces Refuzat login.oauth2AccessDenied=Acces Refuzat
login.oauth2InvalidTokenResponse=Răspuns Invalid la Token login.oauth2InvalidTokenResponse=Răspuns Invalid la Token
login.oauth2InvalidIdToken=Token de Id Invalid login.oauth2InvalidIdToken=Token de Id Invalid
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Utilizatorul este dezactivat, conectarea este în prezent blocată cu acest nume de utilizator. Te rugăm să contactezi administratorul. login.userIsDisabled=Utilizatorul este dezactivat, conectarea este în prezent blocată cu acest nume de utilizator. Te rugăm să contactezi administratorul.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Repară repair.title=Repară
repair.header=Repară documente PDF repair.header=Repară documente PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Scală de gri
pdfToImage.blackwhite=Alb și negru (Poate pierde date!) pdfToImage.blackwhite=Alb și negru (Poate pierde date!)
pdfToImage.submit=Convertește pdfToImage.submit=Convertește
pdfToImage.info=Python nu este instalat. Necesar pentru conversia WebP. pdfToImage.info=Python nu este instalat. Necesar pentru conversia WebP.
pdfToImage.placeholder=(ex. 1,2,8 sau 4,7,12-16 sau 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Неверный запрос
login.oauth2AccessDenied=Доступ запрещен login.oauth2AccessDenied=Доступ запрещен
login.oauth2InvalidTokenResponse=Недействительный ответ токена login.oauth2InvalidTokenResponse=Недействительный ответ токена
login.oauth2InvalidIdToken=Недействительный идентификационный токен login.oauth2InvalidIdToken=Недействительный идентификационный токен
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Пользователь деактивирован, вход с данным именем пользователя заблокирован. Пожалуйста, обратитесь к администратору. login.userIsDisabled=Пользователь деактивирован, вход с данным именем пользователя заблокирован. Пожалуйста, обратитесь к администратору.
login.alreadyLoggedIn=Вы уже вошли в login.alreadyLoggedIn=Вы уже вошли в
login.alreadyLoggedIn2=устройства. Выполните выход из устройств и попробуйте снова. login.alreadyLoggedIn2=устройства. Выполните выход из устройств и попробуйте снова.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Ремонт repair.title=Ремонт
repair.header=Ремонт PDF ов repair.header=Ремонт PDF ов
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Оттенки серого
pdfToImage.blackwhite=Черно-белый (может потерять данные!) pdfToImage.blackwhite=Черно-белый (может потерять данные!)
pdfToImage.submit=Конвертировать pdfToImage.submit=Конвертировать
pdfToImage.info=Питон не установлен. Необходим для конвертации в WebP. pdfToImage.info=Питон не установлен. Необходим для конвертации в WebP.
pdfToImage.placeholder=(например 1,2,8 или 4,7,12-16 или 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Разделить PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token login.oauth2InvalidIdToken=Invalid Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Opraviť repair.title=Opraviť
repair.header=Opraviť PDF repair.header=Opraviť PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Odtiene šedej
pdfToImage.blackwhite=Čierno-biele (Môže stratiť údaje!) pdfToImage.blackwhite=Čierno-biele (Môže stratiť údaje!)
pdfToImage.submit=Konvertovať pdfToImage.submit=Konvertovať
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(napr. 1,2,8 alebo 4,7,12-16 alebo 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token login.oauth2InvalidIdToken=Invalid Id Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Popravi repair.title=Popravi
repair.header=Popravi PDF fajlove repair.header=Popravi PDF fajlove
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Nijanse sive
pdfToImage.blackwhite=Crno-belo (Može izgubiti podatke!) pdfToImage.blackwhite=Crno-belo (Može izgubiti podatke!)
pdfToImage.submit=Konvertuj pdfToImage.submit=Konvertuj
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -250,7 +250,7 @@ database.fileNullOrEmpty=Filen får inte vara null eller tom
database.failedImportFile=Misslyckades med att importera fil database.failedImportFile=Misslyckades med att importera fil
session.expired=Din session har löpt ut. Uppdatera sidan och försök igen. session.expired=Din session har löpt ut. Uppdatera sidan och försök igen.
session.refreshPage=Uppdatera sida session.refreshPage=Refresh Page
############# #############
# HOME-PAGE # # HOME-PAGE #
@@ -519,9 +519,9 @@ home.validateSignature.desc=Verify digital signatures and certificates in PDF do
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
#replace-invert-color #replace-invert-color
replace-color.title=Ersätt-Invertera-Färg replace-color.title=Replace-Invert-Color
replace-color.header=Ersätt-Invertera färg på PDF replace-color.header=Ersätt-Invertera färg på PDF
home.replaceColorPdf.title=Ersätt och Invertera färg home.replaceColorPdf.title=Replace and Invert Color
home.replaceColorPdf.desc=Ersätt färg för text och bakgrund i PDF och invertera hela färgen på PDF för att minska filstorlek home.replaceColorPdf.desc=Ersätt färg för text och bakgrund i PDF och invertera hela färgen på PDF för att minska filstorlek
replaceColorPdf.tags=Ersätt Färg, Sidåtgärder, Bakomliggande, Serversid replaceColorPdf.tags=Ersätt Färg, Sidåtgärder, Bakomliggande, Serversid
replace-color.selectText.1=Ersätt eller Invertera färgalternativ replace-color.selectText.1=Ersätt eller Invertera färgalternativ
@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Ogiltig begäran
login.oauth2AccessDenied=Åtkomst nekad login.oauth2AccessDenied=Åtkomst nekad
login.oauth2InvalidTokenResponse=Ogiltigt token-svar login.oauth2InvalidTokenResponse=Ogiltigt token-svar
login.oauth2InvalidIdToken=Ogiltigt Id-token login.oauth2InvalidIdToken=Ogiltigt Id-token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Användaren är inaktiverad, inloggning är för närvarande blockerad med detta användarnamn. Kontakta administratören. login.userIsDisabled=Användaren är inaktiverad, inloggning är för närvarande blockerad med detta användarnamn. Kontakta administratören.
login.alreadyLoggedIn=Du är redan inloggad på login.alreadyLoggedIn=Du är redan inloggad på
login.alreadyLoggedIn2=enheter. Logga ut från enheterna och försök igen. login.alreadyLoggedIn2=enheter. Logga ut från enheterna och försök igen.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Reparera repair.title=Reparera
repair.header=Reparera PDF-filer repair.header=Reparera PDF-filer
@@ -969,17 +968,17 @@ multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt #decrypt
decrypt.passwordPrompt=Denna fil är lösenordsskyddad. Fyll i lösenord: decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation misslyckades för PDF: {0} decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=Inget lösenord angivet för krypterad PDF: {0} decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Försök igen med korrekt lösenord. decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Felaktigt lösenord eller osupportad kryptering för PDF: {0} decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=Det uppstod ett fel vid processering av filen. Vänligen försök igen. decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Serverfel vid avkryptering: {0} decrypt.serverError=Server error while decrypting: {0}
decrypt.success=Fil avkrypterad. decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Denna funktion finns också tillgänglig i vår <a href="{0}">multi-tool page</a>. Spana in den för bättre sida-för-sida anpassning och ytterligare funktioner! 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=Visa PDF viewPdf.title=Visa PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Gråskala
pdfToImage.blackwhite=Svartvitt (kan förlora data!) pdfToImage.blackwhite=Svartvitt (kan förlora data!)
pdfToImage.submit=Konvertera pdfToImage.submit=Konvertera
pdfToImage.info=Python är inte installerat. Krävs för WebP-konvertering. pdfToImage.info=Python är inte installerat. Krävs för WebP-konvertering.
pdfToImage.placeholder=(t.ex. 1,2,8 eller 4,7,12-16 eller 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Dela upp PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=คำขอไม่ถูกต้อง
login.oauth2AccessDenied=การเข้าถึงถูกปฏิเสธ login.oauth2AccessDenied=การเข้าถึงถูกปฏิเสธ
login.oauth2InvalidTokenResponse=การตอบกลับโทเค็นไม่ถูกต้อง login.oauth2InvalidTokenResponse=การตอบกลับโทเค็นไม่ถูกต้อง
login.oauth2InvalidIdToken=โทเค็น Id ไม่ถูกต้อง login.oauth2InvalidIdToken=โทเค็น Id ไม่ถูกต้อง
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=ผู้ใช้งานถูกระงับการใช้งาน ไม่สามารถเข้าสู่ระบบด้วยชื่อผู้ใช้นี้ได้ กรุณาติดต่อผู้ดูแลระบบ login.userIsDisabled=ผู้ใช้งานถูกระงับการใช้งาน ไม่สามารถเข้าสู่ระบบด้วยชื่อผู้ใช้นี้ได้ กรุณาติดต่อผู้ดูแลระบบ
login.alreadyLoggedIn=คุณได้เข้าสู่ระบบใน login.alreadyLoggedIn=คุณได้เข้าสู่ระบบใน
login.alreadyLoggedIn2=อุปกรณ์แล้ว กรุณาออกจากระบบจากอุปกรณ์ที่ใช้งานอยู่แล้ว จากนั้นลองใหม่อีกครั้ง login.alreadyLoggedIn2=อุปกรณ์แล้ว กรุณาออกจากระบบจากอุปกรณ์ที่ใช้งานอยู่แล้ว จากนั้นลองใหม่อีกครั้ง
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=ซ่อมแซม repair.title=ซ่อมแซม
repair.header=ซ่อมแซม PDF repair.header=ซ่อมแซม PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=ระดับสีเทา
pdfToImage.blackwhite=ขาวดำ (อาจสูญเสียข้อมูล!) pdfToImage.blackwhite=ขาวดำ (อาจสูญเสียข้อมูล!)
pdfToImage.submit=แปลง pdfToImage.submit=แปลง
pdfToImage.info=Python ไม่มีการติดตั้ง จำเป็นสำหรับการแปลง WebP pdfToImage.info=Python ไม่มีการติดตั้ง จำเป็นสำหรับการแปลง WebP
pdfToImage.placeholder=(เช่น 1,2,8 หรือ 4,7,12-16 หรือ 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=แบ่งไฟล์ PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Geçersiz İstek
login.oauth2AccessDenied=Erişim Reddedildi login.oauth2AccessDenied=Erişim Reddedildi
login.oauth2InvalidTokenResponse=Geçersiz Belirteç Yanıtı login.oauth2InvalidTokenResponse=Geçersiz Belirteç Yanıtı
login.oauth2InvalidIdToken=Geçersiz Kimlik Belirteci login.oauth2InvalidIdToken=Geçersiz Kimlik Belirteci
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullanıcı adıyla giriş engellendi. Lütfen yöneticiyle iletişime geçin. login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullanıcı adıyla giriş engellendi. Lütfen yöneticiyle iletişime geçin.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Onar repair.title=Onar
repair.header=PDF'leri Onar repair.header=PDF'leri Onar
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Gri tonlama
pdfToImage.blackwhite=Siyah ve Beyaz (Veri kaybolabilir!) pdfToImage.blackwhite=Siyah ve Beyaz (Veri kaybolabilir!)
pdfToImage.submit=Dönüştür pdfToImage.submit=Dönüştür
pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir. pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir.
pdfToImage.placeholder=(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Недійсний запит
login.oauth2AccessDenied=Доступ заблоковано login.oauth2AccessDenied=Доступ заблоковано
login.oauth2InvalidTokenResponse=Недійсна відповідь з токеном login.oauth2InvalidTokenResponse=Недійсна відповідь з токеном
login.oauth2InvalidIdToken=Недійсний Id токен login.oauth2InvalidIdToken=Недійсний Id токен
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Ремонт repair.title=Ремонт
repair.header=Ремонт PDF repair.header=Ремонт PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Відтінки сірого
pdfToImage.blackwhite=Чорно-білий (може втратити дані!) pdfToImage.blackwhite=Чорно-білий (може втратити дані!)
pdfToImage.submit=Конвертувати pdfToImage.submit=Конвертувати
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(наприклад 1,2,8 або 4,7,12-16 або 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=Yêu cầu không hợp lệ
login.oauth2AccessDenied=Truy cập bị từ chối login.oauth2AccessDenied=Truy cập bị từ chối
login.oauth2InvalidTokenResponse=Phản hồi token không hợp lệ login.oauth2InvalidTokenResponse=Phản hồi token không hợp lệ
login.oauth2InvalidIdToken=Id Token không hợp lệ login.oauth2InvalidIdToken=Id Token không hợp lệ
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
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.
@@ -831,7 +830,7 @@ sign.first=First page
sign.last=Last page sign.last=Last page
sign.next=Next page sign.next=Next page
sign.previous=Previous page sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=Sửa chữa repair.title=Sửa chữa
repair.header=Sửa chữa PDF repair.header=Sửa chữa PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=Thang độ xám
pdfToImage.blackwhite=Đen trắng (Có thể mất dữ liệu!) pdfToImage.blackwhite=Đen trắng (Có thể mất dữ liệu!)
pdfToImage.submit=Chuyển đổi pdfToImage.submit=Chuyển đổi
pdfToImage.info=Python is not installed. Required for WebP conversion. pdfToImage.info=Python is not installed. Required for WebP conversion.
pdfToImage.placeholder=(ví dụ: 1,2,8 hoặc 4,7,12-16 hoặc 2n-1)
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=Split PDF
fileChooser.click=Click fileChooser.click=Click
fileChooser.or=or fileChooser.or=or
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=Drag & Drop
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=无效请求
login.oauth2AccessDenied=拒绝访问 login.oauth2AccessDenied=拒绝访问
login.oauth2InvalidTokenResponse=无效的 Token 响应 login.oauth2InvalidTokenResponse=无效的 Token 响应
login.oauth2InvalidIdToken=无效的 Token login.oauth2InvalidIdToken=无效的 Token
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=用户被禁用,登录已被阻止。请联系管理员。 login.userIsDisabled=用户被禁用,登录已被阻止。请联系管理员。
login.alreadyLoggedIn=您已经登录到了 login.alreadyLoggedIn=您已经登录到了
login.alreadyLoggedIn2=设备,请注销设备后重试。 login.alreadyLoggedIn2=设备,请注销设备后重试。
@@ -831,7 +830,7 @@ sign.first=首页
sign.last=末页 sign.last=末页
sign.next=下一页 sign.next=下一页
sign.previous=上一页 sign.previous=上一页
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=修复 repair.title=修复
repair.header=修复 PDF repair.header=修复 PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=灰度
pdfToImage.blackwhite=黑白(可能会丢失数据!)。 pdfToImage.blackwhite=黑白(可能会丢失数据!)。
pdfToImage.submit=转换 pdfToImage.submit=转换
pdfToImage.info=WebP 转换需要安装 Python pdfToImage.info=WebP 转换需要安装 Python
pdfToImage.placeholder=例如1,2,8 或 4,7,12-16 或 2n-1
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=拆分 PDF
fileChooser.click=单击 fileChooser.click=单击
fileChooser.or= fileChooser.or=
fileChooser.dragAndDrop=拖放文件 fileChooser.dragAndDrop=拖放文件
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=拖放文件到此处 fileChooser.hoveredDragAndDrop=拖放文件到此处
#release notes #release notes

View File

@@ -561,7 +561,6 @@ login.oauth2invalidRequest=無效的回應
login.oauth2AccessDenied=存取被拒 login.oauth2AccessDenied=存取被拒
login.oauth2InvalidTokenResponse=無效的權杖回應 login.oauth2InvalidTokenResponse=無效的權杖回應
login.oauth2InvalidIdToken=無效的識別權杖 login.oauth2InvalidIdToken=無效的識別權杖
login.relyingPartyRegistrationNotFound=No relying party registration found
login.userIsDisabled=使用者已停用,目前此使用者無法登入。請聯絡系統管理員。 login.userIsDisabled=使用者已停用,目前此使用者無法登入。請聯絡系統管理員。
login.alreadyLoggedIn=您已經登入了 login.alreadyLoggedIn=您已經登入了
login.alreadyLoggedIn2=個裝置。請登出其他裝置後再試一次。 login.alreadyLoggedIn2=個裝置。請登出其他裝置後再試一次。
@@ -831,7 +830,7 @@ sign.first=第一頁
sign.last=最後一頁 sign.last=最後一頁
sign.next=下一頁 sign.next=下一頁
sign.previous=上一頁 sign.previous=上一頁
sign.maintainRatio=Toggle maintain aspect ratio
#repair #repair
repair.title=修復 repair.title=修復
repair.header=修復 PDF repair.header=修復 PDF
@@ -1042,7 +1041,6 @@ pdfToImage.grey=灰度
pdfToImage.blackwhite=黑白(可能會遺失資料!) pdfToImage.blackwhite=黑白(可能會遺失資料!)
pdfToImage.submit=轉換 pdfToImage.submit=轉換
pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。 pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。
pdfToImage.placeholder=(例如 1,2,8 或 4,7,12-16 或 2n-1
#addPassword #addPassword
@@ -1286,8 +1284,6 @@ splitByChapters.submit=分割 PDF
fileChooser.click=點選 fileChooser.click=點選
fileChooser.or= fileChooser.or=
fileChooser.dragAndDrop=拖放檔案 fileChooser.dragAndDrop=拖放檔案
fileChooser.dragAndDropPDF=Drag & Drop PDF file
fileChooser.dragAndDropImage=Drag & Drop Image file
fileChooser.hoveredDragAndDrop=將檔案拖放至此 fileChooser.hoveredDragAndDrop=將檔案拖放至此
#release notes #release notes

View File

@@ -84,6 +84,27 @@
"moduleLicense": "The Apache Software License, Version 2.0", "moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
}, },
{
"moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-base",
"moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-base",
"moduleVersion": "2.18.2",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider",
"moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-json-provider",
"moduleVersion": "2.18.2",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "com.fasterxml.jackson.module:jackson-module-jaxb-annotations",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-base",
"moduleVersion": "2.18.2",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{ {
"moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names", "moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names",
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names", "moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names",
@@ -341,6 +362,20 @@
"moduleLicense": "The BSD License", "moduleLicense": "The BSD License",
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
}, },
{
"moduleName": "com.unboundid.product.scim2:scim2-sdk-client",
"moduleUrl": "https://github.com/pingidentity/scim2",
"moduleVersion": "2.3.5",
"moduleLicense": "UnboundID SCIM2 SDK Free Use License",
"moduleLicenseUrl": "https://github.com/pingidentity/scim2"
},
{
"moduleName": "com.unboundid.product.scim2:scim2-sdk-common",
"moduleUrl": "https://github.com/pingidentity/scim2",
"moduleVersion": "2.3.5",
"moduleLicense": "UnboundID SCIM2 SDK Free Use License",
"moduleLicenseUrl": "https://github.com/pingidentity/scim2"
},
{ {
"moduleName": "com.zaxxer:HikariCP", "moduleName": "com.zaxxer:HikariCP",
"moduleUrl": "https://github.com/brettwooldridge/HikariCP", "moduleUrl": "https://github.com/brettwooldridge/HikariCP",
@@ -549,6 +584,20 @@
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception", "moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html" "moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
}, },
{
"moduleName": "javax.activation:javax.activation-api",
"moduleUrl": "http://www.oracle.com",
"moduleVersion": "1.2.0",
"moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0",
"moduleLicenseUrl": "https://opensource.org/licenses/CDDL-1.0"
},
{
"moduleName": "javax.xml.bind:jaxb-api",
"moduleUrl": "http://www.oracle.com/",
"moduleVersion": "2.3.1",
"moduleLicense": "GPL2 w/ CPE",
"moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL+GPL-1.1"
},
{ {
"moduleName": "me.friwi:gluegen-rt", "moduleName": "me.friwi:gluegen-rt",
"moduleUrl": "http://jogamp.org/gluegen/www/", "moduleUrl": "http://jogamp.org/gluegen/www/",

View File

@@ -2,32 +2,22 @@
position: relative; position: relative;
margin: 20px 0; margin: 20px 0;
} }
#pdf-canvas { #pdf-canvas {
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
width: 100%; width: 100%;
} }
.draggable-buttons-box { .draggable-buttons-box {
position: relative; position: absolute;
top: 0; top: 0;
padding: 10px; padding: 10px;
width: calc(100% + 4.4rem); width: 100%;
display: flex; display: flex;
gap: 5px; gap: 5px;
z-index: 5;
margin-left: -2.2rem;
} }
.draggable-buttons-box > button {
.draggable-buttons-box>button { z-index: 10;
z-index: 4;
background-color: rgba(13, 110, 253, 0.1); background-color: rgba(13, 110, 253, 0.1);
flex: 1 1 auto;
min-width: 2.5rem;
max-width: 4rem;
} }
.draggable-canvas { .draggable-canvas {
border: 1px solid red; border: 1px solid red;
position: absolute; position: absolute;
@@ -36,20 +26,3 @@
top: 0px; top: 0px;
left: 0; left: 0;
} }
.input-with-icon {
position: relative;
display: inline-flex;
align-items: center;
}
.input-with-icon .icon {
position: absolute;
left: 0.5rem;
pointer-events: none;
color: #aaa;
}
.input-with-icon input {
padding-left: 2.2rem;
}

View File

@@ -118,7 +118,6 @@
row-gap: 1px; row-gap: 1px;
height: 60px; height: 60px;
width: 60px; width: 60px;
top:4px;
} }
.file-icon { .file-icon {
@@ -166,8 +165,8 @@
height: 15px; height: 15px;
width: 15px; width: 15px;
right: 0px; right: 10px;
top: -17px; top: -5px;
} }
.remove-selected-file * { .remove-selected-file * {
@@ -220,54 +219,3 @@
border-radius: 1rem; border-radius: 1rem;
border: 1px solid rgb(105, 116, 134, 0.5); border: 1px solid rgb(105, 116, 134, 0.5);
} }
.draggable-image-overlay{
position: absolute;
background: rgba(0, 0, 0, 0.7);
display: none;
z-index: 10;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
font-weight: bold;
pointer-events: none;
left:0;
top:0;
height:100%;
width:100%;
border-radius: 1rem;
}
.small-file-container:hover .drag-icon {
display: flex;
}
.drag-icon {
display: none;
position: absolute;
top: 5px;
width: 20px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
color: white;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 14px;
pointer-events: none;
z-index: 1;
}
#imagePreviewModal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}

View File

@@ -22,38 +22,18 @@ select#font-select option {
} }
.draggable-buttons-box { .draggable-buttons-box {
position: relative; position: absolute;
top: 0; top: 0;
padding: 10px; padding: 10px;
width: calc(100% + 4.4rem); width: 100%;
display: flex; display: flex;
gap: 5px; gap: 5px;
z-index: 5; z-index: 5;
margin-left: -2.2rem;
} }
.draggable-buttons-box>button { .draggable-buttons-box>button {
z-index: 4; z-index: 4;
background-color: rgba(13, 110, 253, 0.1); background-color: rgba(13, 110, 253, 0.1);
flex: 1 1 auto;
min-width: 2.5rem;
max-width: 4rem;
}
.rotation-handle {
width: 20px;
height: 20px;
border: 2px solid #3498db;
background-color: rgba(52, 152, 219, 0.1);
color: white;
border-radius: 50%;
text-align: center;
line-height: 20px;
position: absolute;
cursor: grab;
top: -30px;
left: calc(50% - 10px);
} }
.draggable-canvas { .draggable-canvas {
@@ -133,33 +113,3 @@ select#font-select option {
text-align: right; text-align: right;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
} }
.input-with-icon {
position: relative;
display: inline-flex;
align-items: center;
}
.input-with-icon .icon {
position: absolute;
left: 0.5rem;
pointer-events: none;
color: #aaa;
}
.input-with-icon input {
padding-left: 2.2rem;
}
.small-file-container-saved {
padding-top: 1px;
position: relative;
row-gap: 1px;
height: 60px;
width: 60px;
top: 4px;
}
.small-file-container-saved:hover .drag-icon {
display: flex;
}

View File

@@ -7,137 +7,75 @@ const DraggableUtils = {
elementAllPages: [], elementAllPages: [],
documentsMap: new Map(), documentsMap: new Map(),
lastInteracted: null, lastInteracted: null,
padding: 15,
maintainRatioEnabled: true,
init() { init() {
interact('.draggable-canvas') interact('.draggable-canvas')
.draggable({ .draggable({
listeners: { listeners: {
start(event) {
const target = event.target;
x = parseFloat(target.getAttribute('data-bs-x'));
y = parseFloat(target.getAttribute('data-bs-y'));
},
move: (event) => { move: (event) => {
const target = event.target; const target = event.target;
const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
// Retrieve position attributes
let x = parseFloat(target.getAttribute('data-bs-x')) || 0;
let y = parseFloat(target.getAttribute('data-bs-y')) || 0;
const angle = parseFloat(target.getAttribute('data-angle')) || 0;
// Update position based on drag movement
x += event.dx;
y += event.dy;
// Apply translation to the parent container (bounding box)
target.style.transform = `translate(${x}px, ${y}px)`; target.style.transform = `translate(${x}px, ${y}px)`;
// Preserve rotation on the inner canvas
const canvas = target.querySelector('.display-canvas');
const canvasWidth = parseFloat(canvas.style.width);
const canvasHeight = parseFloat(canvas.style.height);
const cosAngle = Math.abs(Math.cos(angle));
const sinAngle = Math.abs(Math.sin(angle));
const rotatedWidth = canvasWidth * cosAngle + canvasHeight * sinAngle;
const rotatedHeight = canvasWidth * sinAngle + canvasHeight * cosAngle;
const offsetX = (rotatedWidth - canvasWidth) / 2;
const offsetY = (rotatedHeight - canvasHeight) / 2;
canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${angle}rad)`;
// Update attributes for persistence
target.setAttribute('data-bs-x', x); target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y); target.setAttribute('data-bs-y', y);
// Set the last interacted element this.onInteraction(target);
this.lastInteracted = target; //update the last interacted element
this.lastInteracted = event.target;
}, },
}, },
}) })
.resizable({ .resizable({
edges: { left: true, right: true, bottom: true, top: true }, edges: {left: true, right: true, bottom: true, top: true},
listeners: { listeners: {
start: (event) => {
const target = event.target;
x = parseFloat(target.getAttribute('data-bs-x')) || 0;
y = parseFloat(target.getAttribute('data-bs-y')) || 0;
},
move: (event) => { move: (event) => {
const target = event.target; var target = event.target;
var x = parseFloat(target.getAttribute('data-bs-x')) || 0;
var y = parseFloat(target.getAttribute('data-bs-y')) || 0;
const MAX_CHANGE = 60; // check if control key is pressed
if (event.ctrlKey) {
const aspectRatio = target.offsetWidth / target.offsetHeight;
// preserve aspect ratio
let width = event.rect.width;
let height = event.rect.height;
let width = event.rect.width - 2 * this.padding; if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
let height = event.rect.height - 2 * this.padding; height = width / aspectRatio;
} else {
const canvas = target.querySelector('.display-canvas'); width = height * aspectRatio;
if (canvas) {
const originalWidth = parseFloat(canvas.style.width) || canvas.width;
const originalHeight = parseFloat(canvas.style.height) || canvas.height;
const angle = parseFloat(target.getAttribute('data-angle')) || 0;
const aspectRatio = originalWidth / originalHeight;
if (!event.ctrlKey && this.maintainRatioEnabled) {
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
} }
const widthChange = width - originalWidth; event.rect.width = width;
const heightChange = height - originalHeight; event.rect.height = height;
if (Math.abs(widthChange) > MAX_CHANGE || Math.abs(heightChange) > MAX_CHANGE) {
const scale = MAX_CHANGE / Math.max(Math.abs(widthChange), Math.abs(heightChange));
width = originalWidth + widthChange * scale;
height = originalHeight + heightChange * scale;
}
const cosAngle = Math.abs(Math.cos(angle));
const sinAngle = Math.abs(Math.sin(angle));
const boundingWidth = width * cosAngle + height * sinAngle;
const boundingHeight = width * sinAngle + height * cosAngle;
if (event.edges.left) {
const dx = event.deltaRect.left;
x += dx;
}
if (event.edges.top) {
const dy = event.deltaRect.top;
y += dy;
}
target.style.transform = `translate(${x}px, ${y}px)`;
target.style.width = `${boundingWidth + 2 * this.padding}px`;
target.style.height = `${boundingHeight + 2 * this.padding}px`;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.style.transform = `translate(${(boundingWidth - width) / 2}px, ${(boundingHeight - height) / 2
}px) rotate(${angle}rad)`;
target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y);
this.lastInteracted = target;
} }
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.transform = 'translate(' + x + 'px,' + y + 'px)';
target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y);
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height);
this.onInteraction(target);
}, },
}, },
modifiers: [ modifiers: [
interact.modifiers.restrictSize({ interact.modifiers.restrictSize({
min: { width: 50, height: 50 }, min: {width: 5, height: 5},
}), }),
], ],
inertia: true, inertia: true,
}); });
//Arrow key Support for Add-Image and Sign pages //Arrow key Support for Add-Image and Sign pages
if (window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) { if (window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) {
window.addEventListener('keydown', (event) => { window.addEventListener('keydown', (event) => {
@@ -179,8 +117,7 @@ const DraggableUtils = {
} }
// Update position // Update position
const angle = parseFloat(target.getAttribute('data-angle')) || 0; target.style.transform = `translate(${x}px, ${y}px)`;
target.style.transform = `translate(${x}px, ${y}px) rotate(${angle}rad)`;
target.setAttribute('data-bs-x', x); target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y); target.setAttribute('data-bs-y', y);
@@ -188,97 +125,72 @@ const DraggableUtils = {
}); });
} }
}, },
onInteraction(target) { onInteraction(target) {
this.lastInteracted = target; this.boxDragContainer.appendChild(target);
// this.boxDragContainer.appendChild(target); },
// target.appendChild(target.querySelector(".display-canvas"));
createDraggableCanvas() {
const createdCanvas = document.createElement('canvas');
createdCanvas.id = `draggable-canvas-${this.nextId++}`;
createdCanvas.classList.add('draggable-canvas');
const x = 0;
const y = 20;
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
createdCanvas.setAttribute('data-bs-x', x);
createdCanvas.setAttribute('data-bs-y', y);
//Click element in order to enable arrow keys
createdCanvas.addEventListener('click', () => {
this.lastInteracted = createdCanvas;
});
createdCanvas.onclick = (e) => this.onInteraction(e.target);
this.boxDragContainer.appendChild(createdCanvas);
//Enable Arrow keys directly after the element is created
this.lastInteracted = createdCanvas;
return createdCanvas;
}, },
createDraggableCanvasFromUrl(dataUrl) { createDraggableCanvasFromUrl(dataUrl) {
return new Promise((resolve) => { return new Promise((resolve) => {
const canvasContainer = document.createElement('div'); var myImage = new Image();
const createdCanvas = document.createElement('canvas'); // Inner canvas
const padding = this.padding;
canvasContainer.id = `draggable-canvas-${this.nextId++}`;
canvasContainer.classList.add('draggable-canvas');
createdCanvas.classList.add('display-canvas');
canvasContainer.style.position = 'absolute';
canvasContainer.style.padding = `${padding}px`;
canvasContainer.style.overflow = 'hidden';
let x = 0,
y = 30,
angle = 0;
canvasContainer.style.transform = `translate(${x}px, ${y}px)`;
canvasContainer.setAttribute('data-bs-x', x);
canvasContainer.setAttribute('data-bs-y', y);
canvasContainer.setAttribute('data-angle', angle);
canvasContainer.addEventListener('click', () => {
this.lastInteracted = canvasContainer;
this.showRotationControls(canvasContainer);
});
canvasContainer.appendChild(createdCanvas);
this.boxDragContainer.appendChild(canvasContainer);
const myImage = new Image();
myImage.src = dataUrl; myImage.src = dataUrl;
myImage.onload = () => { myImage.onload = () => {
const context = createdCanvas.getContext('2d'); var createdCanvas = this.createDraggableCanvas();
createdCanvas.width = myImage.width; createdCanvas.width = myImage.width;
createdCanvas.height = myImage.height; createdCanvas.height = myImage.height;
const imgAspect = myImage.width / myImage.height; const imgAspect = myImage.width / myImage.height;
const containerWidth = this.boxDragContainer.offsetWidth; const pdfAspect = this.boxDragContainer.offsetWidth / this.boxDragContainer.offsetHeight;
const containerHeight = this.boxDragContainer.offsetHeight;
let scaleMultiplier = Math.min(containerWidth / myImage.width, containerHeight / myImage.height); var scaleMultiplier;
const scaleFactor = 0.5; if (imgAspect > pdfAspect) {
scaleMultiplier = this.boxDragContainer.offsetWidth / myImage.width;
} else {
scaleMultiplier = this.boxDragContainer.offsetHeight / myImage.height;
}
const newWidth = myImage.width * scaleMultiplier * scaleFactor; var newWidth = createdCanvas.width;
const newHeight = myImage.height * scaleMultiplier * scaleFactor; var newHeight = createdCanvas.height;
if (scaleMultiplier < 1) {
newWidth = newWidth * scaleMultiplier;
newHeight = newHeight * scaleMultiplier;
}
// Calculate initial bounding box size createdCanvas.style.width = newWidth + 'px';
const cosAngle = Math.abs(Math.cos(angle)); createdCanvas.style.height = newHeight + 'px';
const sinAngle = Math.abs(Math.sin(angle));
const boundingWidth = newWidth * cosAngle + newHeight * sinAngle;
const boundingHeight = newWidth * sinAngle + newHeight * cosAngle;
createdCanvas.style.width = `${newWidth}px`; var myContext = createdCanvas.getContext('2d');
createdCanvas.style.height = `${newHeight}px`; myContext.drawImage(myImage, 0, 0);
resolve(createdCanvas);
canvasContainer.style.width = `${boundingWidth + 2 * padding}px`;
canvasContainer.style.height = `${boundingHeight + 2 * padding}px`;
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high';
context.drawImage(myImage, 0, 0, myImage.width, myImage.height);
this.showRotationControls(canvasContainer);
this.lastInteracted = canvasContainer;
resolve(canvasContainer);
};
myImage.onerror = () => {
console.error('Failed to load the image.');
resolve(null);
}; };
}); });
}, },
toggleMaintainRatio() {
this.maintainRatioEnabled = !this.maintainRatioEnabled;
const button = document.getElementById('ratioToggleBtn');
if (this.maintainRatioEnabled) {
button.classList.remove('btn-danger');
button.classList.add('btn-outline-secondary');
} else {
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-danger');
}
},
deleteAllDraggableCanvases() { deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove()); this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove());
}, },
@@ -354,61 +266,9 @@ const DraggableUtils = {
} }
}, },
getLastInteracted() { getLastInteracted() {
return this.lastInteracted; return this.boxDragContainer.querySelector('.draggable-canvas:last-of-type');
}, },
showRotationControls(element) {
const rotationControls = document.getElementById('rotation-controls');
const rotationInput = document.getElementById('rotation-input');
rotationControls.style.display = 'flex';
rotationInput.value = Math.round((parseFloat(element.getAttribute('data-angle')) * 180) / Math.PI);
rotationInput.addEventListener('input', this.handleRotationInputChange);
},
hideRotationControls() {
const rotationControls = document.getElementById('rotation-controls');
const rotationInput = document.getElementById('rotation-input');
rotationControls.style.display = 'none';
rotationInput.addEventListener('input', this.handleRotationInputChange);
},
applyRotationToElement(element, degrees) {
const radians = degrees * (Math.PI / 180); // Convert degrees to radians
// Get current position
const x = parseFloat(element.getAttribute('data-bs-x')) || 0;
const y = parseFloat(element.getAttribute('data-bs-y')) || 0;
// Get the inner canvas (image)
const canvas = element.querySelector('.display-canvas');
if (canvas) {
const originalWidth = parseFloat(canvas.style.width);
const originalHeight = parseFloat(canvas.style.height);
const padding = this.padding; // Access the padding value
// Calculate rotated bounding box dimensions
const cosAngle = Math.abs(Math.cos(radians));
const sinAngle = Math.abs(Math.sin(radians));
const boundingWidth = originalWidth * cosAngle + originalHeight * sinAngle + 2 * padding;
const boundingHeight = originalWidth * sinAngle + originalHeight * cosAngle + 2 * padding;
// Update parent container to fit the rotated bounding box
element.style.width = `${boundingWidth}px`;
element.style.height = `${boundingHeight}px`;
// Center the canvas within the bounding box, accounting for padding
const offsetX = (boundingWidth - originalWidth) / 2 - padding;
const offsetY = (boundingHeight - originalHeight) / 2 - padding;
canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${radians}rad)`;
}
// Keep the bounding box positioned properly
element.style.transform = `translate(${x}px, ${y}px)`;
element.setAttribute('data-angle', radians);
},
handleRotationInputChange() {
const rotationInput = document.getElementById('rotation-input');
const degrees = parseFloat(rotationInput.value) || 0;
DraggableUtils.applyRotationToElement(DraggableUtils.lastInteracted, degrees);
},
storePageContents() { storePageContents() {
var pagesMap = this.documentsMap.get(this.pdfDoc); var pagesMap = this.documentsMap.get(this.pdfDoc);
if (!pagesMap) { if (!pagesMap) {
@@ -465,7 +325,7 @@ const DraggableUtils = {
// render the page onto the canvas // render the page onto the canvas
var renderContext = { var renderContext = {
canvasContext: this.pdfCanvas.getContext('2d'), canvasContext: this.pdfCanvas.getContext('2d'),
viewport: page.getViewport({ scale: 1 }), viewport: page.getViewport({scale: 1}),
}; };
await page.render(renderContext).promise; await page.render(renderContext).promise;
@@ -492,6 +352,8 @@ const DraggableUtils = {
this.loadPageContents(); this.loadPageContents();
} }
}, },
parseTransform(element) {},
async getOverlayedPdfDocument() { async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData(); const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
@@ -505,6 +367,7 @@ const DraggableUtils = {
if (pageIdx.includes('offset')) { if (pageIdx.includes('offset')) {
continue; continue;
} }
console.log(typeof pageIdx);
const page = pdfDocModified.getPage(parseInt(pageIdx)); const page = pdfDocModified.getPage(parseInt(pageIdx));
let draggablesData = pagesMap[pageIdx]; let draggablesData = pagesMap[pageIdx];
@@ -513,61 +376,45 @@ const DraggableUtils = {
const offsetHeight = pagesMap[pageIdx + '-offsetHeight']; const offsetHeight = pagesMap[pageIdx + '-offsetHeight'];
for (const draggableData of draggablesData) { for (const draggableData of draggablesData) {
// Embed the draggable canvas // embed the draggable canvas
const draggableElement = draggableData.element.querySelector('.display-canvas'); const draggableElement = draggableData.element;
const response = await fetch(draggableElement.toDataURL()); const response = await fetch(draggableElement.toDataURL());
const draggableImgBytes = await response.arrayBuffer(); const draggableImgBytes = await response.arrayBuffer();
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes); const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
// Extract transformation data // calculate the position in the pdf document
const transform = draggableData.element.style.transform || ''; const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, '');
const translateRegex = /translate\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/; const transformComponents = tansform.split(',');
const translateMatch = transform.match(translateRegex);
const translateX = translateMatch ? parseFloat(translateMatch[1]) : 0;
const translateY = translateMatch ? parseFloat(translateMatch[2]) : 0;
const childTransform = draggableElement.style.transform || '';
const childTranslateMatch = childTransform.match(translateRegex);
const childOffsetX = childTranslateMatch ? parseFloat(childTranslateMatch[1]) : 0;
const childOffsetY = childTranslateMatch ? parseFloat(childTranslateMatch[2]) : 0;
const rotateAngle = parseFloat(draggableData.element.getAttribute('data-angle')) || 0;
const draggablePositionPixels = { const draggablePositionPixels = {
x: translateX + childOffsetX + this.padding + 2, x: parseFloat(transformComponents[0]),
y: translateY + childOffsetY + this.padding + 2, y: parseFloat(transformComponents[1]),
width: parseFloat(draggableElement.style.width), width: draggableData.offsetWidth,
height: parseFloat(draggableElement.style.height), height: draggableData.offsetHeight,
angle: rotateAngle, // Store rotation
}; };
// Auxiliary variables //Auxiliary variables
let widthAdjusted = page.getWidth(); let widthAdjusted = page.getWidth();
let heightAdjusted = page.getHeight(); let heightAdjusted = page.getHeight();
const rotation = page.getRotation(); const rotation = page.getRotation();
// Normalize page rotation angle //Normalizing angle
let normalizedAngle = rotation.angle % 360; let normalizedAngle = rotation.angle % 360;
if (normalizedAngle < 0) { if (normalizedAngle < 0) {
normalizedAngle += 360; normalizedAngle += 360;
} }
// Adjust page dimensions for rotated pages //Changing the page dimension if the angle is 90 or 270
if (normalizedAngle === 90 || normalizedAngle === 270) { if (normalizedAngle === 90 || normalizedAngle === 270) {
[widthAdjusted, heightAdjusted] = [heightAdjusted, widthAdjusted]; let widthTemp = widthAdjusted;
widthAdjusted = heightAdjusted;
heightAdjusted = widthTemp;
} }
const draggablePositionRelative = { const draggablePositionRelative = {
x: draggablePositionPixels.x / offsetWidth, x: draggablePositionPixels.x / offsetWidth,
y: draggablePositionPixels.y / offsetHeight, y: draggablePositionPixels.y / offsetHeight,
width: draggablePositionPixels.width / offsetWidth, width: draggablePositionPixels.width / offsetWidth,
height: draggablePositionPixels.height / offsetHeight, height: draggablePositionPixels.height / offsetHeight,
angle: draggablePositionPixels.angle,
}; };
const draggablePositionPdf = { const draggablePositionPdf = {
x: draggablePositionRelative.x * widthAdjusted, x: draggablePositionRelative.x * widthAdjusted,
y: draggablePositionRelative.y * heightAdjusted, y: draggablePositionRelative.y * heightAdjusted,
@@ -575,13 +422,11 @@ const DraggableUtils = {
height: draggablePositionRelative.height * heightAdjusted, height: draggablePositionRelative.height * heightAdjusted,
}; };
// Calculate position based on normalized page rotation //Defining the image if the page has a 0-degree angle
let x = draggablePositionPdf.x; let x = draggablePositionPdf.x;
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height; let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
let originx = x + draggablePositionPdf.width / 2; //Defining the image position if it is at other angles
let originy = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height / 2;
if (normalizedAngle === 90) { if (normalizedAngle === 90) {
x = draggablePositionPdf.y + draggablePositionPdf.height; x = draggablePositionPdf.y + draggablePositionPdf.height;
y = draggablePositionPdf.x; y = draggablePositionPdf.x;
@@ -592,32 +437,17 @@ const DraggableUtils = {
x = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height; x = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
y = widthAdjusted - draggablePositionPdf.x; y = widthAdjusted - draggablePositionPdf.x;
} }
// let angle = draggablePositionPixels.angle % 360;
// if (angle < 0) angle += 360; // Normalize to positive angle // draw the image
const radians = -draggablePositionPixels.angle; // Convert angle to radians
page.pushOperators(
PDFLib.pushGraphicsState(),
PDFLib.concatTransformationMatrix(1, 0, 0, 1, originx, originy),
PDFLib.concatTransformationMatrix(
Math.cos(radians),
Math.sin(radians),
-Math.sin(radians),
Math.cos(radians),
0,
0
),
PDFLib.concatTransformationMatrix(1, 0, 0, 1, -1 * originx, -1 * originy)
);
page.drawImage(pdfImageObject, { page.drawImage(pdfImageObject, {
x: x, x: x,
y: y, y: y,
width: draggablePositionPdf.width, width: draggablePositionPdf.width,
height: draggablePositionPdf.height, height: draggablePositionPdf.height,
rotate: rotation,
}); });
page.pushOperators(PDFLib.popGraphicsState());
} }
} }
this.loadPageContents(); this.loadPageContents();
return pdfDocModified; return pdfDocModified;
}, },

View File

@@ -9,7 +9,6 @@ if (!isScriptExecuted) {
document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput); document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput);
}); });
} }
let hasDroppedImage = false;
function setupFileInput(chooser) { function setupFileInput(chooser) {
const elementId = chooser.getAttribute('data-bs-element-id'); const elementId = chooser.getAttribute('data-bs-element-id');
@@ -19,11 +18,6 @@ function setupFileInput(chooser) {
let inputContainer = document.getElementById(inputContainerId); let inputContainer = document.getElementById(inputContainerId);
if (inputContainer.id === 'pdf-upload-input-container') {
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropPDF;
} else if (inputContainer.id === 'image-upload-input-container') {
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropImage;
}
let allFiles = []; let allFiles = [];
let overlay; let overlay;
let dragCounter = 0; let dragCounter = 0;
@@ -147,17 +141,12 @@ function setupFileInput(chooser) {
files.forEach((file) => dataTransfer.items.add(file)); files.forEach((file) => dataTransfer.items.add(file));
return dataTransfer; return dataTransfer;
} }
function handleFileInputChange(inputElement) { function handleFileInputChange(inputElement) {
const files = allFiles; const files = allFiles;
showOrHideSelectedFilesContainer(files); showOrHideSelectedFilesContainer(files);
const filesInfo = files.map((f) => ({ const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
name: f.name,
size: f.size,
uniqueId: f.uniqueId,
type: f.type,
url: URL.createObjectURL(f),
}));
const selectedFilesContainer = $(inputContainer).siblings('.selected-files'); const selectedFilesContainer = $(inputContainer).siblings('.selected-files');
selectedFilesContainer.empty(); selectedFilesContainer.empty();
@@ -168,111 +157,30 @@ function setupFileInput(chooser) {
$(fileContainer).addClass(fileContainerClasses); $(fileContainer).addClass(fileContainerClasses);
$(fileContainer).attr('id', info.uniqueId); $(fileContainer).attr('id', info.uniqueId);
let fileIconContainer = document.createElement('div'); let fileIconContainer = createFileIconContainer(info);
const isDragAndDropEnabled =
window.location.pathname.includes('add-image') || window.location.pathname.includes('sign');
if (info.type.startsWith('image/')) {
let imgPreview = document.createElement('img');
imgPreview.src = info.url;
imgPreview.alt = 'Preview';
imgPreview.style.width = '50px';
imgPreview.style.height = '50px';
imgPreview.style.objectFit = 'cover';
$(fileIconContainer).append(imgPreview);
if (isDragAndDropEnabled) {
let dragIcon = document.createElement('div');
dragIcon.classList.add('drag-icon');
dragIcon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M360-160q-33 0-56.5-23.5T280-240q0-33 23.5-56.5T360-320q33 0 56.5 23.5T440-240q0 33-23.5 56.5T360-160Zm240 0q-33 0-56.5-23.5T520-240q0-33 23.5-56.5T600-320q33 0 56.5 23.5T680-240q0 33-23.5 56.5T600-160ZM360-400q-33 0-56.5-23.5T280-480q0-33 23.5-56.5T360-560q33 0 56.5 23.5T440-480q0 33-23.5 56.5T360-400Zm240 0q-33 0-56.5-23.5T520-480q0-33 23.5-56.5T600-560q33 0 56.5 23.5T680-480q0 33-23.5 56.5T600-400ZM360-640q-33 0-56.5-23.5T280-720q0-33 23.5-56.5T360-800q33 0 56.5 23.5T440-720q0 33-23.5 56.5T360-640Zm240 0q-33 0-56.5-23.5T520-720q0-33 23.5-56.5T600-800q33 0 56.5 23.5T680-720q0 33-23.5 56.5T600-640Z"/></svg>';
fileContainer.appendChild(dragIcon);
$(fileContainer).attr('draggable', 'true');
$(fileContainer).on('dragstart', (e) => {
e.originalEvent.dataTransfer.setData('fileUrl', info.url);
e.originalEvent.dataTransfer.setData('uniqueId', info.uniqueId);
e.originalEvent.dataTransfer.setDragImage(imgPreview, imgPreview.width / 2, imgPreview.height / 2);
});
enableImagePreviewOnClick(fileIconContainer);
} else {
$(fileContainer).removeAttr('draggable');
}
} else {
fileIconContainer = createFileIconContainer(info);
}
let fileInfoContainer = createFileInfoContainer(info); let fileInfoContainer = createFileInfoContainer(info);
if (!isDragAndDropEnabled) { let removeBtn = document.createElement('div');
let removeBtn = document.createElement('div'); removeBtn.classList.add('remove-selected-file');
removeBtn.classList.add('remove-selected-file');
let removeBtnIconHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#C02223"><path d="m339-288 141-141 141 141 51-51-141-141 141-141-51-51-141 141-141-141-51 51 141 141-141 141 51 51ZM480-96q-79 0-149-30t-122.5-82.5Q156-261 126-331T96-480q0-80 30-149.5t82.5-122Q261-804 331-834t149-30q80 0 149.5 30t122 82.5Q804-699 834-629.5T864-480q0 79-30 149t-82.5 122.5Q699-156 629.5-126T480-96Z"/></svg>`; let removeBtnIconHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#C02223"><path d="m339-288 141-141 141 141 51-51-141-141 141-141-51-51-141 141-141-141-51 51 141 141-141 141 51 51ZM480-96q-79 0-149-30t-122.5-82.5Q156-261 126-331T96-480q0-80 30-149.5t82.5-122Q261-804 331-834t149-30q80 0 149.5 30t122 82.5Q804-699 834-629.5T864-480q0 79-30 149t-82.5 122.5Q699-156 629.5-126T480-96Z"/></svg>`;
$(removeBtn).append(removeBtnIconHTML); $(removeBtn).append(removeBtnIconHTML);
$(removeBtn).attr('data-file-id', info.uniqueId).click(removeFileListener); $(removeBtn).attr('data-file-id', info.uniqueId).click(removeFileListener);
$(fileContainer).append(removeBtn);
} $(fileContainer).append(fileIconContainer);
$(fileContainer).append(fileIconContainer, fileInfoContainer); $(fileContainer).append(fileInfoContainer);
$(fileContainer).append(removeBtn);
selectedFilesContainer.append(fileContainer); selectedFilesContainer.append(fileContainer);
}); });
const pageContainers = $('#box-drag-container');
pageContainers.off('dragover').on('dragover', (e) => {
e.preventDefault();
});
pageContainers.off('drop').on('drop', (e) => { showOrHideSelectedFilesContainer(filesInfo);
e.preventDefault();
const fileUrl = e.originalEvent.dataTransfer.getData('fileUrl');
if (fileUrl) {
const existingImages = $(e.target).find(`img[src="${fileUrl}"]`);
if (existingImages.length === 0) {
DraggableUtils.createDraggableCanvasFromUrl(fileUrl);
}
}
const overlayElement = chooser.querySelector('.drag-drop-overlay');
if (overlayElement) {
overlayElement.style.display = 'none';
}
hasDroppedImage = true;
});
showOrHideSelectedFilesContainer(files);
} }
function showOrHideSelectedFilesContainer(files) { function showOrHideSelectedFilesContainer(files) {
if (files && files.length > 0) { if (files && files.length > 0) chooser.style.setProperty('--selected-files-display', 'flex');
chooser.style.setProperty('--selected-files-display', 'flex'); else chooser.style.setProperty('--selected-files-display', 'none');
} else {
chooser.style.setProperty('--selected-files-display', 'none');
}
const isDragAndDropEnabled =
(window.location.pathname.includes('add-image') || window.location.pathname.includes('sign')) &&
files.some((file) => file.type.startsWith('image/'));
if (!isDragAndDropEnabled) return;
const selectedFilesContainer = chooser.querySelector('.selected-files');
let overlayElement = chooser.querySelector('.drag-drop-overlay');
if (!overlayElement) {
selectedFilesContainer.style.position = 'relative';
overlayElement = document.createElement('div');
overlayElement.classList.add('draggable-image-overlay');
overlayElement.innerHTML = 'Drag images to add them to the page';
selectedFilesContainer.appendChild(overlayElement);
}
if (hasDroppedImage) overlayElement.style.display = files && files.length > 0 ? 'flex' : 'none';
selectedFilesContainer.addEventListener('mouseenter', () => {
overlayElement.style.display = 'none';
});
selectedFilesContainer.addEventListener('mouseleave', () => {
if (!hasDroppedImage) overlayElement.style.display = files && files.length > 0 ? 'flex' : 'none';
});
} }
function removeFileListener(e) { function removeFileListener(e) {
@@ -327,52 +235,4 @@ function setupFileInput(chooser) {
removeFileById(fileId, inputElement); removeFileById(fileId, inputElement);
showOrHideSelectedFilesContainer(allFiles); showOrHideSelectedFilesContainer(allFiles);
}); });
function enableImagePreviewOnClick(container) {
const imagePreviewModal = document.getElementById('imagePreviewModal') || createImagePreviewModal();
container.querySelectorAll('img').forEach((img) => {
if (!img.hasPreviewListener) {
img.addEventListener('mouseup', function () {
const imgElement = imagePreviewModal.querySelector('img');
imgElement.src = this.src;
imagePreviewModal.style.display = 'flex';
});
img.hasPreviewListener = true;
}
});
function createImagePreviewModal() {
const modal = document.createElement('div');
modal.id = 'imagePreviewModal';
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100vw';
modal.style.height = '100vh';
modal.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
modal.style.display = 'none';
modal.style.justifyContent = 'center';
modal.style.alignItems = 'center';
modal.style.zIndex = '2000';
const imgElement = document.createElement('img');
imgElement.style.maxWidth = '90%';
imgElement.style.maxHeight = '90%';
modal.appendChild(imgElement);
document.body.appendChild(modal);
modal.addEventListener('click', () => {
modal.style.display = 'none';
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.style.display === 'flex') {
modal.style.display = 'none';
}
});
return modal;
}
}
} }

View File

@@ -168,21 +168,6 @@ class PdfContainer {
input.click(); input.click();
}); });
} }
async handleDroppedFiles(files, nextSiblingElement = null) {
if (files.length > 0) {
const pages = await this.addFilesFromFiles(files, nextSiblingElement, []);
this.updateFilename(files[0]?.name || 'untitled');
const selectAll = document.getElementById('select-pages-container');
if (selectAll) {
selectAll.classList.remove('hidden');
}
return pages;
}
}
async addFilesFromFiles(files, nextSiblingElement, pages) { async addFilesFromFiles(files, nextSiblingElement, pages) {
this.fileName = files[0].name; this.fileName = files[0].name;
for (var i = 0; i < files.length; i++) { for (var i = 0; i < files.length; i++) {

View File

@@ -8,7 +8,7 @@ class FileDragManager {
this.setCallback(cb); this.setCallback(cb);
// Prevent default behavior for drag events // Prevent default behavior for drag events
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => { ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
document.body.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false);
}); });
@@ -21,13 +21,13 @@ class FileDragManager {
this.dragleaveListener = this.dragleaveListener.bind(this); this.dragleaveListener = this.dragleaveListener.bind(this);
this.dropListener = this.dropListener.bind(this); this.dropListener = this.dropListener.bind(this);
document.body.addEventListener('dragenter', this.dragenterListener); document.body.addEventListener("dragenter", this.dragenterListener);
document.body.addEventListener('dragleave', this.dragleaveListener); document.body.addEventListener("dragleave", this.dragleaveListener);
// Add drop event listener // Add drop event listener
document.body.addEventListener('drop', this.dropListener); document.body.addEventListener("drop", this.dropListener);
} }
setActions({updateFilename}) { setActions({ updateFilename }) {
this.updateFilename = updateFilename; this.updateFilename = updateFilename;
} }
@@ -35,7 +35,7 @@ class FileDragManager {
if (cb) { if (cb) {
this.callback = cb; this.callback = cb;
} else { } else {
this.callback = (files) => console.warn('FileDragManager not set'); this.callback = (files) => console.warn("FileDragManager not set");
} }
} }
@@ -43,21 +43,21 @@ class FileDragManager {
this.dragCounter++; this.dragCounter++;
if (!this.overlay) { if (!this.overlay) {
// Create and show the overlay // Create and show the overlay
this.overlay = document.createElement('div'); this.overlay = document.createElement("div");
this.overlay.style.position = 'fixed'; this.overlay.style.position = "fixed";
this.overlay.style.top = 0; this.overlay.style.top = 0;
this.overlay.style.left = 0; this.overlay.style.left = 0;
this.overlay.style.width = '100%'; this.overlay.style.width = "100%";
this.overlay.style.height = '100%'; this.overlay.style.height = "100%";
this.overlay.style.background = 'rgba(0, 0, 0, 0.5)'; this.overlay.style.background = "rgba(0, 0, 0, 0.5)";
this.overlay.style.color = '#fff'; this.overlay.style.color = "#fff";
this.overlay.style.zIndex = '1000'; this.overlay.style.zIndex = "1000";
this.overlay.style.display = 'flex'; this.overlay.style.display = "flex";
this.overlay.style.alignItems = 'center'; this.overlay.style.alignItems = "center";
this.overlay.style.justifyContent = 'center'; this.overlay.style.justifyContent = "center";
this.overlay.style.pointerEvents = 'none'; this.overlay.style.pointerEvents = "none";
this.overlay.innerHTML = '<p>Drop files anywhere to upload</p>'; this.overlay.innerHTML = "<p>Drop files anywhere to upload</p>";
document.getElementById('content-wrap').appendChild(this.overlay); document.getElementById("content-wrap").appendChild(this.overlay);
} }
} }
@@ -87,16 +87,16 @@ class FileDragManager {
this.overlay = null; this.overlay = null;
} }
this.updateFilename(files ? files[0].name : ''); this.updateFilename(files ? files[0].name : "");
}); });
} }
async addImageFile(file, nextSiblingElement) { async addImageFile(file, nextSiblingElement) {
const div = document.createElement('div'); const div = document.createElement("div");
div.classList.add('page-container'); div.classList.add("page-container");
var img = document.createElement('img'); var img = document.createElement("img");
img.classList.add('page-image'); img.classList.add("page-image");
img.src = URL.createObjectURL(file); img.src = URL.createObjectURL(file);
div.appendChild(img); div.appendChild(img);

View File

@@ -1,38 +1,23 @@
window.goToFirstOrLastPage = goToFirstOrLastPage;
document.getElementById('download-pdf').addEventListener('click', async () => { document.getElementById('download-pdf').addEventListener('click', async () => {
const downloadButton = document.getElementById('download-pdf'); const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const originalContent = downloadButton.innerHTML; const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
downloadButton.disabled = true; const link = document.createElement('a');
downloadButton.innerHTML = ` link.href = URL.createObjectURL(blob);
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> link.download = originalFileName + '_addedImage.pdf';
`; link.click();
try {
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = originalFileName + '_addedImage.pdf';
link.click();
} finally {
downloadButton.disabled = false;
downloadButton.innerHTML = originalContent;
}
}); });
let originalFileName = ''; let originalFileName = '';
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => { document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
const fileInput = event.target; const fileInput = event.target;
fileInput.addEventListener('file-input-change', async (e) => { fileInput.addEventListener('file-input-change', async (e) => {
const { allFiles } = e.detail; const {allFiles} = e.detail;
if (allFiles && allFiles.length > 0) { if (allFiles && allFiles.length > 0) {
const file = allFiles[0]; const file = allFiles[0];
originalFileName = file.name.replace(/\.[^/.]+$/, ''); originalFileName = file.name.replace(/\.[^/.]+$/, '');
const pdfData = await file.arrayBuffer(); const pdfData = await file.arrayBuffer();
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
await DraggableUtils.renderPage(pdfDoc, 0); await DraggableUtils.renderPage(pdfDoc, 0);
document.querySelectorAll('.show-on-file-selected').forEach((el) => { document.querySelectorAll('.show-on-file-selected').forEach((el) => {
@@ -45,11 +30,6 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.show-on-file-selected').forEach((el) => { document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = 'display:none !important'; el.style.cssText = 'display:none !important';
}); });
document.addEventListener('keydown', (e) => {
if (e.key === 'Delete') {
DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted());
}
});
}); });
const imageUpload = document.querySelector('input[name=image-upload]'); const imageUpload = document.querySelector('input[name=image-upload]');
@@ -65,12 +45,3 @@ imageUpload.addEventListener('change', (e) => {
}; };
} }
}); });
async function goToFirstOrLastPage(page) {
if (page) {
const lastPage = DraggableUtils.pdfDoc.numPages;
await DraggableUtils.goToPage(lastPage - 1);
} else {
await DraggableUtils.goToPage(0);
}
}

View File

@@ -73,16 +73,6 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.show-on-file-selected').forEach((el) => { document.querySelectorAll('.show-on-file-selected').forEach((el) => {
el.style.cssText = 'display:none !important'; el.style.cssText = 'display:none !important';
}); });
document.querySelectorAll('.small-file-container-saved img ').forEach((img) => {
img.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('fileUrl', img.src);
});
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Delete') {
DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted());
}
});
}); });
const imageUpload = document.querySelector('input[name=image-upload]'); const imageUpload = document.querySelector('input[name=image-upload]');
@@ -213,26 +203,11 @@ async function goToFirstOrLastPage(page) {
} }
document.getElementById('download-pdf').addEventListener('click', async () => { document.getElementById('download-pdf').addEventListener('click', async () => {
const downloadButton = document.getElementById('download-pdf'); const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const originalContent = downloadButton.innerHTML; const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
downloadButton.disabled = true; const link = document.createElement('a');
downloadButton.innerHTML = ` link.href = URL.createObjectURL(blob);
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> link.download = originalFileName + '_signed.pdf';
`; link.click();
try {
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
const modifiedPdfBytes = await modifiedPdf.save();
const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = originalFileName + '_signed.pdf';
link.click();
} catch (error) {
console.error('Error downloading PDF:', error);
} finally {
downloadButton.disabled = false;
downloadButton.innerHTML = originalContent;
}
}); });

View File

@@ -1,85 +0,0 @@
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
const signaturePad = new SignaturePad(signaturePadCanvas, {
minWidth: 1,
maxWidth: 2,
penColor: 'black',
});
function addDraggableFromPad() {
if (signaturePad.isEmpty()) return;
const startTime = Date.now();
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
console.log(Date.now() - startTime);
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
}
function getCroppedCanvasDataUrl(canvas) {
let originalCtx = canvas.getContext('2d');
let originalWidth = canvas.width;
let originalHeight = canvas.height;
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
let minX = originalWidth + 1,
maxX = -1,
minY = originalHeight + 1,
maxY = -1,
x = 0,
y = 0,
currentPixelColorValueIndex;
for (y = 0; y < originalHeight; y++) {
for (x = 0; x < originalWidth; x++) {
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
if (currentPixelAlphaValue > 0) {
if (minX > x) minX = x;
if (maxX < x) maxX = x;
if (minY > y) minY = y;
if (maxY < y) maxY = y;
}
}
}
let croppedWidth = maxX - minX;
let croppedHeight = maxY - minY;
if (croppedWidth < 0 || croppedHeight < 0) return null;
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
let croppedCanvas = document.createElement('canvas'),
croppedCtx = croppedCanvas.getContext('2d');
croppedCanvas.width = croppedWidth;
croppedCanvas.height = croppedHeight;
croppedCtx.putImageData(cuttedImageData, 0, 0);
return croppedCanvas.toDataURL();
}
function isMobile() {
const userAgentCheck = /Mobi|Android|iPhone|iPad|iPod|Windows Phone|Opera Mini/i.test(navigator.userAgent);
const viewportCheck = window.matchMedia('(max-width: 768px)').matches;
return userAgentCheck || viewportCheck;
}
function getDeviceScalingFactor() {
return isMobile() ? 3 : 10;
}
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
const additionalFactor = getDeviceScalingFactor();
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor);
signaturePad.clear();
}
new IntersectionObserver((entries, observer) => {
if (entries.some((entry) => entry.intersectionRatio > 0)) {
resizeCanvas();
}
}).observe(signaturePadCanvas);
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);

View File

@@ -39,12 +39,6 @@
<option value="single" th:text="#{pdfToImage.single}"></option> <option value="single" th:text="#{pdfToImage.single}"></option>
</select> </select>
</div> </div>
<div class="mb-3" id="singleOptionSection" >
<label for="pageNumbers" th:text="#{pageSelectionPrompt}"></label>
<input type="text" name="pageNumbers" class="form-control" id="pageNumbers" value="all"
th:placeholder="#{pdfToImage.placeholder}" required>
</div>
<div class="mb-3"> <div class="mb-3">
<label th:text="#{pdfToImage.colorType}"></label> <label th:text="#{pdfToImage.colorType}"></label>
<select class="form-control" name="colorType"> <select class="form-control" name="colorType">
@@ -65,6 +59,5 @@
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

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