Compare commits
68
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a53cce80b6 | ||
|
|
b8d582a1e3 | ||
|
|
0549c5b191 | ||
|
|
0742364a03 | ||
|
|
9af09c6ac2 | ||
|
|
117d906be3 | ||
|
|
33549c2d67 | ||
|
|
a35956d927 | ||
|
|
3fd52ed564 | ||
|
|
2f977ff250 | ||
|
|
922bbc9076 | ||
|
|
584e2ecee7 | ||
|
|
80e425926e | ||
|
|
58d53c7a6b | ||
|
|
89cda87490 | ||
|
|
af8e065e87 | ||
|
|
21210850ec | ||
|
|
50d745225c | ||
|
|
f446fcd50b | ||
|
|
618485576d | ||
|
|
e75213a206 | ||
|
|
248a14c571 | ||
|
|
62fb5025a9 | ||
|
|
3444aa6525 | ||
|
|
5786421616 | ||
|
|
0090d9f483 | ||
|
|
29916d85b1 | ||
|
|
ffe5b9577b | ||
|
|
2f90220b7b | ||
|
|
bfc679edc5 | ||
|
|
27dd11ba8a | ||
|
|
a51960b199 | ||
|
|
16e56e3208 | ||
|
|
25e9db2570 | ||
|
|
cbc5616a39 | ||
|
|
215bb86a8e | ||
|
|
3ebf75ae6f | ||
|
|
9c410865f9 | ||
|
|
868969192b | ||
|
|
d981968e0f | ||
|
|
ae508730c3 | ||
|
|
bf6a74210f | ||
|
|
e7995a0256 | ||
|
|
7fc850b138 | ||
|
|
ac3da9b7c2 | ||
|
|
5298cb1607 | ||
|
|
41efd80dd5 | ||
|
|
342345cbb9 | ||
|
|
6bf41a4cbe | ||
|
|
1e0949ff2b | ||
|
|
b1584151ba | ||
|
|
0c76bf2c8b | ||
|
|
b5d84db3c8 | ||
|
|
7dd79aa2a1 | ||
|
|
61eac67dc1 | ||
|
|
16f150a203 | ||
|
|
a3c4f1a305 | ||
|
|
0650a6a877 | ||
|
|
09f05ac8d0 | ||
|
|
5f862f55d9 | ||
|
|
15761ae743 | ||
|
|
d216811317 | ||
|
|
41c82b15da | ||
|
|
5f7a4e1664 | ||
|
|
b5e7995554 | ||
|
|
f789533c83 | ||
|
|
d669964975 | ||
|
|
b567f4b110 |
@@ -5,7 +5,8 @@
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(cat:*)"
|
||||
"Bash(cat:*)",
|
||||
"Bash(find:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
# All PRs to V1 must be approved by Frooodle
|
||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh @EthanHealy01
|
||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
build: &build
|
||||
- build.gradle
|
||||
- app/core/build.gradle
|
||||
- app/common/build.gradle
|
||||
- app/proprietary/build.gradle
|
||||
|
||||
app: &app
|
||||
- app/core/src/main/java/**
|
||||
- app/common/src/main/java/**
|
||||
- app/proprietary/src/main/java/**
|
||||
|
||||
openapi: &openapi
|
||||
- build.gradle
|
||||
- app/core/build.gradle
|
||||
- app/core/src/main/java/**
|
||||
- app/common/build.gradle
|
||||
- app/common/src/main/java/**
|
||||
- app/proprietary/build.gradle
|
||||
- app/proprietary/src/main/java/**
|
||||
|
||||
project: &project
|
||||
- app/**
|
||||
- exampleYmlFiles/**
|
||||
- gradle/**
|
||||
- libs/**
|
||||
- scripts/**
|
||||
- testing/**
|
||||
- build.gradle
|
||||
- Dockerfile
|
||||
- Dockerfile.fat
|
||||
- Dockerfile.ultra-lite
|
||||
- gradle.properties
|
||||
- gradlew
|
||||
- gradlew.bat
|
||||
- launch4jConfig.xml
|
||||
- settings.gradle
|
||||
@@ -7,7 +7,6 @@
|
||||
"sbplat",
|
||||
"reecebrowne",
|
||||
"DarioGii",
|
||||
"ConnorYoh",
|
||||
"EthanHealy01"
|
||||
"ConnorYoh"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
name: Auto PR V2 Deployment
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
check-pr:
|
||||
if: github.event.action != 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_deploy: ${{ steps.check-conditions.outputs.should_deploy }}
|
||||
pr_number: ${{ github.event.number }}
|
||||
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
|
||||
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check deployment conditions
|
||||
id: check-conditions
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
echo "PR Title: $PR_TITLE"
|
||||
echo "PR Author: $PR_AUTHOR"
|
||||
echo "PR Branch: $PR_BRANCH"
|
||||
echo "PR Base Branch: ${{ github.event.pull_request.base.ref }}"
|
||||
|
||||
# Define authorized users
|
||||
authorized_users=(
|
||||
"Frooodle"
|
||||
"sf298"
|
||||
"Ludy87"
|
||||
"LaserKaspar"
|
||||
"sbplat"
|
||||
"reecebrowne"
|
||||
"DarioGii"
|
||||
"ConnorYoh"
|
||||
"EthanHealy01"
|
||||
)
|
||||
|
||||
# Check if author is in the authorized list
|
||||
is_authorized=false
|
||||
for user in "${authorized_users[@]}"; do
|
||||
if [[ "$PR_AUTHOR" == "$user" ]]; then
|
||||
is_authorized=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If PR is targeting V2 and user is authorized, deploy unconditionally
|
||||
PR_BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
|
||||
if [[ "$PR_BASE_BRANCH" == "V2" && "$is_authorized" == "true" ]]; then
|
||||
echo "✅ Deployment forced: PR targets V2 and author is authorized."
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Otherwise, continue with original keyword checks
|
||||
has_v2_keyword=false
|
||||
if [[ "$PR_TITLE" =~ [Vv]2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?[Tt]wo ]]; then
|
||||
has_v2_keyword=true
|
||||
fi
|
||||
|
||||
has_branch_keyword=false
|
||||
if [[ "$PR_BRANCH" =~ [Vv]2 ]] || [[ "$PR_BRANCH" =~ [Rr]eact ]]; then
|
||||
has_branch_keyword=true
|
||||
fi
|
||||
|
||||
if [[ "$is_authorized" == "true" && ( "$has_v2_keyword" == "true" || "$has_branch_keyword" == "true" ) ]]; then
|
||||
echo "✅ Deployment conditions met"
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ Deployment conditions not met"
|
||||
echo " - Authorized user: $is_authorized"
|
||||
echo " - Has V2 keyword in title: $has_v2_keyword"
|
||||
echo " - Has V2/React keyword in branch: $has_branch_keyword"
|
||||
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Get PR repository and ref
|
||||
id: get-pr-info
|
||||
if: steps.check-conditions.outputs.should_deploy == 'true'
|
||||
run: |
|
||||
# For forks, use the full repository name, for internal PRs use the current repo
|
||||
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
|
||||
repository="${{ github.event.pull_request.head.repo.full_name }}"
|
||||
else
|
||||
repository="${{ github.repository }}"
|
||||
fi
|
||||
|
||||
echo "repository=$repository" >> $GITHUB_OUTPUT
|
||||
echo "ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT
|
||||
|
||||
deploy-v2-pr:
|
||||
needs: check-pr
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.check-pr.outputs.should_deploy == 'true'
|
||||
# Concurrency control - only one deployment per PR at a time
|
||||
concurrency:
|
||||
group: v2-deploy-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout main repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: main
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
id: setup-bot
|
||||
uses: ./.github/actions/setup-bot
|
||||
continue-on-error: true
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Add deployment started comment
|
||||
id: deployment-started
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||
|
||||
// Delete previous V2 deployment comments to avoid clutter
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
const v2Comments = comments.filter(comment =>
|
||||
comment.body.includes('🚀 **Auto-deploying V2 version**') ||
|
||||
comment.body.includes('## 🚀 V2 Auto-Deployment Complete!') ||
|
||||
comment.body.includes('❌ **V2 Auto-deployment failed**')
|
||||
);
|
||||
|
||||
for (const comment of v2Comments) {
|
||||
console.log(`Deleting old V2 comment: ${comment.id}`);
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id
|
||||
});
|
||||
}
|
||||
|
||||
// Create new deployment started comment
|
||||
const { data: newComment } = await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment triggered by V2/version2 keywords in the PR title or V2/React keywords in the branch name._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.`
|
||||
});
|
||||
|
||||
return newComment.id;
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: ${{ needs.check-pr.outputs.pr_repository }}
|
||||
ref: ${{ needs.check-pr.outputs.pr_ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # Fetch full history for commit hash detection
|
||||
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: |
|
||||
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Get commit hashes for frontend and backend
|
||||
id: commit-hashes
|
||||
run: |
|
||||
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
|
||||
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
|
||||
if [ -z "$FRONTEND_HASH" ]; then
|
||||
FRONTEND_HASH="no-frontend-changes"
|
||||
fi
|
||||
|
||||
# Get last commit that touched backend code, docker/backend, or docker/compose
|
||||
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
|
||||
if [ -z "$BACKEND_HASH" ]; then
|
||||
BACKEND_HASH="no-backend-changes"
|
||||
fi
|
||||
|
||||
echo "Frontend hash: $FRONTEND_HASH"
|
||||
echo "Backend hash: $BACKEND_HASH"
|
||||
|
||||
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
|
||||
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
|
||||
|
||||
# Short hashes for tags
|
||||
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
|
||||
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
|
||||
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check if frontend image exists
|
||||
id: check-frontend
|
||||
run: |
|
||||
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "Frontend image already exists, skipping build"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "Frontend image needs to be built"
|
||||
fi
|
||||
|
||||
- name: Check if backend image exists
|
||||
id: check-backend
|
||||
run: |
|
||||
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "Backend image already exists, skipping build"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "Backend image needs to be built"
|
||||
fi
|
||||
|
||||
- name: Build and push V2 frontend image
|
||||
if: steps.check-frontend.outputs.exists == 'false'
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/frontend/Dockerfile
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||
build-args: VERSION_TAG=v2-alpha
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Build and push V2 backend image
|
||||
if: steps.check-backend.outputs.exists == 'false'
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/backend/Dockerfile
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||
build-args: VERSION_TAG=v2-alpha
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||
sudo chmod 600 ../private.key
|
||||
|
||||
- name: Deploy V2 to VPS
|
||||
id: deploy
|
||||
run: |
|
||||
# Use same port strategy as regular PRs - just the PR number
|
||||
V2_PORT=${{ needs.check-pr.outputs.pr_number }}
|
||||
BACKEND_PORT=$((V2_PORT + 10000)) # Backend on higher port to avoid conflicts
|
||||
|
||||
# Create docker-compose for V2 with separate frontend and backend
|
||||
cat > docker-compose.yml << EOF
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf-v2-backend:
|
||||
container_name: stirling-pdf-v2-backend-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||
ports:
|
||||
- "${BACKEND_PORT}:8080" # Backend API port
|
||||
volumes:
|
||||
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/data:/usr/share/tessdata:rw
|
||||
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/config:/configs:rw
|
||||
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
SYSTEM_DEFAULTLOCALE: en-GB
|
||||
UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||
UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Frontend/Backend Split Architecture"
|
||||
UI_APPNAMENAVBAR: "V2 PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||
restart: on-failure:5
|
||||
|
||||
stirling-pdf-v2-frontend:
|
||||
container_name: stirling-pdf-v2-frontend-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||
ports:
|
||||
- "${V2_PORT}:80" # Frontend port (same as regular PRs)
|
||||
environment:
|
||||
VITE_API_BASE_URL: "http://${{ secrets.VPS_HOST }}:${BACKEND_PORT}"
|
||||
depends_on:
|
||||
- stirling-pdf-v2-backend
|
||||
restart: on-failure:5
|
||||
EOF
|
||||
|
||||
# Deploy to VPS
|
||||
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose-v2.yml
|
||||
|
||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
|
||||
# Create V2 PR-specific directories
|
||||
mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs}
|
||||
|
||||
# Move docker-compose file to correct location
|
||||
mv /tmp/docker-compose-v2.yml /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/docker-compose.yml
|
||||
|
||||
# Stop any existing container and clean up
|
||||
cd /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}
|
||||
docker-compose down --remove-orphans 2>/dev/null || true
|
||||
|
||||
# Start the new container
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
|
||||
# Clean up unused Docker resources to save space
|
||||
docker system prune -af --volumes
|
||||
|
||||
# Clean up old backend/frontend images (older than 2 weeks)
|
||||
docker image prune -af --filter "until=336h" --filter "label!=keep=true"
|
||||
ENDSSH
|
||||
|
||||
# Set port for output
|
||||
echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Post V2 deployment URL to PR
|
||||
if: success()
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||
const v2Port = ${{ steps.deploy.outputs.v2_port }};
|
||||
|
||||
// Delete the "deploying..." comment since we're posting the final result
|
||||
const deploymentStartedId = ${{ steps.deployment-started.outputs.result }};
|
||||
if (deploymentStartedId) {
|
||||
console.log(`Deleting deployment started comment: ${deploymentStartedId}`);
|
||||
try {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: deploymentStartedId
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Could not delete deployment started comment: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${v2Port}`;
|
||||
|
||||
const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` +
|
||||
`Your V2 PR with the new frontend/backend split architecture has been deployed!\n\n` +
|
||||
`🔗 **V2 Test URL:** [${deploymentUrl}](${deploymentUrl})\n\n` +
|
||||
`_This deployment will be automatically cleaned up when the PR is closed._\n\n` +
|
||||
`🔄 **Auto-deployed** because PR title or branch name contains V2/version2/React keywords.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
cleanup-v2-deployment:
|
||||
if: github.event.action == 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
id: setup-bot
|
||||
uses: ./.github/actions/setup-bot
|
||||
continue-on-error: true
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Clean up V2 deployment comments
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = ${{ github.event.pull_request.number }};
|
||||
|
||||
// Find and delete V2 deployment comments
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const v2Comments = comments.filter(c =>
|
||||
c.body?.includes("## 🚀 V2 Auto-Deployment Complete!") &&
|
||||
c.user?.type === "Bot"
|
||||
);
|
||||
|
||||
for (const comment of v2Comments) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id
|
||||
});
|
||||
console.log(`Deleted V2 deployment comment (ID: ${comment.id})`);
|
||||
}
|
||||
|
||||
- name: Set up SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||
sudo chmod 600 ../private.key
|
||||
|
||||
- name: Cleanup V2 deployment
|
||||
run: |
|
||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||
if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then
|
||||
echo "Found V2 PR directory, proceeding with cleanup..."
|
||||
|
||||
# Stop and remove V2 containers
|
||||
cd /stirling/V2-PR-${{ github.event.pull_request.number }}
|
||||
docker-compose down || true
|
||||
|
||||
# Go back to root before removal
|
||||
cd /
|
||||
|
||||
# Remove V2 PR-specific directories
|
||||
rm -rf /stirling/V2-PR-${{ github.event.pull_request.number }}
|
||||
|
||||
# Clean up V2 containers by name (in case compose cleanup missed them)
|
||||
docker rm -f stirling-pdf-v2-frontend-pr-${{ github.event.pull_request.number }} || true
|
||||
docker rm -f stirling-pdf-v2-backend-pr-${{ github.event.pull_request.number }} || true
|
||||
|
||||
echo "V2 cleanup completed"
|
||||
else
|
||||
echo "V2 PR directory not found, nothing to clean up"
|
||||
fi
|
||||
|
||||
# Clean up old unused images (older than 2 weeks) but keep recent ones for reuse
|
||||
docker image prune -af --filter "until=336h" --filter "label!=keep=true"
|
||||
|
||||
# Note: We don't remove the commit-based images since they can be reused across PRs
|
||||
# Only remove PR-specific containers and directories
|
||||
ENDSSH
|
||||
|
||||
- name: Cleanup temporary files
|
||||
if: always()
|
||||
run: |
|
||||
rm -f ../private.key
|
||||
continue-on-error: true
|
||||
|
||||
@@ -180,7 +180,6 @@ jobs:
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: |
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
name: PR Auto Deploy
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: ["main"]
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-pr:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.action != 'closed' &&
|
||||
(
|
||||
github.event.pull_request.user.login == 'frooodle' ||
|
||||
github.event.pull_request.user.login == 'sf298' ||
|
||||
github.event.pull_request.user.login == 'Ludy87' ||
|
||||
github.event.pull_request.user.login == 'LaserKaspar' ||
|
||||
github.event.pull_request.user.login == 'sbplat' ||
|
||||
github.event.pull_request.user.login == 'reecebrowne' ||
|
||||
github.event.pull_request.user.login == 'DarioGii' ||
|
||||
github.event.pull_request.user.login == 'EthanHealy01' ||
|
||||
github.event.pull_request.user.login == 'ConnorYoh'
|
||||
)
|
||||
outputs:
|
||||
pr_number: ${{ steps.get-pr.outputs.pr_number }}
|
||||
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
|
||||
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
|
||||
disable_security: 'true'
|
||||
enable_pro: 'false'
|
||||
enable_enterprise: 'false'
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Get PR data
|
||||
id: get-pr
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
console.log(`PR Number: ${prNumber}`);
|
||||
core.setOutput('pr_number', prNumber);
|
||||
|
||||
- name: Get PR repository and ref
|
||||
id: get-pr-info
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const pr = context.payload.pull_request;
|
||||
const repository = pr.head.repo.fork ? pr.head.repo.full_name : `${owner}/${repo}`;
|
||||
console.log(`PR Repository: ${repository}`);
|
||||
console.log(`PR Branch: ${pr.head.ref}`);
|
||||
core.setOutput('repository', repository);
|
||||
core.setOutput('ref', pr.head.ref);
|
||||
|
||||
deploy-pr:
|
||||
if: github.event.action != 'closed'
|
||||
needs: check-pr
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
id: setup-bot
|
||||
uses: ./.github/actions/setup-bot
|
||||
continue-on-error: true
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: ${{ needs.check-pr.outputs.pr_repository }}
|
||||
ref: ${{ needs.check-pr.outputs.pr_ref }}
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: |
|
||||
if [ "${{ needs.check-pr.outputs.disable_security }}" == "true" ]; then
|
||||
export DISABLE_ADDITIONAL_FEATURES=true
|
||||
else
|
||||
export DISABLE_ADDITIONAL_FEATURES=false
|
||||
fi
|
||||
./gradlew clean build
|
||||
env:
|
||||
STIRLING_PDF_DESKTOP_UI: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Build and push PR-specific image
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-pr.outputs.pr_number }}
|
||||
build-args: VERSION_TAG=alpha
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||
sudo chmod 600 ../private.key
|
||||
|
||||
- name: Deploy to VPS
|
||||
id: deploy
|
||||
run: |
|
||||
# Set security settings based on flags
|
||||
if [ "${{ needs.check-pr.outputs.disable_security }}" == "false" ]; then
|
||||
DISABLE_ADDITIONAL_FEATURES="false"
|
||||
LOGIN_SECURITY="true"
|
||||
SECURITY_STATUS="🔒 Security Enabled"
|
||||
else
|
||||
DISABLE_ADDITIONAL_FEATURES="true"
|
||||
LOGIN_SECURITY="false"
|
||||
SECURITY_STATUS="Security Disabled"
|
||||
fi
|
||||
|
||||
# Set pro/enterprise settings (enterprise implies pro)
|
||||
if [ "${{ needs.check-pr.outputs.enable_enterprise }}" == "true" ]; then
|
||||
PREMIUM_ENABLED="true"
|
||||
PREMIUM_KEY="${{ secrets.ENTERPRISE_KEY }}"
|
||||
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
|
||||
elif [ "${{ needs.check-pr.outputs.enable_pro }}" == "true" ]; then
|
||||
PREMIUM_ENABLED="true"
|
||||
PREMIUM_KEY="${{ secrets.PREMIUM_KEY }}"
|
||||
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
|
||||
else
|
||||
PREMIUM_ENABLED="false"
|
||||
PREMIUM_KEY=""
|
||||
PREMIUM_PROFEATURES_AUDIT_ENABLED="false"
|
||||
fi
|
||||
|
||||
# First create the docker-compose content locally
|
||||
cat > docker-compose.yml << EOF
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: stirling-pdf-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-pr.outputs.pr_number }}
|
||||
ports:
|
||||
- "${{ needs.check-pr.outputs.pr_number }}:8080"
|
||||
volumes:
|
||||
- /stirling/PR-${{ needs.check-pr.outputs.pr_number }}/data:/usr/share/tessdata:rw
|
||||
- /stirling/PR-${{ needs.check-pr.outputs.pr_number }}/config:/configs:rw
|
||||
- /stirling/PR-${{ needs.check-pr.outputs.pr_number }}/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "${DISABLE_ADDITIONAL_FEATURES}"
|
||||
SECURITY_ENABLELOGIN: "${LOGIN_SECURITY}"
|
||||
SYSTEM_DEFAULTLOCALE: en-GB
|
||||
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||
UI_HOMEDESCRIPTION: "PR#${{ needs.check-pr.outputs.pr_number }} for Stirling-PDF Latest"
|
||||
UI_APPNAMENAVBAR: "PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||
PREMIUM_KEY: "${PREMIUM_KEY}"
|
||||
PREMIUM_ENABLED: "${PREMIUM_ENABLED}"
|
||||
PREMIUM_PROFEATURES_AUDIT_ENABLED: "${PREMIUM_PROFEATURES_AUDIT_ENABLED}"
|
||||
restart: on-failure:5
|
||||
EOF
|
||||
|
||||
# Then copy the file and execute commands
|
||||
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose.yml
|
||||
|
||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
|
||||
# Create PR-specific directories
|
||||
mkdir -p /stirling/PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs}
|
||||
|
||||
# Move docker-compose file to correct location
|
||||
mv /tmp/docker-compose.yml /stirling/PR-${{ needs.check-pr.outputs.pr_number }}/docker-compose.yml
|
||||
|
||||
# Start or restart the container
|
||||
cd /stirling/PR-${{ needs.check-pr.outputs.pr_number }}
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
ENDSSH
|
||||
|
||||
# Set output for use in PR comment
|
||||
echo "security_status=${SECURITY_STATUS}" >> $GITHUB_ENV
|
||||
|
||||
- name: Add 'pr-deployed' label
|
||||
if: success()
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['pr-deployed']
|
||||
});
|
||||
|
||||
- name: Post deployment URL to PR
|
||||
if: success()
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${prNumber}`;
|
||||
const securityStatus = process.env.security_status || "Security Disabled";
|
||||
const body = `## 🚀 PR Test Deployment\n\n` +
|
||||
`Your PR has been deployed for testing!\n\n` +
|
||||
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n` +
|
||||
`${securityStatus}\n\n` +
|
||||
`This deployment will be automatically cleaned up when the PR is closed.\n\n`;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body
|
||||
});
|
||||
|
||||
- name: Cleanup temporary files
|
||||
if: always()
|
||||
run: |
|
||||
echo "Cleaning up temporary files..."
|
||||
rm -f ../private.key docker-compose.yml
|
||||
echo "Cleanup complete."
|
||||
continue-on-error: true
|
||||
|
||||
cleanup:
|
||||
if: github.event.action == 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
id: setup-bot
|
||||
uses: ./.github/actions/setup-bot
|
||||
continue-on-error: true
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Remove 'pr-deployed' label if present
|
||||
id: remove-label-comment
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const prNumber = ${{ github.event.pull_request.number }};
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const hasLabel = labels.some(label => label.name === 'pr-deployed');
|
||||
|
||||
if (hasLabel) {
|
||||
console.log("Label 'pr-deployed' found. Removing...");
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: 'pr-deployed'
|
||||
});
|
||||
} else {
|
||||
console.log("Label 'pr-deployed' not found. Nothing to do.");
|
||||
}
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
const deploymentComments = comments.filter(c =>
|
||||
c.body?.includes("## 🚀 PR Test Deployment") &&
|
||||
c.user?.type === "Bot"
|
||||
);
|
||||
|
||||
if (deploymentComments.length > 0) {
|
||||
for (const comment of deploymentComments) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id
|
||||
});
|
||||
console.log(`Deleted deployment comment (ID: ${comment.id})`);
|
||||
}
|
||||
} else {
|
||||
console.log("No matching deployment comments found.");
|
||||
}
|
||||
|
||||
const hasDeploymentComment = deploymentComments.length > 0;
|
||||
core.setOutput('present', (hasLabel || hasDeploymentComment) ? 'true' : 'false');
|
||||
|
||||
- name: Set up SSH
|
||||
if: steps.remove-label-comment.outputs.present == 'true'
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||
sudo chmod 600 ../private.key
|
||||
|
||||
- name: Cleanup PR deployment
|
||||
if: steps.remove-label-comment.outputs.present == 'true'
|
||||
id: cleanup
|
||||
run: |
|
||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
|
||||
echo "Found PR directory, proceeding with cleanup..."
|
||||
cd /stirling/PR-${{ github.event.pull_request.number }}
|
||||
docker-compose down || true
|
||||
cd /
|
||||
rm -rf /stirling/PR-${{ github.event.pull_request.number }}
|
||||
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ github.event.pull_request.number }} || true
|
||||
echo "PERFORMED_CLEANUP"
|
||||
else
|
||||
echo "PR directory not found, nothing to clean up"
|
||||
echo "NO_CLEANUP_NEEDED"
|
||||
fi
|
||||
ENDSSH
|
||||
|
||||
- name: Cleanup temporary files
|
||||
if: always()
|
||||
run: |
|
||||
echo "Cleaning up temporary files..."
|
||||
rm -f ../private.key
|
||||
echo "Cleanup complete."
|
||||
continue-on-error: true
|
||||
+47
-131
@@ -1,11 +1,10 @@
|
||||
name: Build and Test Workflow
|
||||
name: Build repo
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches: ["main"]
|
||||
push:
|
||||
branches: ["main", "V2", "V2-gha"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
branches: ["main", "V2", "V2-gha"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -38,12 +37,6 @@ jobs:
|
||||
with:
|
||||
java-version: ${{ matrix.jdk-version }}
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
||||
run: ./gradlew clean build
|
||||
@@ -95,7 +88,6 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
|
||||
check-generateOpenApiDocs:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -103,40 +95,26 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Filter for integration changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: files
|
||||
with:
|
||||
filters: ".github/config/.files.yaml"
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
if: ${{ steps.files.outputs.openapi == 'true' }}
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
if: ${{ steps.files.outputs.openapi == 'true' }}
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
|
||||
- name: Generate OpenAPI documentation
|
||||
if: ${{ steps.files.outputs.openapi == 'true' }}
|
||||
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
||||
|
||||
- name: Upload OpenAPI Documentation
|
||||
if: ${{ steps.files.outputs.openapi == 'true' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: openapi-docs
|
||||
path: ./SwaggerDoc.json
|
||||
|
||||
check-licence:
|
||||
needs: build
|
||||
frontend-validation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
@@ -147,22 +125,53 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Filter for integration changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: files
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
filters: ".github/config/.files.yaml"
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: |
|
||||
cd frontend
|
||||
npm ci
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd frontend
|
||||
npm run build
|
||||
|
||||
- name: Run frontend tests (if available)
|
||||
run: |
|
||||
cd frontend
|
||||
npm test --passWithNoTests --watchAll=false || true
|
||||
|
||||
- name: Upload frontend build artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: frontend-build
|
||||
path: frontend/dist/
|
||||
retention-days: 3
|
||||
|
||||
check-licence:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK 17
|
||||
if: ${{ steps.files.outputs.build == 'true' }}
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
distribution: "adopt"
|
||||
|
||||
- name: check the licenses for compatibility
|
||||
if: ${{ steps.files.outputs.build == 'true' }}
|
||||
run: ./gradlew clean checkLicense
|
||||
|
||||
- name: FAILED - check the licenses for compatibility
|
||||
@@ -200,126 +209,33 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Filter for integration changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: files
|
||||
with:
|
||||
filters: ".github/config/.files.yaml"
|
||||
|
||||
- name: Set up Java 17
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Install Docker Compose
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
run: |
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.37.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
- name: Set up Python
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: 'pip' # caching pip dependencies
|
||||
cache-dependency-path: ./testing/cucumber/requirements.txt
|
||||
|
||||
- name: Pip requirements
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
run: |
|
||||
pip install --require-hashes -r ./testing/cucumber/requirements.txt
|
||||
|
||||
- name: Run Docker Compose Tests
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
run: |
|
||||
chmod +x ./testing/test_webpages.sh
|
||||
chmod +x ./testing/test.sh
|
||||
chmod +x ./testing/test_disabledEndpoints.sh
|
||||
./testing/test.sh
|
||||
|
||||
test-build-docker-images:
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: [build, check-generateOpenApiDocs, check-licence]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
docker-rev: ["Dockerfile", "Dockerfile.ultra-lite", "Dockerfile.fat"]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Filter for integration changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: files
|
||||
with:
|
||||
filters: ".github/config/.files.yaml"
|
||||
|
||||
- name: Set up JDK 17
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
- name: Build application
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
run: ./gradlew clean build
|
||||
env:
|
||||
DISABLE_ADDITIONAL_FEATURES: true
|
||||
STIRLING_PDF_DESKTOP_UI: false
|
||||
|
||||
- name: Set up QEMU
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Build ${{ matrix.docker-rev }}
|
||||
if: ${{ steps.files.outputs.project == 'true' }}
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./${{ matrix.docker-rev }}
|
||||
push: false
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
provenance: true
|
||||
sbom: true
|
||||
|
||||
- name: Upload Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: reports-docker-${{ matrix.docker-rev }}
|
||||
path: |
|
||||
build/reports/tests/
|
||||
build/test-results/
|
||||
build/reports/problems/
|
||||
retention-days: 3
|
||||
if-no-files-found: warn
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
name: Frontend License Report Workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- V2
|
||||
paths:
|
||||
- "frontend/package.json"
|
||||
- "frontend/package-lock.json"
|
||||
- "frontend/scripts/generate-licenses.js"
|
||||
pull_request:
|
||||
branches:
|
||||
- V2
|
||||
paths:
|
||||
- "frontend/package.json"
|
||||
- "frontend/package-lock.json"
|
||||
- "frontend/scripts/generate-licenses.js"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
generate-frontend-license-report:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
repository-projects: write # Required for enabling automerge
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
uses: ./.github/actions/setup-bot
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: frontend
|
||||
run: npm ci
|
||||
|
||||
- name: Generate frontend license report
|
||||
working-directory: frontend
|
||||
run: npm run generate-licenses
|
||||
|
||||
- name: Check for license warnings
|
||||
run: |
|
||||
if [ -f "frontend/src/assets/license-warnings.json" ]; then
|
||||
echo "LICENSE_WARNINGS_EXIST=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# PR Event: Check licenses and comment on PR
|
||||
- name: Delete previous license check comments
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.issue.number;
|
||||
|
||||
// Get all comments on the PR
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
// Filter for license check comments
|
||||
const licenseComments = comments.filter(comment =>
|
||||
comment.body.includes('## ✅ Frontend License Check Passed') ||
|
||||
comment.body.includes('## ❌ Frontend License Check Failed')
|
||||
);
|
||||
|
||||
// Delete old license check comments
|
||||
for (const comment of licenseComments) {
|
||||
console.log(`Deleting old license check comment: ${comment.id}`);
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: comment.id
|
||||
});
|
||||
}
|
||||
|
||||
- name: Comment on PR - License Check Results
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.issue.number;
|
||||
const hasWarnings = process.env.LICENSE_WARNINGS_EXIST === 'true';
|
||||
|
||||
let commentBody;
|
||||
|
||||
if (hasWarnings) {
|
||||
// Read warnings file to get specific issues
|
||||
const fs = require('fs');
|
||||
let warningDetails = '';
|
||||
try {
|
||||
const warnings = JSON.parse(fs.readFileSync('frontend/src/assets/license-warnings.json', 'utf8'));
|
||||
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
|
||||
} catch (e) {
|
||||
warningDetails = 'Unable to read warning details';
|
||||
}
|
||||
|
||||
commentBody = `## ❌ Frontend License Check Failed
|
||||
|
||||
The frontend license check has detected compatibility warnings that require review:
|
||||
|
||||
${warningDetails}
|
||||
|
||||
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
|
||||
|
||||
_This check will fail the PR until license issues are resolved._`;
|
||||
} else {
|
||||
commentBody = `## ✅ Frontend License Check Passed
|
||||
|
||||
All frontend licenses have been validated and no compatibility warnings were detected.
|
||||
|
||||
The frontend license report has been updated successfully.`;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
- name: Fail workflow if license warnings exist (PR only)
|
||||
if: github.event_name == 'pull_request' && env.LICENSE_WARNINGS_EXIST == 'true'
|
||||
run: |
|
||||
echo "❌ License warnings detected. Failing the workflow."
|
||||
exit 1
|
||||
|
||||
# Push Event: Commit license files and create PR
|
||||
- name: Commit changes (Push only)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
git add frontend/src/assets/3rdPartyLicenses.json
|
||||
# Note: Do NOT commit license-warnings.json - it's only for PR review
|
||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||
|
||||
- name: Prepare PR body (Push only)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
PR_BODY="Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot]
|
||||
|
||||
This PR updates the frontend license report based on changes to package.json dependencies."
|
||||
|
||||
if [ "${{ env.LICENSE_WARNINGS_EXIST }}" = "true" ]; then
|
||||
PR_BODY="$PR_BODY
|
||||
|
||||
## ⚠️ License Compatibility Warnings
|
||||
|
||||
The following licenses may require review for corporate compatibility:
|
||||
|
||||
$(cat frontend/src/assets/license-warnings.json | jq -r '.warnings[].message')
|
||||
|
||||
Please review these licenses to ensure they are acceptable for your use case."
|
||||
fi
|
||||
|
||||
echo "PR_BODY<<EOF" >> $GITHUB_ENV
|
||||
echo "$PR_BODY" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Pull Request (Push only)
|
||||
id: cpr
|
||||
if: github.event_name == 'push' && env.CHANGES_DETECTED == 'true'
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
commit-message: "Update Frontend 3rd Party Licenses"
|
||||
committer: ${{ steps.setup-bot.outputs.committer }}
|
||||
author: ${{ steps.setup-bot.outputs.committer }}
|
||||
signoff: true
|
||||
branch: update-frontend-3rd-party-licenses
|
||||
base: V2
|
||||
title: "Update Frontend 3rd Party Licenses"
|
||||
body: ${{ env.PR_BODY }}
|
||||
labels: Licenses,github-actions,frontend
|
||||
draft: false
|
||||
delete-branch: true
|
||||
sign-commits: true
|
||||
|
||||
- name: Enable Pull Request Automerge (Push only)
|
||||
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'false'
|
||||
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
||||
|
||||
- name: Add review required label (Push only)
|
||||
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'true'
|
||||
run: gh pr edit "${{ steps.cpr.outputs.pull-request-number }}" --add-label "license-review-required"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
||||
@@ -39,8 +39,7 @@ jobs:
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
|
||||
@@ -32,7 +32,6 @@ jobs:
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: gradle
|
||||
|
||||
# ✅ Get version from Gradle
|
||||
- name: Get version number
|
||||
@@ -72,7 +71,6 @@ jobs:
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
@@ -161,7 +159,6 @@ jobs:
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
|
||||
@@ -37,7 +37,6 @@ jobs:
|
||||
with:
|
||||
python-version: 3.12
|
||||
cache: 'pip' # caching pip dependencies
|
||||
cache-dependency-path: ./.github/scripts/requirements_pre_commit.txt
|
||||
|
||||
- name: Run Pre-Commit Hooks
|
||||
run: |
|
||||
|
||||
@@ -29,7 +29,6 @@ jobs:
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
|
||||
@@ -34,7 +34,6 @@ jobs:
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
|
||||
@@ -25,7 +25,6 @@ jobs:
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
cache: gradle
|
||||
|
||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
|
||||
|
||||
@@ -24,12 +24,6 @@ jobs:
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean build
|
||||
@@ -117,11 +111,6 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
|
||||
- name: Run TestDriver.ai
|
||||
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
|
||||
with:
|
||||
|
||||
@@ -27,6 +27,7 @@ clientWebUI/
|
||||
!cucumber/exampleFiles/
|
||||
!cucumber/exampleFiles/example_html.zip
|
||||
exampleYmlFiles/stirling/
|
||||
stirling/
|
||||
/testing/file_snapshots
|
||||
SwaggerDoc.json
|
||||
|
||||
@@ -196,6 +197,8 @@ id_ed25519.pub
|
||||
.pytest_cache
|
||||
.ipynb_checkpoints
|
||||
|
||||
|
||||
|
||||
**/jcef-bundle/
|
||||
|
||||
# node_modules
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Common Development Commands
|
||||
|
||||
### Build and Test
|
||||
- **Build project**: `./gradlew clean build`
|
||||
- **Run locally**: `./gradlew bootRun`
|
||||
- **Full test suite**: `./test.sh` (builds all Docker variants and runs comprehensive tests)
|
||||
- **Code formatting**: `./gradlew spotlessApply` (runs automatically before compilation)
|
||||
|
||||
### Docker Development
|
||||
- **Build ultra-lite**: `docker build -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .`
|
||||
- **Build standard**: `docker build -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .`
|
||||
- **Build fat version**: `docker build -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .`
|
||||
- **Example compose files**: Located in `exampleYmlFiles/` directory
|
||||
|
||||
### Security Mode Development
|
||||
Set `DOCKER_ENABLE_SECURITY=true` environment variable to enable security features during development. This is required for testing the full version locally.
|
||||
|
||||
### Frontend Development
|
||||
- **Frontend dev server**: `cd frontend && npm run dev` (requires backend on localhost:8080)
|
||||
- **Tech Stack**: Vite + React + TypeScript + Mantine UI + TailwindCSS
|
||||
- **Proxy Configuration**: Vite proxies `/api/*` calls to backend (localhost:8080)
|
||||
- **Build Process**: DO NOT run build scripts manually - builds are handled by CI/CD pipelines
|
||||
- **Package Installation**: DO NOT run npm install commands - package management handled separately
|
||||
- **Deployment Options**:
|
||||
- **Desktop App**: `npm run tauri-build` (native desktop application)
|
||||
- **Web Server**: `npm run build` then serve dist/ folder
|
||||
- **Development**: `npm run tauri-dev` for desktop dev mode
|
||||
|
||||
#### Multi-Tool Workflow Architecture
|
||||
Frontend designed for **stateful document processing**:
|
||||
- Users upload PDFs once, then chain tools (split → merge → compress → view)
|
||||
- File state and processing results persist across tool switches
|
||||
- No file reloading between tools - performance critical for large PDFs (up to 100GB+)
|
||||
|
||||
#### FileContext - Central State Management
|
||||
**Location**: `src/contexts/FileContext.tsx`
|
||||
- **Active files**: Currently loaded PDFs and their variants
|
||||
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
|
||||
- **Memory management**: PDF document cleanup, blob URL lifecycle, Web Worker management
|
||||
- **IndexedDB persistence**: File storage with thumbnail caching
|
||||
- **Preview system**: Tools can preview results (e.g., Split → Viewer → back to Split) without context pollution
|
||||
|
||||
**Critical**: All file operations go through FileContext. Don't bypass with direct file handling.
|
||||
|
||||
#### Processing Services
|
||||
- **enhancedPDFProcessingService**: Background PDF parsing and manipulation
|
||||
- **thumbnailGenerationService**: Web Worker-based with main-thread fallback
|
||||
- **fileStorage**: IndexedDB with LRU cache management
|
||||
|
||||
#### Memory Management Strategy
|
||||
**Why manual cleanup exists**: Large PDFs (up to 100GB+) through multiple tools accumulate:
|
||||
- PDF.js documents that need explicit .destroy() calls
|
||||
- Blob URLs from tool outputs that need revocation
|
||||
- Web Workers that need termination
|
||||
Without cleanup: browser crashes with memory leaks.
|
||||
|
||||
#### Tool Development
|
||||
- **Pattern**: Follow `src/tools/Split.tsx` as reference implementation
|
||||
- **File Access**: Tools receive `selectedFiles` prop (computed from activeFiles based on user selection)
|
||||
- **File Selection**: Users select files in FileEditor (tool mode) → stored as IDs → computed to File objects for tools
|
||||
- **Integration**: All files are part of FileContext ecosystem - automatic memory management and operation tracking
|
||||
- **Parameters**: Tool parameter handling patterns still being standardized
|
||||
- **Preview Integration**: Tools can implement preview functionality (see Split tool's thumbnail preview)
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Project Structure
|
||||
- **Backend**: Spring Boot application with Thymeleaf templating
|
||||
- **Frontend**: React-based SPA in `/frontend` directory (Thymeleaf templates fully replaced)
|
||||
- **File Storage**: IndexedDB for client-side file persistence and thumbnails
|
||||
- **Internationalization**: JSON-based translations (converted from backend .properties)
|
||||
- **PDF Processing**: PDFBox for core PDF operations, LibreOffice for conversions, PDF.js for client-side rendering
|
||||
- **Security**: Spring Security with optional authentication (controlled by `DOCKER_ENABLE_SECURITY`)
|
||||
- **Configuration**: YAML-based configuration with environment variable overrides
|
||||
|
||||
### Controller Architecture
|
||||
- **API Controllers** (`src/main/java/.../controller/api/`): REST endpoints for PDF operations
|
||||
- Organized by function: converters, security, misc, pipeline
|
||||
- Follow pattern: `@RestController` + `@RequestMapping("/api/v1/...")`
|
||||
- **Web Controllers** (`src/main/java/.../controller/web/`): Serve Thymeleaf templates
|
||||
- Pattern: `@Controller` + return template names
|
||||
|
||||
### Key Components
|
||||
- **SPDFApplication.java**: Main application class with desktop UI and browser launching logic
|
||||
- **ConfigInitializer**: Handles runtime configuration and settings files
|
||||
- **Pipeline System**: Automated PDF processing workflows via `PipelineController`
|
||||
- **Security Layer**: Authentication, authorization, and user management (when enabled)
|
||||
|
||||
### Component Architecture
|
||||
- **React Components**: Located in `frontend/src/components/` and `frontend/src/tools/`
|
||||
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/public/` (modern)
|
||||
- **Internationalization**:
|
||||
- Backend: `messages_*.properties` files
|
||||
- Frontend: JSON files in `frontend/public/locales/` (converted from .properties)
|
||||
- Conversion Script: `scripts/convert_properties_to_json.py`
|
||||
|
||||
### Configuration Modes
|
||||
- **Ultra-lite**: Basic PDF operations only
|
||||
- **Standard**: Full feature set
|
||||
- **Fat**: Pre-downloaded dependencies for air-gapped environments
|
||||
- **Security Mode**: Adds authentication, user management, and enterprise features
|
||||
|
||||
### Testing Strategy
|
||||
- **Integration Tests**: Cucumber tests in `testing/cucumber/`
|
||||
- **Docker Testing**: `test.sh` validates all Docker variants
|
||||
- **Manual Testing**: No unit tests currently - relies on UI and API testing
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Local Development**:
|
||||
- Backend: `./gradlew bootRun` (runs on localhost:8080)
|
||||
- Frontend: `cd frontend && npm run dev` (runs on localhost:5173, proxies to backend)
|
||||
2. **Docker Testing**: Use `./test.sh` before submitting PRs
|
||||
3. **Code Style**: Spotless enforces Google Java Format automatically
|
||||
4. **Translations**:
|
||||
- Backend: Use helper scripts in `/scripts` for multi-language updates
|
||||
- Frontend: Update JSON files in `frontend/public/locales/` or use conversion script
|
||||
5. **Documentation**: API docs auto-generated and available at `/swagger-ui/index.html`
|
||||
|
||||
## Frontend Architecture Status
|
||||
|
||||
- **Core Status**: React SPA architecture complete with multi-tool workflow support
|
||||
- **State Management**: FileContext handles all file operations and tool navigation
|
||||
- **File Processing**: Production-ready with memory management for large PDF workflows (up to 100GB+)
|
||||
- **Tool Integration**: Standardized tool interface - see `src/tools/Split.tsx` as reference
|
||||
- **Preview System**: Tool results can be previewed without polluting file context (Split tool example)
|
||||
- **Performance**: Web Worker thumbnails, IndexedDB persistence, background processing
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Java Version**: Minimum JDK 17, supports and recommends JDK 21
|
||||
- **Lombok**: Used extensively - ensure IDE plugin is installed
|
||||
- **Desktop Mode**: Set `STIRLING_PDF_DESKTOP_UI=true` for desktop application mode
|
||||
- **File Persistence**:
|
||||
- **Backend**: Designed to be stateless - files are processed in memory/temp locations only
|
||||
- **Frontend**: Uses IndexedDB for client-side file storage and caching (with thumbnails)
|
||||
- **Security**: When `DOCKER_ENABLE_SECURITY=false`, security-related classes are excluded from compilation
|
||||
- **FileContext**: All file operations MUST go through FileContext - never bypass with direct File handling
|
||||
- **Memory Management**: Manual cleanup required for PDF.js documents and blob URLs - don't remove cleanup code
|
||||
- **Tool Development**: New tools should follow Split tool pattern (`src/tools/Split.tsx`)
|
||||
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
||||
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
||||
|
||||
## Communication Style
|
||||
- Be direct and to the point
|
||||
- No apologies or conversational filler
|
||||
- Answer questions directly without preamble
|
||||
- Explain reasoning concisely when asked
|
||||
- Avoid unnecessary elaboration
|
||||
|
||||
## Decision Making
|
||||
- Ask clarifying questions before making assumptions
|
||||
- Stop and ask when uncertain about project-specific details
|
||||
- Confirm approach before making structural changes
|
||||
- Request guidance on preferences (cross-platform vs specific tools, etc.)
|
||||
- Verify understanding of requirements before proceeding
|
||||
@@ -0,0 +1,692 @@
|
||||
# Stirling-PDF Developer Guide
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
Stirling-PDF is a robust, locally hosted, web-based PDF manipulation tool. **Stirling 2.0** represents a complete frontend rewrite, replacing the legacy Thymeleaf-based UI with a modern React SPA (Single Page Application).
|
||||
|
||||
This guide focuses on developing for Stirling 2.0, including both the React frontend and Spring Boot backend development workflows.
|
||||
|
||||
## 2. Project Overview
|
||||
|
||||
**Stirling 2.0** is built using:
|
||||
|
||||
**Backend:**
|
||||
- Spring Boot (Java 17+, JDK 21 recommended)
|
||||
- PDFBox for core PDF operations
|
||||
- LibreOffice for document conversions
|
||||
- qpdf for PDF optimization
|
||||
- Spring Security (optional, controlled by `DOCKER_ENABLE_SECURITY`)
|
||||
- Lombok for reducing boilerplate code
|
||||
|
||||
**Frontend (React SPA):**
|
||||
- React + TypeScript
|
||||
- Vite for build tooling and development server
|
||||
- Mantine UI component library
|
||||
- TailwindCSS for styling
|
||||
- PDF.js for client-side PDF rendering
|
||||
- PDF-LIB.js for client-side PDF manipulation
|
||||
- IndexedDB for client-side file storage and thumbnails
|
||||
- i18next for internationalization
|
||||
|
||||
**Infrastructure:**
|
||||
- Docker for containerization
|
||||
- Gradle for build management
|
||||
|
||||
**Legacy (reference only during development):**
|
||||
- Thymeleaf templates (being completely replaced in 2.0)
|
||||
|
||||
## 3. Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker
|
||||
- Git
|
||||
- Java JDK 17 or later (JDK 21 recommended)
|
||||
- Node.js 18+ and npm (required for frontend development)
|
||||
- Gradle 7.0 or later (Included within the repo)
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Stirling-Tools/Stirling-PDF.git
|
||||
cd Stirling-PDF
|
||||
```
|
||||
|
||||
2. Install Docker and JDK17 if not already installed.
|
||||
|
||||
3. Install a recommended Java IDE such as Eclipse, IntelliJ, or VSCode
|
||||
1. Only VSCode
|
||||
1. Open VS Code.
|
||||
2. When prompted, install the recommended extensions.
|
||||
3. Alternatively, open the command palette (`Ctrl + Shift + P` or `Cmd + Shift + P` on macOS) and run:
|
||||
|
||||
```sh
|
||||
Extensions: Show Recommended Extensions
|
||||
```
|
||||
|
||||
4. Install the required extensions from the list.
|
||||
|
||||
4. Lombok Setup
|
||||
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
||||
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
|
||||
|
||||
5. Add environment variable
|
||||
For local testing, you should generally be testing the full 'Security' version of Stirling PDF. To do this, you must add the environment flag DISABLE_ADDITIONAL_FEATURES=false to your system and/or IDE build/run step.
|
||||
5. **Frontend Setup (Required for Stirling 2.0)**
|
||||
Navigate to the frontend directory and install dependencies using npm.
|
||||
|
||||
## 4. Stirling 2.0 Development Workflow
|
||||
|
||||
### Frontend Development (React)
|
||||
The frontend is a React SPA that runs independently during development:
|
||||
|
||||
1. **Start the backend**: Run the Spring Boot application (serves API endpoints on localhost:8080)
|
||||
2. **Start the frontend dev server**: Navigate to the frontend directory and run the development server (serves UI on localhost:5173)
|
||||
3. **Development flow**: The Vite dev server automatically proxies API calls to the backend
|
||||
|
||||
### File Storage Architecture
|
||||
Stirling 2.0 uses client-side file storage:
|
||||
- **IndexedDB**: Stores files locally in the browser with automatic thumbnail generation
|
||||
- **PDF.js**: Handles client-side PDF rendering and processing
|
||||
- **URL Parameters**: Support for deep linking and tool state persistence
|
||||
|
||||
### Legacy Code Reference
|
||||
The existing Thymeleaf templates remain in the codebase during development as reference material but will be completely removed for the 2.0 release.
|
||||
|
||||
## 5. Project Structure
|
||||
|
||||
```bash
|
||||
Stirling-PDF/
|
||||
├── .github/ # GitHub-specific files (workflows, issue templates)
|
||||
├── configs/ # Configuration files used by stirling at runtime (generated at runtime)
|
||||
├── frontend/ # React SPA frontend (Stirling 2.0)
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── tools/ # Tool-specific React components
|
||||
│ │ ├── hooks/ # Custom React hooks
|
||||
│ │ ├── services/ # API and utility services
|
||||
│ │ ├── types/ # TypeScript type definitions
|
||||
│ │ └── utils/ # Utility functions
|
||||
│ ├── public/
|
||||
│ │ └── locales/ # Internationalization files (JSON)
|
||||
│ ├── package.json # Frontend dependencies
|
||||
│ └── vite.config.ts # Vite configuration
|
||||
├── customFiles/ # Custom static files and templates (generated at runtime used to replace existing files)
|
||||
├── docs/ # Documentation files
|
||||
├── exampleYmlFiles/ # Example YAML configuration files
|
||||
├── images/ # Image assets
|
||||
├── pipeline/ # Pipeline-related files (generated at runtime)
|
||||
├── scripts/ # Utility scripts
|
||||
├── src/ # Source code
|
||||
│ ├── main/
|
||||
│ │ ├── java/
|
||||
│ │ │ └── stirling/
|
||||
│ │ │ └── software/
|
||||
│ │ │ └── SPDF/
|
||||
│ │ │ ├── config/
|
||||
│ │ │ ├── controller/
|
||||
│ │ │ ├── model/
|
||||
│ │ │ ├── repository/
|
||||
│ │ │ ├── service/
|
||||
│ │ │ └── utils/
|
||||
│ │ └── resources/
|
||||
│ │ ├── static/ # Legacy static assets (reference only)
|
||||
│ │ │ ├── css/
|
||||
│ │ │ ├── js/
|
||||
│ │ │ └── pdfjs/
|
||||
│ │ └── templates/ # Legacy Thymeleaf templates (reference only)
|
||||
│ └── test/
|
||||
├── testing/ # Cucumber and integration tests
|
||||
│ └── cucumber/ # Cucumber test files
|
||||
├── build.gradle # Gradle build configuration
|
||||
├── Dockerfile # Main Dockerfile
|
||||
├── Dockerfile.ultra-lite # Dockerfile for ultra-lite version
|
||||
├── Dockerfile.fat # Dockerfile for fat version
|
||||
├── docker-compose.yml # Docker Compose configuration
|
||||
└── test.sh # Test script to deploy all docker versions and run cuke tests
|
||||
```
|
||||
|
||||
## 6. Docker-based Development
|
||||
|
||||
Stirling-PDF offers several Docker versions:
|
||||
|
||||
- Full: All features included
|
||||
- Ultra-Lite: Basic PDF operations only
|
||||
- Fat: Includes additional libraries and fonts predownloaded
|
||||
|
||||
### Example Docker Compose Files
|
||||
|
||||
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory, such as:
|
||||
|
||||
- `docker-compose-latest.yml`: Latest version without login and security features
|
||||
- `docker-compose-latest-security.yml`: Latest version with login and security features enabled
|
||||
- `docker-compose-latest-fat-security.yml`: Fat version with login and security features enabled
|
||||
|
||||
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
```
|
||||
|
||||
To use these example files, copy the desired file to your project root and rename it to `docker-compose.yml`, or specify the file explicitly when running Docker Compose:
|
||||
|
||||
```bash
|
||||
docker-compose -f exampleYmlFiles/docker-compose-latest-security.yml up
|
||||
```
|
||||
|
||||
### Building Docker Images
|
||||
|
||||
Stirling-PDF uses different Docker images for various configurations. The build process is controlled by environment variables and uses specific Dockerfile variants. Here's how to build the Docker images:
|
||||
|
||||
1. Set the security environment variable:
|
||||
|
||||
```bash
|
||||
export DISABLE_ADDITIONAL_FEATURES=true # or false for to enable login and security features for builds
|
||||
```
|
||||
|
||||
2. Build the project with Gradle:
|
||||
|
||||
```bash
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
3. Build the Docker images:
|
||||
|
||||
For the latest version:
|
||||
|
||||
```bash
|
||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
|
||||
```
|
||||
|
||||
For the ultra-lite version:
|
||||
|
||||
```bash
|
||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
|
||||
```
|
||||
|
||||
For the fat version (with login and security features enabled):
|
||||
|
||||
```bash
|
||||
export DISABLE_ADDITIONAL_FEATURES=false
|
||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
|
||||
```
|
||||
|
||||
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase
|
||||
|
||||
## 7. Testing
|
||||
|
||||
### Comprehensive Testing Script
|
||||
|
||||
Stirling-PDF provides a `test.sh` script in the root directory. This script builds all versions of Stirling-PDF, checks that each version works, and runs Cucumber tests. It's recommended to run this script before submitting a final pull request.
|
||||
|
||||
To run the test script:
|
||||
|
||||
```bash
|
||||
./test.sh
|
||||
```
|
||||
|
||||
This script performs the following actions:
|
||||
|
||||
1. Builds all Docker images (full, ultra-lite, fat).
|
||||
2. Runs each version to ensure it starts correctly.
|
||||
3. Executes Cucumber tests against the main version and ensures feature compatibility. In the event these tests fail, your PR will not be merged.
|
||||
|
||||
Note: The `test.sh` script will run automatically when you raise a PR. However, it's recommended to run it locally first to save resources and catch any issues early.
|
||||
|
||||
### Full Testing with Docker
|
||||
|
||||
1. Build and run the Docker container per the above instructions:
|
||||
|
||||
2. Access the application at `http://localhost:8080` and manually test all features developed.
|
||||
|
||||
### Frontend Development Testing (Stirling 2.0)
|
||||
|
||||
For React frontend development:
|
||||
|
||||
1. Start the backend: Run the Spring Boot application to serve API endpoints on localhost:8080
|
||||
2. Start the frontend dev server: Navigate to the frontend directory and run the development server on localhost:5173
|
||||
3. The Vite dev server automatically proxies API calls to the backend
|
||||
4. Test React components, UI interactions, and IndexedDB file operations using browser developer tools
|
||||
|
||||
### Local Testing (Java and UI Components)
|
||||
|
||||
For quick iterations and development of Java backend, JavaScript, and UI components, you can run and test Stirling-PDF locally without Docker. This approach allows you to work on and verify changes to:
|
||||
|
||||
- Java backend logic
|
||||
- RESTful API endpoints
|
||||
- JavaScript functionality
|
||||
- User interface components and styling
|
||||
- Thymeleaf templates
|
||||
|
||||
To run Stirling-PDF locally:
|
||||
|
||||
1. Compile and run the project using built-in IDE methods or by running:
|
||||
|
||||
```bash
|
||||
./gradlew bootRun
|
||||
```
|
||||
|
||||
2. Access the application at `http://localhost:8080` in your web browser.
|
||||
|
||||
3. Manually test the features you're working on through the UI.
|
||||
|
||||
4. For API changes, use tools like Postman or curl to test endpoints directly.
|
||||
|
||||
Important notes:
|
||||
|
||||
- Local testing doesn't include features that depend on external tools like qpdf, LibreOffice, or Python scripts.
|
||||
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
|
||||
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
|
||||
|
||||
## 8. Contributing
|
||||
|
||||
1. Fork the repository on GitHub.
|
||||
2. Create a new branch for your feature or bug fix.
|
||||
3. Make your changes and commit them with clear, descriptive messages and ensure any documentation is updated related to your changes.
|
||||
4. Test your changes thoroughly in the Docker environment.
|
||||
5. Run the `test.sh` script to ensure all versions build correctly and pass the Cucumber tests:
|
||||
|
||||
```bash
|
||||
./test.sh
|
||||
```
|
||||
|
||||
6. Push your changes to your fork.
|
||||
7. Submit a pull request to the main repository.
|
||||
8. See additional [contributing guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md).
|
||||
|
||||
When you raise a PR:
|
||||
|
||||
- The `test.sh` script will run automatically against your PR.
|
||||
- The PR checks will verify versioning and dependency updates.
|
||||
- Documentation will be automatically updated for dependency changes.
|
||||
- Security issues will be checked using Snyk and PixeeBot.
|
||||
|
||||
Address any issues that arise from these checks before finalizing your pull request.
|
||||
|
||||
## 9. API Documentation
|
||||
|
||||
API documentation is available at `/swagger-ui/index.html` when running the application. You can also view the latest API documentation [here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/).
|
||||
|
||||
## 10. Customization
|
||||
|
||||
Stirling-PDF can be customized through environment variables or a `settings.yml` file. Key customization options include:
|
||||
|
||||
- Application name and branding
|
||||
- Security settings
|
||||
- UI customization
|
||||
- Endpoint management
|
||||
|
||||
When using Docker, pass environment variables using the `-e` flag or in your `docker-compose.yml` file.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
docker run -p 8080:8080 -e APP_NAME="My PDF Tool" stirling-pdf:full
|
||||
```
|
||||
|
||||
Refer to the main README for a full list of customization options.
|
||||
|
||||
## 11. Language Translations
|
||||
|
||||
For managing language translations that affect multiple files, Stirling-PDF provides a helper script:
|
||||
|
||||
```bash
|
||||
/scripts/replace_translation_line.sh
|
||||
```
|
||||
|
||||
This script helps you make consistent replacements across language files.
|
||||
|
||||
When contributing translations:
|
||||
|
||||
1. Use the helper script for multi-file changes.
|
||||
2. Ensure all language files are updated consistently.
|
||||
3. The PR checks will verify consistency in language file updates.
|
||||
|
||||
Remember to test your changes thoroughly to ensure they don't break any existing functionality.
|
||||
|
||||
## Code examples
|
||||
|
||||
### React Component Development (Stirling 2.0)
|
||||
|
||||
For Stirling 2.0, new features are built as React components instead of Thymeleaf templates:
|
||||
|
||||
#### Creating a New Tool Component
|
||||
|
||||
1. **Create the React Component:**
|
||||
```typescript
|
||||
// frontend/src/tools/NewTool.tsx
|
||||
import { useState } from 'react';
|
||||
import { Button, FileInput, Container } from '@mantine/core';
|
||||
|
||||
interface NewToolProps {
|
||||
params: Record<string, any>;
|
||||
updateParams: (updates: Record<string, any>) => void;
|
||||
}
|
||||
|
||||
export default function NewTool({ params, updateParams }: NewToolProps) {
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
|
||||
const handleProcess = async () => {
|
||||
// Process files using API or client-side logic
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<FileInput
|
||||
multiple
|
||||
accept="application/pdf"
|
||||
onChange={setFiles}
|
||||
/>
|
||||
<Button onClick={handleProcess}>Process</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add API Integration:**
|
||||
```typescript
|
||||
// Use existing API endpoints or create new ones
|
||||
const response = await fetch('/api/v1/new-tool', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
```
|
||||
|
||||
3. **Register in Tool Picker:**
|
||||
Update the tool picker component to include the new tool with proper routing and URL parameter support.
|
||||
|
||||
### Legacy Reference: Overview of Thymeleaf
|
||||
|
||||
Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot.
|
||||
|
||||
### Thymeleaf overview
|
||||
|
||||
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `stirling-pdf/src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
|
||||
|
||||
Some examples of this are:
|
||||
|
||||
```html
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
```
|
||||
or
|
||||
```html
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
```
|
||||
|
||||
Where it uses the `th:block`, `th:` indicating it's a special Thymeleaf element to be used server-side in generating the HTML, and block being the actual element type.
|
||||
In this case, we are inserting the `navbar` entry within the `fragments/navbar.html` fragment into the `th:block` element.
|
||||
|
||||
They can be more complex, such as:
|
||||
|
||||
```html
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
|
||||
```
|
||||
|
||||
Which is the same as above but passes the parameters title and header into the fragment `common.html` to be used in its HTML generation.
|
||||
|
||||
Thymeleaf can also be used to loop through objects or pass things from the Java side into the HTML side.
|
||||
|
||||
```java
|
||||
@GetMapping
|
||||
public String newFeaturePage(Model model) {
|
||||
model.addAttribute("exampleData", exampleData);
|
||||
return "new-feature";
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, if exampleData is a list of plain java objects of class Person and within it, you had id, name, age, etc. You can reference it like so
|
||||
|
||||
```html
|
||||
<tbody>
|
||||
<!-- Use th:each to iterate over the list -->
|
||||
<tr th:each="person : ${exampleData}">
|
||||
<td th:text="${person.id}"></td>
|
||||
<td th:text="${person.name}"></td>
|
||||
<td th:text="${person.age}"></td>
|
||||
<td th:text="${person.email}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
```
|
||||
|
||||
This would generate n entries of tr for each person in exampleData
|
||||
|
||||
### Adding a New Feature to the Backend (API)
|
||||
|
||||
1. **Create a New Controller:**
|
||||
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/api` directory.
|
||||
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
|
||||
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
|
||||
|
||||
```java
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/new-feature")
|
||||
@Tag(name = "General", description = "General APIs")
|
||||
public class NewFeatureController {
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
||||
public String newFeature() {
|
||||
return "NewFeatureResponse"; // This refers to the NewFeatureResponse.html template presenting the user with the generated html from that file when they navigate to /api/v1/new-feature
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Define the Service Layer:** (Not required but often useful)
|
||||
- Create a new service class in the `stirling-pdf/src/main/java/stirling/software/SPDF/service` directory.
|
||||
- Implement the business logic for the new feature.
|
||||
|
||||
```java
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class NewFeatureService {
|
||||
|
||||
public String getNewFeatureData() {
|
||||
// Implement business logic here
|
||||
return "New Feature Data";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2b. **Integrate the Service with the Controller:**
|
||||
|
||||
- Autowire the service class in the controller and use it to handle the API request.
|
||||
|
||||
```java
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import stirling.software.SPDF.service.NewFeatureService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/new-feature")
|
||||
@Tag(name = "General", description = "General APIs")
|
||||
public class NewFeatureController {
|
||||
|
||||
@Autowired
|
||||
private NewFeatureService newFeatureService;
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
||||
public String newFeature() {
|
||||
return newFeatureService.getNewFeatureData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a New Feature to the Frontend (UI)
|
||||
|
||||
1. **Create a New Thymeleaf Template:**
|
||||
- Create a new HTML file in the `stirling-pdf/src/main/resources/templates` directory.
|
||||
- Use Thymeleaf attributes to dynamically generate content.
|
||||
- Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{newFeature.title}, header=#{newFeature.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<br><br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 bg-card">
|
||||
<div class="tool-header">
|
||||
<span class="material-symbols-rounded tool-header-icon organize">upload</span>
|
||||
<span class="tool-header-text" th:text="#{newFeature.header}"></span>
|
||||
</div>
|
||||
<form th:action="@{'/api/v1/new-feature'}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||
<input type="hidden" id="customMode" name="customMode" value="">
|
||||
<div class="mb-3">
|
||||
<label for="featureInput" th:text="#{newFeature.prompt}"></label>
|
||||
<input type="text" class="form-control" id="featureInput" name="featureInput" th:placeholder="#{newFeature.placeholder}" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{newFeature.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. **Create a New Controller for the UI:**
|
||||
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/ui` directory.
|
||||
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
|
||||
|
||||
```java
|
||||
package stirling.software.SPDF.controller.ui;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import stirling.software.SPDF.service.NewFeatureService;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/new-feature")
|
||||
public class NewFeatureUIController {
|
||||
|
||||
@Autowired
|
||||
private NewFeatureService newFeatureService;
|
||||
|
||||
@GetMapping
|
||||
public String newFeaturePage(Model model) {
|
||||
model.addAttribute("newFeatureData", newFeatureService.getNewFeatureData());
|
||||
return "new-feature";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Update the Navigation Bar:**
|
||||
- Add a link to the new feature page in the navigation bar.
|
||||
- Update the `stirling-pdf/src/main/resources/templates/fragments/navbar.html` file.
|
||||
|
||||
```html
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" th:href="@{'/new-feature'}">New Feature</a>
|
||||
</li>
|
||||
```
|
||||
|
||||
## Adding New Translations to Existing Language Files in Stirling-PDF
|
||||
|
||||
When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide:
|
||||
|
||||
### 1. Locate Existing Language Files
|
||||
|
||||
Find the existing `messages.properties` files in the `stirling-pdf/src/main/resources` directory. You'll see files like:
|
||||
|
||||
- `messages.properties` (default, usually English)
|
||||
- `messages_en_GB.properties`
|
||||
- `messages_fr_FR.properties`
|
||||
- `messages_de_DE.properties`
|
||||
- etc.
|
||||
|
||||
### 2. Add New Translation Entries
|
||||
|
||||
Open each of these files and add your new translation entries. For example, if you're adding a new feature called "PDF Splitter",
|
||||
Use descriptive, hierarchical keys (e.g., `feature.element.description`)
|
||||
you might add:
|
||||
|
||||
```properties
|
||||
pdfSplitter.title=PDF Splitter
|
||||
pdfSplitter.description=Split your PDF into multiple documents
|
||||
pdfSplitter.button.split=Split PDF
|
||||
pdfSplitter.input.pages=Enter page numbers to split
|
||||
```
|
||||
|
||||
Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language.
|
||||
|
||||
### 3. Use Translations in Thymeleaf Templates
|
||||
|
||||
In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations:
|
||||
|
||||
```html
|
||||
<h1 th:text="#{pdfSplitter.title}">PDF Splitter</h1>
|
||||
<p th:text="#{pdfSplitter.description}">Split your PDF into multiple documents</p>
|
||||
<input type="text" th:placeholder="#{pdfSplitter.input.pages}">
|
||||
<button th:text="#{pdfSplitter.button.split}">Split PDF</button>
|
||||
```
|
||||
|
||||
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
||||
@@ -1,61 +0,0 @@
|
||||
# dockerfile.dev
|
||||
|
||||
# Basisimage: Gradle mit JDK 17 (Debian-basiert)
|
||||
FROM gradle:8.14-jdk17
|
||||
|
||||
# Als Root-Benutzer arbeiten, um benötigte Pakete zu installieren
|
||||
USER root
|
||||
|
||||
# Set GRADLE_HOME und füge Gradle zum PATH hinzu
|
||||
ENV GRADLE_HOME=/opt/gradle
|
||||
ENV PATH="$GRADLE_HOME/bin:$PATH"
|
||||
|
||||
# Update und Installation zusätzlicher Pakete (Debian/Ubuntu-basiert)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
sudo \
|
||||
libreoffice \
|
||||
poppler-utils \
|
||||
qpdf \
|
||||
# settings.yml | tessdataDir: /usr/share/tesseract-ocr/5/tessdata
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
fonts-terminus fonts-dejavu fonts-font-awesome fonts-noto fonts-noto-core fonts-noto-cjk fonts-noto-extra fonts-liberation fonts-linuxlibertine fonts-urw-base35 \
|
||||
python3-uno \
|
||||
python3-venv \
|
||||
# ss -tln
|
||||
iproute2 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Setze die Environment Variable für setuptools
|
||||
ENV SETUPTOOLS_USE_DISTUTILS=local \
|
||||
STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
|
||||
TMPDIR=/tmp/stirling-pdf \
|
||||
TEMP=/tmp/stirling-pdf \
|
||||
TMP=/tmp/stirling-pdf
|
||||
|
||||
# Installation der benötigten Python-Pakete
|
||||
RUN python3 -m venv --system-site-packages /opt/venv \
|
||||
&& . /opt/venv/bin/activate \
|
||||
&& pip install --upgrade pip setuptools \
|
||||
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
|
||||
|
||||
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
COPY . /workspace
|
||||
|
||||
RUN mkdir -p /tmp/stirling-pdf \
|
||||
&& fc-cache -f -v \
|
||||
&& adduser --disabled-password --gecos '' devuser \
|
||||
&& chown -R devuser:devuser /home/devuser /workspace /tmp/stirling-pdf
|
||||
RUN echo "devuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/devuser \
|
||||
&& chmod 0440 /etc/sudoers.d/devuser
|
||||
|
||||
# Setze das Arbeitsverzeichnis (wird später per Bind-Mount überschrieben)
|
||||
WORKDIR /workspace
|
||||
|
||||
RUN chmod +x /workspace/.devcontainer/git-init.sh
|
||||
RUN sudo chmod +x /workspace/.devcontainer/init-setup.sh
|
||||
|
||||
# Wechsel zum Nicht‑Root Benutzer
|
||||
USER devuser
|
||||
@@ -143,7 +143,7 @@ Stirling-PDF currently supports 40 languages!
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
|
||||
@@ -26,7 +26,7 @@ dependencies {
|
||||
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
||||
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
|
||||
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
||||
api 'org.snakeyaml:snakeyaml-engine:2.10'
|
||||
api 'org.snakeyaml:snakeyaml-engine:2.9'
|
||||
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
|
||||
api 'jakarta.mail:jakarta.mail-api:2.1.3'
|
||||
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.3'
|
||||
|
||||
@@ -46,7 +46,7 @@ dependencies {
|
||||
implementation 'commons-io:commons-io:2.19.0'
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
implementation 'io.micrometer:micrometer-core:1.15.2'
|
||||
implementation 'io.micrometer:micrometer-core:1.15.1'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
implementation "org.commonmark:commonmark:$commonmarkVersion" // https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:$commonmarkVersion"
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URLConnection;
|
||||
@@ -88,7 +87,7 @@ public class ConvertImgPDFController {
|
||||
// returns bytes for image
|
||||
boolean singleImage = "single".equals(singleOrMultiple);
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(new File(file.getOriginalFilename()).getName())
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
result =
|
||||
@@ -232,7 +231,7 @@ public class ConvertImgPDFController {
|
||||
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
bytes,
|
||||
new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
||||
file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
||||
}
|
||||
|
||||
private String getMediaType(String imageFormat) {
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.common.configuration.AppConfig;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Config", description = "Configuration APIs")
|
||||
@RequestMapping("/api/v1/config")
|
||||
@RequiredArgsConstructor
|
||||
@Hidden
|
||||
public class ConfigController {
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
|
||||
@GetMapping("/app-config")
|
||||
public ResponseEntity<Map<String, Object>> getAppConfig() {
|
||||
Map<String, Object> configData = new HashMap<>();
|
||||
|
||||
try {
|
||||
// Get AppConfig bean
|
||||
AppConfig appConfig = applicationContext.getBean(AppConfig.class);
|
||||
|
||||
// Extract key configuration values from AppConfig
|
||||
configData.put("baseUrl", appConfig.getBaseUrl());
|
||||
configData.put("contextPath", appConfig.getContextPath());
|
||||
configData.put("serverPort", appConfig.getServerPort());
|
||||
|
||||
// Extract values from ApplicationProperties
|
||||
configData.put("appName", applicationProperties.getUi().getAppName());
|
||||
configData.put("appNameNavbar", applicationProperties.getUi().getAppNameNavbar());
|
||||
configData.put("homeDescription", applicationProperties.getUi().getHomeDescription());
|
||||
configData.put("languages", applicationProperties.getUi().getLanguages());
|
||||
|
||||
// Security settings
|
||||
configData.put("enableLogin", applicationProperties.getSecurity().getEnableLogin());
|
||||
|
||||
// System settings
|
||||
configData.put(
|
||||
"enableAlphaFunctionality",
|
||||
applicationProperties.getSystem().getEnableAlphaFunctionality());
|
||||
configData.put(
|
||||
"enableAnalytics", applicationProperties.getSystem().getEnableAnalytics());
|
||||
|
||||
// Premium/Enterprise settings
|
||||
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
|
||||
|
||||
// Legal settings
|
||||
configData.put(
|
||||
"termsAndConditions", applicationProperties.getLegal().getTermsAndConditions());
|
||||
configData.put("privacyPolicy", applicationProperties.getLegal().getPrivacyPolicy());
|
||||
configData.put("cookiePolicy", applicationProperties.getLegal().getCookiePolicy());
|
||||
configData.put("impressum", applicationProperties.getLegal().getImpressum());
|
||||
configData.put(
|
||||
"accessibilityStatement",
|
||||
applicationProperties.getLegal().getAccessibilityStatement());
|
||||
|
||||
// Try to get EEAppConfig values if available
|
||||
try {
|
||||
if (applicationContext.containsBean("runningProOrHigher")) {
|
||||
configData.put(
|
||||
"runningProOrHigher",
|
||||
applicationContext.getBean("runningProOrHigher", Boolean.class));
|
||||
}
|
||||
if (applicationContext.containsBean("runningEE")) {
|
||||
configData.put(
|
||||
"runningEE", applicationContext.getBean("runningEE", Boolean.class));
|
||||
}
|
||||
if (applicationContext.containsBean("license")) {
|
||||
configData.put("license", applicationContext.getBean("license", String.class));
|
||||
}
|
||||
if (applicationContext.containsBean("GoogleDriveEnabled")) {
|
||||
configData.put(
|
||||
"GoogleDriveEnabled",
|
||||
applicationContext.getBean("GoogleDriveEnabled", Boolean.class));
|
||||
}
|
||||
if (applicationContext.containsBean("SSOAutoLogin")) {
|
||||
configData.put(
|
||||
"SSOAutoLogin",
|
||||
applicationContext.getBean("SSOAutoLogin", Boolean.class));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// EE features not available, continue without them
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(configData);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Return basic config if there are any issues
|
||||
configData.put("error", "Unable to retrieve full configuration");
|
||||
return ResponseEntity.ok(configData);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/endpoint-enabled")
|
||||
public ResponseEntity<Boolean> isEndpointEnabled(@RequestParam String endpoint) {
|
||||
boolean enabled = endpointConfiguration.isEndpointEnabled(endpoint);
|
||||
return ResponseEntity.ok(enabled);
|
||||
}
|
||||
|
||||
@GetMapping("/endpoints-enabled")
|
||||
public ResponseEntity<Map<String, Boolean>> areEndpointsEnabled(
|
||||
@RequestParam String endpoints) {
|
||||
Map<String, Boolean> result = new HashMap<>();
|
||||
String[] endpointArray = endpoints.split(",");
|
||||
for (String endpoint : endpointArray) {
|
||||
String trimmedEndpoint = endpoint.trim();
|
||||
result.put(trimmedEndpoint, endpointConfiguration.isEndpointEnabled(trimmedEndpoint));
|
||||
}
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -33,7 +33,7 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ScannerEffectRequest;
|
||||
import stirling.software.SPDF.model.api.misc.FakeScanRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@@ -42,7 +42,7 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
@Tag(name = "Misc", description = "Miscellaneous PDF APIs")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ScannerEffectController {
|
||||
public class FakeScanController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private static final Random RANDOM = new Random();
|
||||
@@ -52,12 +52,12 @@ public class ScannerEffectController {
|
||||
private static final int MAX_IMAGE_HEIGHT = 8192;
|
||||
private static final long MAX_IMAGE_PIXELS = 16_777_216; // 4096x4096
|
||||
|
||||
@PostMapping(value = "/scanner-effect", consumes = "multipart/form-data")
|
||||
@PostMapping(value = "/fake-scan", consumes = "multipart/form-data")
|
||||
@Operation(
|
||||
summary = "Apply scanner effect to PDF",
|
||||
summary = "Convert PDF to look like a scanned document",
|
||||
description =
|
||||
"Applies various effects to simulate a scanned document, including rotation, noise, and edge softening. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> scannerEffect(@Valid @ModelAttribute ScannerEffectRequest request)
|
||||
"Applies various effects to make a PDF look like it was scanned, including rotation, noise, and edge softening. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> fakeScan(@Valid @ModelAttribute FakeScanRequest request)
|
||||
throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
|
||||
@@ -80,7 +80,7 @@ public class ScannerEffectController {
|
||||
float noise = request.getNoise();
|
||||
boolean yellowish = request.isYellowish();
|
||||
int resolution = request.getResolution();
|
||||
ScannerEffectRequest.Colorspace colorspace = request.getColorspace();
|
||||
FakeScanRequest.Colorspace colorspace = request.getColorspace();
|
||||
|
||||
try (PDDocument document = pdfDocumentFactory.load(file)) {
|
||||
PDDocument outputDocument = new PDDocument();
|
||||
@@ -130,7 +130,7 @@ public class ScannerEffectController {
|
||||
|
||||
// 1. Convert to grayscale or keep color
|
||||
BufferedImage processed;
|
||||
if (colorspace == ScannerEffectRequest.Colorspace.grayscale) {
|
||||
if (colorspace == FakeScanRequest.Colorspace.grayscale) {
|
||||
processed =
|
||||
new BufferedImage(
|
||||
image.getWidth(),
|
||||
@@ -316,7 +316,7 @@ public class ScannerEffectController {
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_scanner_effect.pdf";
|
||||
+ "_scanned.pdf";
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
outputStream.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);
|
||||
-5
@@ -7,7 +7,6 @@ import java.awt.print.Printable;
|
||||
import java.awt.print.PrinterException;
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -46,10 +45,6 @@ public class PrintFileController {
|
||||
public ResponseEntity<String> printFile(@ModelAttribute PrintFileRequest request)
|
||||
throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (originalFilename != null && (originalFilename.contains("..") || Paths.get(originalFilename).isAbsolute())) {
|
||||
throw new IOException("Invalid file path detected: " + originalFilename);
|
||||
}
|
||||
String printerName = request.getPrinterName();
|
||||
String contentType = file.getContentType();
|
||||
try {
|
||||
|
||||
-10
@@ -42,7 +42,6 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import java.lang.IllegalArgumentException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@@ -63,18 +62,9 @@ public class StampController {
|
||||
public ResponseEntity<byte[]> addStamp(@ModelAttribute AddStampRequest request)
|
||||
throws IOException, Exception {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pdfFileName = pdfFile.getOriginalFilename();
|
||||
if (pdfFileName.contains("..") || pdfFileName.startsWith("/")) {
|
||||
throw new IllegalArgumentException("Invalid PDF file path");
|
||||
}
|
||||
|
||||
String stampType = request.getStampType();
|
||||
String stampText = request.getStampText();
|
||||
MultipartFile stampImage = request.getStampImage();
|
||||
String stampImageName = stampImage.getOriginalFilename();
|
||||
if (stampImageName.contains("..") || stampImageName.startsWith("/")) {
|
||||
throw new IllegalArgumentException("Invalid stamp image file path");
|
||||
}
|
||||
String alphabet = request.getAlphabet();
|
||||
float fontSize = request.getFontSize();
|
||||
float rotation = request.getRotation();
|
||||
|
||||
+1
-7
@@ -108,9 +108,7 @@ public class PipelineProcessor {
|
||||
if (inputFileTypes == null) {
|
||||
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
|
||||
}
|
||||
if (!operation.matches("^[a-zA-Z0-9_-]+$")) {
|
||||
throw new IllegalArgumentException("Invalid operation value received.");
|
||||
}
|
||||
// List outputFileTypes = apiDocService.getExtensionTypes(true, operation);
|
||||
String url = getBaseUrl() + operation;
|
||||
List<Resource> newOutputFiles = new ArrayList<>();
|
||||
if (!isMultiInputOperation) {
|
||||
@@ -329,10 +327,6 @@ public class PipelineProcessor {
|
||||
}
|
||||
List<Resource> outputFiles = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
Path normalizedPath = Paths.get(file.getName()).normalize();
|
||||
if (normalizedPath.startsWith("..")) {
|
||||
throw new SecurityException("Potential path traversal attempt in file name: " + file.getName());
|
||||
}
|
||||
Path path = Paths.get(file.getAbsolutePath());
|
||||
// debug statement
|
||||
log.info("Reading file: " + path);
|
||||
|
||||
-10
@@ -74,19 +74,9 @@ public class WatermarkController {
|
||||
public ResponseEntity<byte[]> addWatermark(@ModelAttribute AddWatermarkRequest request)
|
||||
throws IOException, Exception {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String pdfFileName = pdfFile.getOriginalFilename();
|
||||
if (pdfFileName != null && (pdfFileName.contains("..") || pdfFileName.startsWith("/"))) {
|
||||
throw new SecurityException("Invalid file path in pdfFile");
|
||||
}
|
||||
String watermarkType = request.getWatermarkType();
|
||||
String watermarkText = request.getWatermarkText();
|
||||
MultipartFile watermarkImage = request.getWatermarkImage();
|
||||
if (watermarkImage != null) {
|
||||
String watermarkImageFileName = watermarkImage.getOriginalFilename();
|
||||
if (watermarkImageFileName != null && (watermarkImageFileName.contains("..") || watermarkImageFileName.startsWith("/"))) {
|
||||
throw new SecurityException("Invalid file path in watermarkImage");
|
||||
}
|
||||
}
|
||||
String alphabet = request.getAlphabet();
|
||||
float fontSize = request.getFontSize();
|
||||
float rotation = request.getRotation();
|
||||
|
||||
+4
-4
@@ -70,11 +70,11 @@ public class OtherWebController {
|
||||
return "misc/add-page-numbers";
|
||||
}
|
||||
|
||||
@GetMapping("/scanner-effect")
|
||||
@GetMapping("/fake-scan")
|
||||
@Hidden
|
||||
public String scannerEffectForm(Model model) {
|
||||
model.addAttribute("currentPage", "scanner-effect");
|
||||
return "misc/scanner-effect";
|
||||
public String fakeScanForm(Model model) {
|
||||
model.addAttribute("currentPage", "fake-scan");
|
||||
return "misc/fake-scan";
|
||||
}
|
||||
|
||||
@GetMapping("/extract-images")
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class ReactRoutingController {
|
||||
|
||||
@GetMapping("/{path:^(?!api|static|robots\\.txt|favicon\\.ico)[^\\.]*$}")
|
||||
public String forwardRootPaths() {
|
||||
return "forward:/index.html";
|
||||
}
|
||||
|
||||
@GetMapping("/{path:^(?!api|static)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
|
||||
public String forwardNestedPaths() {
|
||||
return "forward:/index.html";
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
public class ScannerEffectRequest {
|
||||
public class FakeScanRequest {
|
||||
public enum Quality {
|
||||
low,
|
||||
medium,
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Diese Cookies sind für das
|
||||
cookieBanner.preferencesModal.analytics.title=Analyse
|
||||
cookieBanner.preferencesModal.analytics.description=Diese Cookies helfen uns zu verstehen, wie unsere Tools genutzt werden, damit wir uns darauf konzentrieren können, die Funktionen zu entwickeln, die unserer Community am meisten am Herzen liegen. Seien Sie beruhigt – Stirling PDF kann und wird niemals den Inhalt der Dokumente verfolgen, mit denen Sie arbeiten.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner-Effect-PDF
|
||||
scannerEffect.header=Scanner-Effect-PDF
|
||||
scannerEffect.description=Erstellen Sie ein PDF, das so aussieht, als wäre es gescannt worden
|
||||
scannerEffect.selectPDF=Wählen Sie PDF:
|
||||
scannerEffect.quality=Scan-Qualität
|
||||
scannerEffect.quality.low=Niedrig
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=Hoch
|
||||
scannerEffect.rotation=Rotationswinkel
|
||||
scannerEffect.rotation.none=Keiner
|
||||
scannerEffect.rotation.slight=Leicht
|
||||
scannerEffect.rotation.moderate=Mäßig
|
||||
scannerEffect.rotation.severe=Schwer
|
||||
scannerEffect.submit=Erstellen Sie einen Scanner-Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake-Scan-PDF
|
||||
fakeScan.header=Fake-Scan-PDF
|
||||
fakeScan.description=Erstellen Sie ein PDF, das so aussieht, als wäre es gescannt worden
|
||||
fakeScan.selectPDF=Wählen Sie PDF:
|
||||
fakeScan.quality=Scan-Qualität
|
||||
fakeScan.quality.low=Niedrig
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=Hoch
|
||||
fakeScan.rotation=Rotationswinkel
|
||||
fakeScan.rotation.none=Keiner
|
||||
fakeScan.rotation.slight=Leicht
|
||||
fakeScan.rotation.moderate=Mäßig
|
||||
fakeScan.rotation.severe=Schwer
|
||||
fakeScan.submit=Erstellen Sie einen Fake-Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner-Effect-PDF
|
||||
home.scannerEffect.desc=Erstellen Sie ein PDF, das so aussieht, als wäre es gescannt worden
|
||||
scannerEffect.tags=scannen,simulieren,realistisch,konvertieren,fake,scan,pdf
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake-Scan-PDF
|
||||
home.fakeScan.desc=Erstellen Sie ein PDF, das so aussieht, als wäre es gescannt worden
|
||||
fakeScan.tags=scannen,simulieren,realistisch,konvertieren,fake,scan,pdf
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Aktivieren Sie erweiterte Scaneinstellungen
|
||||
scannerEffect.colorspace=Farbraum
|
||||
scannerEffect.colorspace.grayscale=Graustufen
|
||||
scannerEffect.colorspace.color=Farbe
|
||||
scannerEffect.border=Grenze (PX)
|
||||
scannerEffect.rotate=Grundrotation (Grad)
|
||||
scannerEffect.rotateVariance=Rotationsvarianz (Grad)
|
||||
scannerEffect.brightness=Helligkeit
|
||||
scannerEffect.contrast=Kontrast
|
||||
scannerEffect.blur=Verwischen
|
||||
scannerEffect.noise=Rauschen
|
||||
scannerEffect.yellowish=Gelblich (simulieren Sie altes Papier)
|
||||
scannerEffect.resolution=Auflösung (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Aktivieren Sie erweiterte Scaneinstellungen
|
||||
fakeScan.colorspace=Farbraum
|
||||
fakeScan.colorspace.grayscale=Graustufen
|
||||
fakeScan.colorspace.color=Farbe
|
||||
fakeScan.border=Grenze (PX)
|
||||
fakeScan.rotate=Grundrotation (Grad)
|
||||
fakeScan.rotateVariance=Rotationsvarianz (Grad)
|
||||
fakeScan.brightness=Helligkeit
|
||||
fakeScan.contrast=Kontrast
|
||||
fakeScan.blur=Verwischen
|
||||
fakeScan.noise=Rauschen
|
||||
fakeScan.yellowish=Gelblich (simulieren Sie altes Papier)
|
||||
fakeScan.resolution=Auflösung (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,105 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
####################
|
||||
# React Frontend #
|
||||
####################
|
||||
# Common UI elements
|
||||
removeMetadata.submit=Remove Metadata
|
||||
sidebar.toggle=Toggle Sidebar
|
||||
theme.toggle=Toggle Theme
|
||||
view.viewer=Viewer
|
||||
view.pageEditor=Page Editor
|
||||
view.fileManager=File Manager
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
# File management
|
||||
fileManager.dragDrop=Drag & Drop files here
|
||||
fileManager.clickToUpload=Click to upload files
|
||||
fileManager.selectedFiles=Selected Files
|
||||
fileManager.clearAll=Clear All
|
||||
fileManager.storage=Storage
|
||||
fileManager.filesStored=files stored
|
||||
fileManager.storageError=Storage error occurred
|
||||
fileManager.storageLow=Storage is running low. Consider removing old files.
|
||||
fileManager.uploadError=Failed to upload some files.
|
||||
fileManager.supportMessage=Powered by browser database storage for unlimited capacity
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# Page Editor
|
||||
pageEditor.title=Page Editor
|
||||
pageEditor.save=Save Changes
|
||||
pageEditor.noPdfLoaded=No PDF loaded. Please upload a PDF to edit.
|
||||
pageEditor.rotatedLeft=Rotated left:
|
||||
pageEditor.rotatedRight=Rotated right:
|
||||
pageEditor.deleted=Deleted:
|
||||
pageEditor.movedLeft=Moved left:
|
||||
pageEditor.movedRight=Moved right:
|
||||
pageEditor.splitAt=Split at:
|
||||
pageEditor.insertedPageBreak=Inserted page break at:
|
||||
pageEditor.addFileNotImplemented=Add file not implemented in demo
|
||||
pageEditor.closePdf=Close PDF
|
||||
|
||||
# Viewer
|
||||
viewer.noPdfLoaded=No PDF loaded. Click to upload a PDF.
|
||||
viewer.choosePdf=Choose PDF
|
||||
viewer.noPagesToDisplay=No pages to display.
|
||||
viewer.singlePageView=Single Page View
|
||||
viewer.dualPageView=Dual Page View
|
||||
viewer.hideSidebars=Hide Sidebars
|
||||
viewer.showSidebars=Show Sidebars
|
||||
viewer.zoomOut=Zoom out
|
||||
viewer.zoomIn=Zoom in
|
||||
|
||||
# Tool Picker
|
||||
toolPicker.searchPlaceholder=Search tools...
|
||||
toolPicker.noToolsFound=No tools found
|
||||
pageEditor.reset=Reset Changes
|
||||
pageEditor.zoomIn=Zoom In
|
||||
pageEditor.zoomOut=Zoom Out
|
||||
pageEditor.fitToWidth=Fit to Width
|
||||
pageEditor.actualSize=Actual Size
|
||||
|
||||
# Viewer
|
||||
viewer.previousPage=Previous Page
|
||||
viewer.nextPage=Next Page
|
||||
viewer.pageNavigation=Page Navigation
|
||||
viewer.currentPage=Current Page
|
||||
viewer.totalPages=Total Pages
|
||||
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Estas cookies son esenciales
|
||||
cookieBanner.preferencesModal.analytics.title=Análisis
|
||||
cookieBanner.preferencesModal.analytics.description=Estas cookies nos ayudan a entender cómo se están utilizando nuestras herramientas, para que podamos centrarnos en desarrollar las funciones que nuestra comunidad valora más. Tenga la seguridad de que Stirling PDF no puede y nunca podrá rastrear el contenido de los documentos con los que trabaja.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Ces cookies sont essentiels
|
||||
cookieBanner.preferencesModal.analytics.title=Analyse
|
||||
cookieBanner.preferencesModal.analytics.description=Ces cookies nous aident à comprendre comment nos outils sont utilisés, afin que nous puissions nous concentrer sur les fonctionnalités les plus appréciées par notre communauté. Soyez rassuré — Stirling PDF ne peut pas et ne suivra jamais le contenu des documents que vous utilisez.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Fausse numérisation
|
||||
scannerEffect.header=Fausse numérisation
|
||||
scannerEffect.description=Créer un PDF qui ressemble à une numérisation
|
||||
scannerEffect.selectPDF=Sélectionner un PDF :
|
||||
scannerEffect.quality=Qualité de numérisation
|
||||
scannerEffect.quality.low=Faible
|
||||
scannerEffect.quality.medium=Moyenne
|
||||
scannerEffect.quality.high=Élevée
|
||||
scannerEffect.rotation=Angle de rotation
|
||||
scannerEffect.rotation.none=Aucun
|
||||
scannerEffect.rotation.slight=Léger
|
||||
scannerEffect.rotation.moderate=Modéré
|
||||
scannerEffect.rotation.severe=Sévère
|
||||
scannerEffect.submit=Créer une fausse numérisation
|
||||
#fakeScan
|
||||
fakeScan.title=Fausse numérisation
|
||||
fakeScan.header=Fausse numérisation
|
||||
fakeScan.description=Créer un PDF qui ressemble à une numérisation
|
||||
fakeScan.selectPDF=Sélectionner un PDF :
|
||||
fakeScan.quality=Qualité de numérisation
|
||||
fakeScan.quality.low=Faible
|
||||
fakeScan.quality.medium=Moyenne
|
||||
fakeScan.quality.high=Élevée
|
||||
fakeScan.rotation=Angle de rotation
|
||||
fakeScan.rotation.none=Aucun
|
||||
fakeScan.rotation.slight=Léger
|
||||
fakeScan.rotation.moderate=Modéré
|
||||
fakeScan.rotation.severe=Sévère
|
||||
fakeScan.submit=Créer une fausse numérisation
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Fausse numérisation
|
||||
home.scannerEffect.desc=Créer un PDF qui ressemble à une numérisation
|
||||
scannerEffect.tags=numérisation,simuler,réaliste,convertir,scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fausse numérisation
|
||||
home.fakeScan.desc=Créer un PDF qui ressemble à une numérisation
|
||||
fakeScan.tags=numérisation,simuler,réaliste,convertir,scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Activer les paramètres de numérisation avancés
|
||||
scannerEffect.colorspace=Espace colorimétrique
|
||||
scannerEffect.colorspace.grayscale=Niveaux de gris
|
||||
scannerEffect.colorspace.color=Couleur
|
||||
scannerEffect.border=Bordure (px)
|
||||
scannerEffect.rotate=Rotation de base (degrés)
|
||||
scannerEffect.rotateVariance=Variance de rotation (degrés)
|
||||
scannerEffect.brightness=Luminosité
|
||||
scannerEffect.contrast=Contraste
|
||||
scannerEffect.blur=Flou
|
||||
scannerEffect.noise=Bruit
|
||||
scannerEffect.yellowish=Jaunâtre (simuler du vieux papier)
|
||||
scannerEffect.resolution=Résolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Activer les paramètres de numérisation avancés
|
||||
fakeScan.colorspace=Espace colorimétrique
|
||||
fakeScan.colorspace.grayscale=Niveaux de gris
|
||||
fakeScan.colorspace.color=Couleur
|
||||
fakeScan.border=Bordure (px)
|
||||
fakeScan.rotate=Rotation de base (degrés)
|
||||
fakeScan.rotateVariance=Variance de rotation (degrés)
|
||||
fakeScan.brightness=Luminosité
|
||||
fakeScan.contrast=Contraste
|
||||
fakeScan.blur=Flou
|
||||
fakeScan.noise=Bruit
|
||||
fakeScan.yellowish=Jaunâtre (simuler du vieux papier)
|
||||
fakeScan.resolution=Résolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Ezek a sütik elengedhetetle
|
||||
cookieBanner.preferencesModal.analytics.title=Adatelemzések
|
||||
cookieBanner.preferencesModal.analytics.description=Ezek a sütik segítenek megérteni, hogyan használják eszközeinket, így a közösségünk által leginkább értékelt funkciókra összpontosíthatunk. Nyugodt lehet-a Stirling PDF nem képes és soha nem is fog nyomon követni az Ön által használt dokumentumok tartalmát.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Látszat szkennelés
|
||||
scannerEffect.header=Látszat szkennelés
|
||||
scannerEffect.description=Olyan PDF létrehozása, amely szkenneltnek tűnik
|
||||
scannerEffect.selectPDF=PDF kiválasztása:
|
||||
scannerEffect.quality=Szkennelési minőség
|
||||
scannerEffect.quality.low=Alacsony
|
||||
scannerEffect.quality.medium=Közepes
|
||||
scannerEffect.quality.high=Magas
|
||||
scannerEffect.rotation=Forgatási szög
|
||||
scannerEffect.rotation.none=Nincs
|
||||
scannerEffect.rotation.slight=Enyhe
|
||||
scannerEffect.rotation.moderate=Mérsékelt
|
||||
scannerEffect.rotation.severe=Erős
|
||||
scannerEffect.submit=Látszat szkennelés létrehozása
|
||||
#fakeScan
|
||||
fakeScan.title=Látszat szkennelés
|
||||
fakeScan.header=Látszat szkennelés
|
||||
fakeScan.description=Olyan PDF létrehozása, amely szkenneltnek tűnik
|
||||
fakeScan.selectPDF=PDF kiválasztása:
|
||||
fakeScan.quality=Szkennelési minőség
|
||||
fakeScan.quality.low=Alacsony
|
||||
fakeScan.quality.medium=Közepes
|
||||
fakeScan.quality.high=Magas
|
||||
fakeScan.rotation=Forgatási szög
|
||||
fakeScan.rotation.none=Nincs
|
||||
fakeScan.rotation.slight=Enyhe
|
||||
fakeScan.rotation.moderate=Mérsékelt
|
||||
fakeScan.rotation.severe=Erős
|
||||
fakeScan.submit=Látszat szkennelés létrehozása
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Látszat szkennelés
|
||||
home.scannerEffect.desc=Olyan PDF létrehozása, amely szkenneltnek tűnik
|
||||
scannerEffect.tags=szkennelés,szimuláció,valósághű,konvertálás
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Látszat szkennelés
|
||||
home.fakeScan.desc=Olyan PDF létrehozása, amely szkenneltnek tűnik
|
||||
fakeScan.tags=szkennelés,szimuláció,valósághű,konvertálás
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Haladó szkennelési beállítások engedélyezése
|
||||
scannerEffect.colorspace=Színtér
|
||||
scannerEffect.colorspace.grayscale=Szürkeárnyalatos
|
||||
scannerEffect.colorspace.color=Színes
|
||||
scannerEffect.border=Keret (px)
|
||||
scannerEffect.rotate=Alapforgatás (fok)
|
||||
scannerEffect.rotateVariance=Forgatási változó (fok)
|
||||
scannerEffect.brightness=Fényerő
|
||||
scannerEffect.contrast=Kontraszt
|
||||
scannerEffect.blur=Elmosás
|
||||
scannerEffect.noise=Zaj
|
||||
scannerEffect.yellowish=Sárgás (régi papír szimulálása)
|
||||
scannerEffect.resolution=Felbontás (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Haladó szkennelési beállítások engedélyezése
|
||||
fakeScan.colorspace=Színtér
|
||||
fakeScan.colorspace.grayscale=Szürkeárnyalatos
|
||||
fakeScan.colorspace.color=Színes
|
||||
fakeScan.border=Keret (px)
|
||||
fakeScan.rotate=Alapforgatás (fok)
|
||||
fakeScan.rotateVariance=Forgatási változó (fok)
|
||||
fakeScan.brightness=Fényerő
|
||||
fakeScan.contrast=Kontraszt
|
||||
fakeScan.blur=Elmosás
|
||||
fakeScan.noise=Zaj
|
||||
fakeScan.yellowish=Sárgás (régi papír szimulálása)
|
||||
fakeScan.resolution=Felbontás (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Questi cookie sono essenzial
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=Questi cookie ci aiutano a capire come vengono utilizzati i nostri strumenti, così possiamo concentrarci sullo sviluppo delle funzionalità che la nostra community apprezza di più. Non preoccuparti: Stirling PDF non può e non traccerà mai il contenuto dei documenti con cui lavori.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Falsa scansione
|
||||
scannerEffect.header=Falsa scansione
|
||||
scannerEffect.description=Crea un PDF che sembra scansionato
|
||||
scannerEffect.selectPDF=Seleziona PDF:
|
||||
scannerEffect.quality=Qualità di scansione
|
||||
scannerEffect.quality.low=Bassa
|
||||
scannerEffect.quality.medium=Media
|
||||
scannerEffect.quality.high=Alta
|
||||
scannerEffect.rotation=Angolo di rotazione
|
||||
scannerEffect.rotation.none=Nessuno
|
||||
scannerEffect.rotation.slight=Lieve
|
||||
scannerEffect.rotation.moderate=Moderato
|
||||
scannerEffect.rotation.severe=Severo
|
||||
scannerEffect.submit=Crea una falsa scansione
|
||||
#fakeScan
|
||||
fakeScan.title=Falsa scansione
|
||||
fakeScan.header=Falsa scansione
|
||||
fakeScan.description=Crea un PDF che sembra scansionato
|
||||
fakeScan.selectPDF=Seleziona PDF:
|
||||
fakeScan.quality=Qualità di scansione
|
||||
fakeScan.quality.low=Bassa
|
||||
fakeScan.quality.medium=Media
|
||||
fakeScan.quality.high=Alta
|
||||
fakeScan.rotation=Angolo di rotazione
|
||||
fakeScan.rotation.none=Nessuno
|
||||
fakeScan.rotation.slight=Lieve
|
||||
fakeScan.rotation.moderate=Moderato
|
||||
fakeScan.rotation.severe=Severo
|
||||
fakeScan.submit=Crea una falsa scansione
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Falsa scansione
|
||||
home.scannerEffect.desc=Crea un PDF che sembra scansionato
|
||||
scannerEffect.tags=scansiona, simula, realistico, converti
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Falsa scansione
|
||||
home.fakeScan.desc=Crea un PDF che sembra scansionato
|
||||
fakeScan.tags=scansiona, simula, realistico, converti
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Abilita impostazioni di scansione avanzate
|
||||
scannerEffect.colorspace=Spazio colore
|
||||
scannerEffect.colorspace.grayscale=Scala di grigi
|
||||
scannerEffect.colorspace.color=Colore
|
||||
scannerEffect.border=Bordo (px)
|
||||
scannerEffect.rotate=Rotazione di base (gradi)
|
||||
scannerEffect.rotateVariance=Varianza di rotazione (gradi)
|
||||
scannerEffect.brightness=Luminosità
|
||||
scannerEffect.contrast=Contrasto
|
||||
scannerEffect.blur=Sfocatura
|
||||
scannerEffect.noise=Rumore
|
||||
scannerEffect.yellowish=Giallastro (simula carta vecchia)
|
||||
scannerEffect.resolution=Risoluzione (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Abilita impostazioni di scansione avanzate
|
||||
fakeScan.colorspace=Spazio colore
|
||||
fakeScan.colorspace.grayscale=Scala di grigi
|
||||
fakeScan.colorspace.color=Colore
|
||||
fakeScan.border=Bordo (px)
|
||||
fakeScan.rotate=Rotazione di base (gradi)
|
||||
fakeScan.rotateVariance=Varianza di rotazione (gradi)
|
||||
fakeScan.brightness=Luminosità
|
||||
fakeScan.contrast=Contrasto
|
||||
fakeScan.blur=Sfocatura
|
||||
fakeScan.noise=Rumore
|
||||
fakeScan.yellowish=Giallastro (simula carta vecchia)
|
||||
fakeScan.resolution=Risoluzione (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=これらのCookieはウェ
|
||||
cookieBanner.preferencesModal.analytics.title=分析
|
||||
cookieBanner.preferencesModal.analytics.description=これらのCookieはツールがどのように使用されているかを把握するのに役立ちます。これによりコミュニティが最も重視する機能の開発に集中することができます。ご安心ください。Stirling PDFはお客様が操作するドキュメントの内容を追跡することは決してありません。
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=フェイクスキャン
|
||||
scannerEffect.header=フェイクスキャン
|
||||
scannerEffect.description=スキャンされたように見えるPDFを作成します
|
||||
scannerEffect.selectPDF=PDFを選択:
|
||||
scannerEffect.quality=スキャン品質
|
||||
scannerEffect.quality.low=低
|
||||
scannerEffect.quality.medium=中
|
||||
scannerEffect.quality.high=高
|
||||
scannerEffect.rotation=回転角度
|
||||
scannerEffect.rotation.none=なし
|
||||
scannerEffect.rotation.slight=微少
|
||||
scannerEffect.rotation.moderate=適度
|
||||
scannerEffect.rotation.severe=多大
|
||||
scannerEffect.submit=フェイクスキャンの生成
|
||||
#fakeScan
|
||||
fakeScan.title=フェイクスキャン
|
||||
fakeScan.header=フェイクスキャン
|
||||
fakeScan.description=スキャンされたように見えるPDFを作成します
|
||||
fakeScan.selectPDF=PDFを選択:
|
||||
fakeScan.quality=スキャン品質
|
||||
fakeScan.quality.low=低
|
||||
fakeScan.quality.medium=中
|
||||
fakeScan.quality.high=高
|
||||
fakeScan.rotation=回転角度
|
||||
fakeScan.rotation.none=なし
|
||||
fakeScan.rotation.slight=微少
|
||||
fakeScan.rotation.moderate=適度
|
||||
fakeScan.rotation.severe=多大
|
||||
fakeScan.submit=フェイクスキャンの生成
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=フェイクスキャン
|
||||
home.scannerEffect.desc=スキャンされたように見えるPDFを作成します
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=フェイクスキャン
|
||||
home.fakeScan.desc=スキャンされたように見えるPDFを作成します
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=高度なスキャン設定を有効にする
|
||||
scannerEffect.colorspace=色空間
|
||||
scannerEffect.colorspace.grayscale=グレースケール
|
||||
scannerEffect.colorspace.color=カラー
|
||||
scannerEffect.border=縁 (px)
|
||||
scannerEffect.rotate=ベースの回転(度)
|
||||
scannerEffect.rotateVariance=回転分散(度)
|
||||
scannerEffect.brightness=輝度
|
||||
scannerEffect.contrast=コントラスト
|
||||
scannerEffect.blur=ぼかし
|
||||
scannerEffect.noise=ノイズ
|
||||
scannerEffect.yellowish=黄色がかった(古い紙をシミュレート)
|
||||
scannerEffect.resolution=解像度 (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=高度なスキャン設定を有効にする
|
||||
fakeScan.colorspace=色空間
|
||||
fakeScan.colorspace.grayscale=グレースケール
|
||||
fakeScan.colorspace.color=カラー
|
||||
fakeScan.border=縁 (px)
|
||||
fakeScan.rotate=ベースの回転(度)
|
||||
fakeScan.rotateVariance=回転分散(度)
|
||||
fakeScan.brightness=輝度
|
||||
fakeScan.contrast=コントラスト
|
||||
fakeScan.blur=ぼかし
|
||||
fakeScan.noise=ノイズ
|
||||
fakeScan.yellowish=黄色がかった(古い紙をシミュレート)
|
||||
fakeScan.resolution=解像度 (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=വെബ്സൈറ്റ
|
||||
cookieBanner.preferencesModal.analytics.title=അനലിറ്റിക്സ്
|
||||
cookieBanner.preferencesModal.analytics.description=ഞങ്ങളുടെ ടൂളുകൾ എങ്ങനെ ഉപയോഗിക്കുന്നുവെന്ന് മനസ്സിലാക്കാൻ ഈ കുക്കികൾ ഞങ്ങളെ സഹായിക്കുന്നു, അതിനാൽ ഞങ്ങളുടെ കമ്മ്യൂണിറ്റി ഏറ്റവും കൂടുതൽ വിലമതിക്കുന്ന ഫീച്ചറുകൾ നിർമ്മിക്കുന്നതിൽ ഞങ്ങൾക്ക് ശ്രദ്ധ കേന്ദ്രീകരിക്കാൻ കഴിയും. ഉറപ്പാക്കുക—സ്റ്റെർലിംഗ് PDF-ന് നിങ്ങൾ പ്രവർത്തിക്കുന്ന പ്രമാണങ്ങളുടെ ഉള്ളടക്കം ട്രാക്ക് ചെയ്യാൻ കഴിയില്ല, ഒരിക്കലും കഴിയില്ല.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Estes cookies são essenciai
|
||||
cookieBanner.preferencesModal.analytics.title=Cookies Analíticos
|
||||
cookieBanner.preferencesModal.analytics.description=Estes cookies nos ajudam a entender como nossas ferramentas estão sendo utilizadas, para que possamos nos concentrar na construção dos recursos que nossa comunidade mais valoriza. Fique tranquilo: o Stirling PDF não pode e nunca rastreará o conteúdo dos documentos com os quais você manipula.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Поддельное сканирование
|
||||
scannerEffect.header=Поддельное сканирование
|
||||
scannerEffect.description=Создайте PDF-файл, который выглядит так, как будто он был отсканирован
|
||||
scannerEffect.selectPDF=Выбрать PDF:
|
||||
scannerEffect.quality=Качество сканирования
|
||||
scannerEffect.quality.low=Низкое
|
||||
scannerEffect.quality.medium=Среднее
|
||||
scannerEffect.quality.high=Хорошее
|
||||
scannerEffect.rotation=Угол поворота
|
||||
scannerEffect.rotation.none=Нет
|
||||
scannerEffect.rotation.slight=Незначительный
|
||||
scannerEffect.rotation.moderate=Умеренный
|
||||
scannerEffect.rotation.severe=Сильный
|
||||
scannerEffect.submit=Создать поддельное сканирование
|
||||
#fakeScan
|
||||
fakeScan.title=Поддельное сканирование
|
||||
fakeScan.header=Поддельное сканирование
|
||||
fakeScan.description=Создайте PDF-файл, который выглядит так, как будто он был отсканирован
|
||||
fakeScan.selectPDF=Выбрать PDF:
|
||||
fakeScan.quality=Качество сканирования
|
||||
fakeScan.quality.low=Низкое
|
||||
fakeScan.quality.medium=Среднее
|
||||
fakeScan.quality.high=Хорошее
|
||||
fakeScan.rotation=Угол поворота
|
||||
fakeScan.rotation.none=Нет
|
||||
fakeScan.rotation.slight=Незначительный
|
||||
fakeScan.rotation.moderate=Умеренный
|
||||
fakeScan.rotation.severe=Сильный
|
||||
fakeScan.submit=Создать поддельное сканирование
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Поддельное сканирование
|
||||
home.scannerEffect.desc=Создайте PDF-файл, который выглядит так, как будто он был отсканирован
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Поддельное сканирование
|
||||
home.fakeScan.desc=Создайте PDF-файл, который выглядит так, как будто он был отсканирован
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Включите расширенные параметры сканирования
|
||||
scannerEffect.colorspace=Цветовое пространство
|
||||
scannerEffect.colorspace.grayscale=Оттенки серого
|
||||
scannerEffect.colorspace.color=Цветное
|
||||
scannerEffect.border=Рамка (px)
|
||||
scannerEffect.rotate=Базовый наклон (degrees)
|
||||
scannerEffect.rotateVariance=Скорость вращения (degrees)
|
||||
scannerEffect.brightness=Яркость
|
||||
scannerEffect.contrast=Контраст
|
||||
scannerEffect.blur=Размытие
|
||||
scannerEffect.noise=Шум
|
||||
scannerEffect.yellowish=Желтоватый оттенок (имитация старой бумаги)
|
||||
scannerEffect.resolution=Разрешение (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Включите расширенные параметры сканирования
|
||||
fakeScan.colorspace=Цветовое пространство
|
||||
fakeScan.colorspace.grayscale=Оттенки серого
|
||||
fakeScan.colorspace.color=Цветное
|
||||
fakeScan.border=Рамка (px)
|
||||
fakeScan.rotate=Базовый наклон (degrees)
|
||||
fakeScan.rotateVariance=Скорость вращения (degrees)
|
||||
fakeScan.brightness=Яркость
|
||||
fakeScan.contrast=Контраст
|
||||
fakeScan.blur=Размытие
|
||||
fakeScan.noise=Шум
|
||||
fakeScan.yellowish=Желтоватый оттенок (имитация старой бумаги)
|
||||
fakeScan.resolution=Разрешение (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Ovi kolačići su neophodni
|
||||
cookieBanner.preferencesModal.analytics.title=Analitika
|
||||
cookieBanner.preferencesModal.analytics.description=Ovi kolačići nam pomažu da razumemo kako se naši alati koriste, kako bismo mogli da se fokusiramo na razvoj funkcija koje naša zajednica najviše ceni. Budite sigurni — Stirling PDF ne može i nikada neće pratiti sadržaj dokumenata sa kojima radite.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Lažno skeniranje
|
||||
scannerEffect.header=Lažno skeniranje
|
||||
scannerEffect.description=Kreiraj PDF koji izgleda kao da je skeniran
|
||||
scannerEffect.selectPDF=Izaberi PDF:
|
||||
scannerEffect.quality=Kvalitet skeniranja:
|
||||
scannerEffect.quality.low=Nizak
|
||||
scannerEffect.quality.medium=Srednji
|
||||
scannerEffect.quality.high=Visok
|
||||
scannerEffect.rotation=Ugao rotiranja:
|
||||
scannerEffect.rotation.none=Nijedno
|
||||
scannerEffect.rotation.slight=Blago
|
||||
scannerEffect.rotation.moderate=Umereno
|
||||
scannerEffect.rotation.severe=Značajno
|
||||
scannerEffect.submit=Kreiraj lažno skeniranje
|
||||
#fakeScan
|
||||
fakeScan.title=Lažno skeniranje
|
||||
fakeScan.header=Lažno skeniranje
|
||||
fakeScan.description=Kreiraj PDF koji izgleda kao da je skeniran
|
||||
fakeScan.selectPDF=Izaberi PDF:
|
||||
fakeScan.quality=Kvalitet skeniranja:
|
||||
fakeScan.quality.low=Nizak
|
||||
fakeScan.quality.medium=Srednji
|
||||
fakeScan.quality.high=Visok
|
||||
fakeScan.rotation=Ugao rotiranja:
|
||||
fakeScan.rotation.none=Nijedno
|
||||
fakeScan.rotation.slight=Blago
|
||||
fakeScan.rotation.moderate=Umereno
|
||||
fakeScan.rotation.severe=Značajno
|
||||
fakeScan.submit=Kreiraj lažno skeniranje
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Lažno skeniranje
|
||||
home.scannerEffect.desc=Kreiraj PDF koji izgleda kao da je skeniran
|
||||
scannerEffect.tags=sken,simuliraj,realistično,konvertuj
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Lažno skeniranje
|
||||
home.fakeScan.desc=Kreiraj PDF koji izgleda kao da je skeniran
|
||||
fakeScan.tags=sken,simuliraj,realistično,konvertuj
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Omogući naprednja podešavanja za skeniranje
|
||||
scannerEffect.colorspace=Režim boja:
|
||||
scannerEffect.colorspace.grayscale=Monohromatski
|
||||
scannerEffect.colorspace.color=Kolor
|
||||
scannerEffect.border=Ivica (px)
|
||||
scannerEffect.rotate=Osnovni ugao rotacije (stepeni)
|
||||
scannerEffect.rotateVariance=Varijacija rotacije (stepeni)
|
||||
scannerEffect.brightness=Osvetljenje
|
||||
scannerEffect.contrast=Kontrast
|
||||
scannerEffect.blur=Zamućenje
|
||||
scannerEffect.noise=Buka
|
||||
scannerEffect.yellowish=Žutilo (simulacija starog papira)
|
||||
scannerEffect.resolution=Rezolucija (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Omogući naprednja podešavanja za skeniranje
|
||||
fakeScan.colorspace=Režim boja:
|
||||
fakeScan.colorspace.grayscale=Monohromatski
|
||||
fakeScan.colorspace.color=Kolor
|
||||
fakeScan.border=Ivica (px)
|
||||
fakeScan.rotate=Osnovni ugao rotacije (stepeni)
|
||||
fakeScan.rotateVariance=Varijacija rotacije (stepeni)
|
||||
fakeScan.brightness=Osvetljenje
|
||||
fakeScan.contrast=Kontrast
|
||||
fakeScan.blur=Zamućenje
|
||||
fakeScan.noise=Buka
|
||||
fakeScan.yellowish=Žutilo (simulacija starog papira)
|
||||
fakeScan.resolution=Rezolucija (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=Bu çerezler, web sitesinin
|
||||
cookieBanner.preferencesModal.analytics.title=Analitik
|
||||
cookieBanner.preferencesModal.analytics.description=Bu çerezler, araçlarımızın nasıl kullanıldığını anlamamıza yardımcı olur, böylece topluluğumuzun en çok değer verdiği özellikleri geliştirmeye odaklanabiliriz. İçiniz rahat olsun — Stirling PDF, belgelerinizin içeriğini asla takip etmez ve etmeyecektir.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Sahte Tarama
|
||||
home.scannerEffect.desc=Tarama yapılmış gibi görünen bir PDF oluşturun
|
||||
scannerEffect.tags=tarama,simülasyon,gerçekçi,dönüştürme
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Sahte Tarama
|
||||
home.fakeScan.desc=Tarama yapılmış gibi görünen bir PDF oluşturun
|
||||
fakeScan.tags=tarama,simülasyon,gerçekçi,dönüştürme
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Gelişmiş Tarama Ayarlarını Etkinleştir
|
||||
scannerEffect.colorspace=Renk Uzayı
|
||||
scannerEffect.colorspace.grayscale=Gri Tonlama
|
||||
scannerEffect.colorspace.color=Renkli
|
||||
scannerEffect.border=Kenar Boşluğu (piksel)
|
||||
scannerEffect.rotate=Temel Döndürme (derece)
|
||||
scannerEffect.rotateVariance=Döndürme Varyansı (derece)
|
||||
scannerEffect.brightness=Parlaklık
|
||||
scannerEffect.contrast=Kontrast
|
||||
scannerEffect.blur=Bulanıklık
|
||||
scannerEffect.noise=Gürültü
|
||||
scannerEffect.yellowish=Sarartı (eski kağıt efekti)
|
||||
scannerEffect.resolution=Çözünürlük (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Gelişmiş Tarama Ayarlarını Etkinleştir
|
||||
fakeScan.colorspace=Renk Uzayı
|
||||
fakeScan.colorspace.grayscale=Gri Tonlama
|
||||
fakeScan.colorspace.color=Renkli
|
||||
fakeScan.border=Kenar Boşluğu (piksel)
|
||||
fakeScan.rotate=Temel Döndürme (derece)
|
||||
fakeScan.rotateVariance=Döndürme Varyansı (derece)
|
||||
fakeScan.brightness=Parlaklık
|
||||
fakeScan.contrast=Kontrast
|
||||
fakeScan.blur=Bulanıklık
|
||||
fakeScan.noise=Gürültü
|
||||
fakeScan.yellowish=Sarartı (eski kağıt efekti)
|
||||
fakeScan.resolution=Çözünürlük (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=这些Cookie对网站基础
|
||||
cookieBanner.preferencesModal.analytics.title=分析统计
|
||||
cookieBanner.preferencesModal.analytics.description=这些Cookie帮助我们分析工具使用情况,以便聚焦开发用户最需要的功能。再次强调:Stirling PDF绝不会追踪您处理的文档内容。
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=模拟扫描
|
||||
scannerEffect.header=模拟扫描
|
||||
scannerEffect.description=创建一个看起来像扫描的 PDF
|
||||
scannerEffect.selectPDF=选择 PDF:
|
||||
scannerEffect.quality=扫描质量
|
||||
scannerEffect.quality.low=低
|
||||
scannerEffect.quality.medium=中
|
||||
scannerEffect.quality.high=高
|
||||
scannerEffect.rotation=旋转角度
|
||||
scannerEffect.rotation.none=无
|
||||
scannerEffect.rotation.slight=轻微
|
||||
scannerEffect.rotation.moderate=中等
|
||||
scannerEffect.rotation.severe=严重
|
||||
scannerEffect.submit=创建模拟扫描
|
||||
#fakeScan
|
||||
fakeScan.title=模拟扫描
|
||||
fakeScan.header=模拟扫描
|
||||
fakeScan.description=创建一个看起来像扫描的 PDF
|
||||
fakeScan.selectPDF=选择 PDF:
|
||||
fakeScan.quality=扫描质量
|
||||
fakeScan.quality.low=低
|
||||
fakeScan.quality.medium=中
|
||||
fakeScan.quality.high=高
|
||||
fakeScan.rotation=旋转角度
|
||||
fakeScan.rotation.none=无
|
||||
fakeScan.rotation.slight=轻微
|
||||
fakeScan.rotation.moderate=中等
|
||||
fakeScan.rotation.severe=严重
|
||||
fakeScan.submit=创建模拟扫描
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=模拟扫描
|
||||
home.scannerEffect.desc=创建一个看起来像扫描的 PDF
|
||||
scannerEffect.tags=扫描、模拟、真实、转换
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=模拟扫描
|
||||
home.fakeScan.desc=创建一个看起来像扫描的 PDF
|
||||
fakeScan.tags=扫描、模拟、真实、转换
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=启用高级扫描设置
|
||||
scannerEffect.colorspace=色彩空间
|
||||
scannerEffect.colorspace.grayscale=灰度
|
||||
scannerEffect.colorspace.color=颜色
|
||||
scannerEffect.border=边框(像素)
|
||||
scannerEffect.rotate=基础旋转(度)
|
||||
scannerEffect.rotateVariance=旋转偏差(度)
|
||||
scannerEffect.brightness=亮度
|
||||
scannerEffect.contrast=对比度
|
||||
scannerEffect.blur=模糊
|
||||
scannerEffect.noise=噪点
|
||||
scannerEffect.yellowish=泛黄效果(模拟旧纸张)
|
||||
scannerEffect.resolution=分辨率(DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=启用高级扫描设置
|
||||
fakeScan.colorspace=色彩空间
|
||||
fakeScan.colorspace.grayscale=灰度
|
||||
fakeScan.colorspace.color=颜色
|
||||
fakeScan.border=边框(像素)
|
||||
fakeScan.rotate=基础旋转(度)
|
||||
fakeScan.rotateVariance=旋转偏差(度)
|
||||
fakeScan.brightness=亮度
|
||||
fakeScan.contrast=对比度
|
||||
fakeScan.blur=模糊
|
||||
fakeScan.noise=噪点
|
||||
fakeScan.yellowish=泛黄效果(模拟旧纸张)
|
||||
fakeScan.resolution=分辨率(DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -1811,41 +1811,41 @@ cookieBanner.preferencesModal.necessary.description=這些 Cookies 對網站正
|
||||
cookieBanner.preferencesModal.analytics.title=分析 Cookies
|
||||
cookieBanner.preferencesModal.analytics.description=這些 Cookies 幫助我們分析您如何使用我們的工具,好讓我們能專注在建構社群最重視的功能。儘管放心—— Stirling PDF 不會且永不追蹤您的文件
|
||||
|
||||
#scannerEffect
|
||||
scannerEffect.title=Scanner Effect
|
||||
scannerEffect.header=Scanner Effect
|
||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
||||
scannerEffect.selectPDF=Select PDF:
|
||||
scannerEffect.quality=Scan Quality
|
||||
scannerEffect.quality.low=Low
|
||||
scannerEffect.quality.medium=Medium
|
||||
scannerEffect.quality.high=High
|
||||
scannerEffect.rotation=Rotation Angle
|
||||
scannerEffect.rotation.none=None
|
||||
scannerEffect.rotation.slight=Slight
|
||||
scannerEffect.rotation.moderate=Moderate
|
||||
scannerEffect.rotation.severe=Severe
|
||||
scannerEffect.submit=Create Scanner Effect
|
||||
#fakeScan
|
||||
fakeScan.title=Fake Scan
|
||||
fakeScan.header=Fake Scan
|
||||
fakeScan.description=Create a PDF that looks like it was scanned
|
||||
fakeScan.selectPDF=Select PDF:
|
||||
fakeScan.quality=Scan Quality
|
||||
fakeScan.quality.low=Low
|
||||
fakeScan.quality.medium=Medium
|
||||
fakeScan.quality.high=High
|
||||
fakeScan.rotation=Rotation Angle
|
||||
fakeScan.rotation.none=None
|
||||
fakeScan.rotation.slight=Slight
|
||||
fakeScan.rotation.moderate=Moderate
|
||||
fakeScan.rotation.severe=Severe
|
||||
fakeScan.submit=Create Fake Scan
|
||||
|
||||
#home.scannerEffect
|
||||
home.scannerEffect.title=Scanner Effect
|
||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||
scannerEffect.tags=scan,simulate,realistic,convert
|
||||
#home.fakeScan
|
||||
home.fakeScan.title=Fake Scan
|
||||
home.fakeScan.desc=Create a PDF that looks like it was scanned
|
||||
fakeScan.tags=scan,simulate,realistic,convert
|
||||
|
||||
# ScannerEffect advanced settings (frontend)
|
||||
scannerEffect.advancedSettings=Enable Advanced Scan Settings
|
||||
scannerEffect.colorspace=Colorspace
|
||||
scannerEffect.colorspace.grayscale=Grayscale
|
||||
scannerEffect.colorspace.color=Color
|
||||
scannerEffect.border=Border (px)
|
||||
scannerEffect.rotate=Base Rotation (degrees)
|
||||
scannerEffect.rotateVariance=Rotation Variance (degrees)
|
||||
scannerEffect.brightness=Brightness
|
||||
scannerEffect.contrast=Contrast
|
||||
scannerEffect.blur=Blur
|
||||
scannerEffect.noise=Noise
|
||||
scannerEffect.yellowish=Yellowish (simulate old paper)
|
||||
scannerEffect.resolution=Resolution (DPI)
|
||||
# FakeScan advanced settings (frontend)
|
||||
fakeScan.advancedSettings=Enable Advanced Scan Settings
|
||||
fakeScan.colorspace=Colorspace
|
||||
fakeScan.colorspace.grayscale=Grayscale
|
||||
fakeScan.colorspace.color=Color
|
||||
fakeScan.border=Border (px)
|
||||
fakeScan.rotate=Base Rotation (degrees)
|
||||
fakeScan.rotateVariance=Rotation Variance (degrees)
|
||||
fakeScan.brightness=Brightness
|
||||
fakeScan.contrast=Contrast
|
||||
fakeScan.blur=Blur
|
||||
fakeScan.noise=Noise
|
||||
fakeScan.yellowish=Yellowish (simulate old paper)
|
||||
fakeScan.resolution=Resolution (DPI)
|
||||
|
||||
|
||||
# Table of Contents Feature
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntry :: navbarEntry('show-javascript', 'javascript', 'home.showJS.title', 'home.showJS.desc', 'showJS.tags', 'advance')}">
|
||||
</div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry('scanner-effect', 'scanner', 'scannerEffect.title', 'scannerEffect.description', 'scannerEffect.tags', 'advance')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry('fake-scan', 'scanner', 'fakeScan.title', 'fakeScan.description', 'fakeScan.tags', 'advance')}"></div>
|
||||
<div
|
||||
th:replace="~{fragments/navbarEntryCustom :: navbarEntry('split-by-size-or-count', '/images/split-size.svg#icon-split-size', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags', 'advance')}">
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry('add-attachments', 'attachment', 'home.attachments.title', 'home.attachments.desc', 'attachments.tags', 'other')}">
|
||||
</div>
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry('scanner-effect', 'scanner', 'scannerEffect.title', 'scannerEffect.description', 'scannerEffect.tags', 'advance')}">
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry('fake-scan', 'scanner', 'fakeScan.title', 'fakeScan.description', 'fakeScan.tags', 'advance')}">
|
||||
</div>
|
||||
<div class="newfeature"
|
||||
th:insert="~{fragments/navbarEntry :: navbarEntry('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPDFs.tags', 'advance')}">
|
||||
|
||||
+26
-26
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{scannerEffect.title}, header=#{scannerEffect.header})}"></th:block>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{fakeScan.title}, header=#{fakeScan.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -15,81 +15,81 @@
|
||||
<div class="col-md-6 bg-card">
|
||||
<div class="tool-header">
|
||||
<span class="material-symbols-rounded tool-header-icon advance">scanner</span>
|
||||
<span class="tool-header-text" th:text="#{scannerEffect.title}"></span>
|
||||
<span class="tool-header-text" th:text="#{fakeScan.title}"></span>
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" id="uploadForm" th:action="@{'/api/v1/misc/scanner-effect'}">
|
||||
<form method="post" enctype="multipart/form-data" id="uploadForm" th:action="@{'/api/v1/misc/fake-scan'}">
|
||||
<input type="hidden" name="advancedEnabled" id="advancedEnabled" value="false">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||
<br>
|
||||
<div class="mb-3">
|
||||
<label for="quality" class="form-label" th:text="#{scannerEffect.quality}"></label>
|
||||
<label for="quality" class="form-label" th:text="#{fakeScan.quality}"></label>
|
||||
<select class="form-select" id="quality" name="quality">
|
||||
<option value="low" th:text="#{scannerEffect.quality.low}"></option>
|
||||
<option value="medium" th:text="#{scannerEffect.quality.medium}"></option>
|
||||
<option value="high" th:text="#{scannerEffect.quality.high}" selected></option>
|
||||
<option value="low" th:text="#{fakeScan.quality.low}"></option>
|
||||
<option value="medium" th:text="#{fakeScan.quality.medium}"></option>
|
||||
<option value="high" th:text="#{fakeScan.quality.high}" selected></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rotation" class="form-label" th:text="#{scannerEffect.rotation}"></label>
|
||||
<label for="rotation" class="form-label" th:text="#{fakeScan.rotation}"></label>
|
||||
<select class="form-select" id="rotation" name="rotation">
|
||||
<option value="none" th:text="#{scannerEffect.rotation.none}"></option>
|
||||
<option value="slight" th:text="#{scannerEffect.rotation.slight}" selected></option>
|
||||
<option value="moderate" th:text="#{scannerEffect.rotation.moderate}"></option>
|
||||
<option value="severe" th:text="#{scannerEffect.rotation.severe}"></option>
|
||||
<option value="none" th:text="#{fakeScan.rotation.none}"></option>
|
||||
<option value="slight" th:text="#{fakeScan.rotation.slight}" selected></option>
|
||||
<option value="moderate" th:text="#{fakeScan.rotation.moderate}"></option>
|
||||
<option value="severe" th:text="#{fakeScan.rotation.severe}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="advancedSettingsToggle">
|
||||
<label class="form-check-label" for="advancedSettingsToggle" th:text="#{scannerEffect.advancedSettings}"></label>
|
||||
<label class="form-check-label" for="advancedSettingsToggle" th:text="#{fakeScan.advancedSettings}"></label>
|
||||
</div>
|
||||
<div id="advancedSettings" style="display:none; border:1px solid #eee; padding:1em; border-radius:8px; margin-bottom:1em;">
|
||||
<div class="mb-3">
|
||||
<label for="colorspace" class="form-label" th:text="#{scannerEffect.colorspace}"></label>
|
||||
<label for="colorspace" class="form-label" th:text="#{fakeScan.colorspace}"></label>
|
||||
<select class="form-select" id="colorspace" name="colorspace">
|
||||
<option value="grayscale" th:text="#{scannerEffect.colorspace.grayscale}" selected></option>
|
||||
<option value="color" th:text="#{scannerEffect.colorspace.color}"></option>
|
||||
<option value="grayscale" th:text="#{fakeScan.colorspace.grayscale}" selected></option>
|
||||
<option value="color" th:text="#{fakeScan.colorspace.color}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="border" class="form-label" th:text="#{scannerEffect.border}"></label>
|
||||
<label for="border" class="form-label" th:text="#{fakeScan.border}"></label>
|
||||
<input type="number" class="form-control" id="border" name="border" min="0" max="100" value="20">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rotate" class="form-label" th:text="#{scannerEffect.rotate}"></label>
|
||||
<label for="rotate" class="form-label" th:text="#{fakeScan.rotate}"></label>
|
||||
<input type="number" class="form-control" id="rotate" name="rotate" min="-15" max="15" value="0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rotateVariance" class="form-label" th:text="#{scannerEffect.rotateVariance}"></label>
|
||||
<label for="rotateVariance" class="form-label" th:text="#{fakeScan.rotateVariance}"></label>
|
||||
<input type="number" class="form-control" id="rotateVariance" name="rotateVariance" min="0" max="10" value="2">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="brightness" class="form-label" th:text="#{scannerEffect.brightness}"></label>
|
||||
<label for="brightness" class="form-label" th:text="#{fakeScan.brightness}"></label>
|
||||
<input type="range" class="form-range" id="brightness" name="brightness" min="0.5" max="1.5" step="0.01" value="1.0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="contrast" class="form-label" th:text="#{scannerEffect.contrast}"></label>
|
||||
<label for="contrast" class="form-label" th:text="#{fakeScan.contrast}"></label>
|
||||
<input type="range" class="form-range" id="contrast" name="contrast" min="0.5" max="1.5" step="0.01" value="1.0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="blur" class="form-label" th:text="#{scannerEffect.blur}"></label>
|
||||
<label for="blur" class="form-label" th:text="#{fakeScan.blur}"></label>
|
||||
<input type="range" class="form-range" id="blur" name="blur" min="0" max="5" step="0.1" value="1.0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="noise" class="form-label" th:text="#{scannerEffect.noise}"></label>
|
||||
<label for="noise" class="form-label" th:text="#{fakeScan.noise}"></label>
|
||||
<input type="range" class="form-range" id="noise" name="noise" min="0" max="20" step="0.1" value="8.0">
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="yellowish" name="yellowish">
|
||||
<label class="form-check-label" for="yellowish" th:text="#{scannerEffect.yellowish}"></label>
|
||||
<label class="form-check-label" for="yellowish" th:text="#{fakeScan.yellowish}"></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="resolution" class="form-label" th:text="#{scannerEffect.resolution}"></label>
|
||||
<label for="resolution" class="form-label" th:text="#{fakeScan.resolution}"></label>
|
||||
<input type="number" class="form-control" id="resolution" name="resolution" min="72" max="600" value="300">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 text-left">
|
||||
<button type="submit" class="btn btn-primary" th:text="#{scannerEffect.submit}" id="submitBtn"></button>
|
||||
<button type="submit" class="btn btn-primary" th:text="#{fakeScan.submit}" id="submitBtn"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
# Docker Setup for Stirling-PDF
|
||||
|
||||
This directory contains the organized Docker configurations for the split frontend/backend architecture.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
docker/
|
||||
├── backend/ # Backend Docker files
|
||||
│ ├── Dockerfile # Standard backend
|
||||
│ ├── Dockerfile.ultra-lite # Minimal backend
|
||||
│ └── Dockerfile.fat # Full-featured backend
|
||||
├── frontend/ # Frontend Docker files
|
||||
│ ├── Dockerfile # React/Vite frontend with nginx
|
||||
│ ├── nginx.conf # Nginx configuration
|
||||
│ └── entrypoint.sh # Dynamic backend URL setup
|
||||
└── compose/ # Docker Compose files
|
||||
├── docker-compose.yml # Standard setup
|
||||
├── docker-compose.ultra-lite.yml # Ultra-lite setup
|
||||
└── docker-compose.fat.yml # Full-featured setup
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Separate Containers (Recommended)
|
||||
|
||||
From the project root directory:
|
||||
|
||||
```bash
|
||||
# Standard version
|
||||
docker-compose -f docker/compose/docker-compose.yml up --build
|
||||
|
||||
# Ultra-lite version
|
||||
docker-compose -f docker/compose/docker-compose.ultra-lite.yml up --build
|
||||
|
||||
# Fat version
|
||||
docker-compose -f docker/compose/docker-compose.fat.yml up --build
|
||||
```
|
||||
|
||||
|
||||
## Access Points
|
||||
|
||||
- **Frontend**: http://localhost:3000
|
||||
- **Backend API (debugging)**: http://localhost:8080 (TODO: Remove in production)
|
||||
- **Backend API (via frontend)**: http://localhost:3000/api/*
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Backend URL**: Set `VITE_API_BASE_URL` environment variable for custom backend locations
|
||||
- **Custom Ports**: Modify port mappings in docker-compose files
|
||||
- **Memory Limits**: Adjust memory limits per variant (2G ultra-lite, 4G standard, 6G fat)
|
||||
|
||||
## Development vs Production
|
||||
|
||||
- **Development**: Keep backend port 8080 exposed for debugging
|
||||
- **Production**: Remove backend port exposure, use only frontend proxy
|
||||
@@ -1,3 +1,27 @@
|
||||
# Backend Dockerfile - Java Spring Boot with all dependencies and build stage
|
||||
# Build the application
|
||||
FROM gradle:8.14-jdk21 AS build
|
||||
|
||||
COPY build.gradle .
|
||||
COPY settings.gradle .
|
||||
COPY gradlew .
|
||||
COPY gradle gradle/
|
||||
COPY app/core/build.gradle core/.
|
||||
COPY app/common/build.gradle common/.
|
||||
COPY app/proprietary/build.gradle proprietary/.
|
||||
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the entire project to the working directory
|
||||
COPY . .
|
||||
|
||||
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
|
||||
|
||||
@@ -5,12 +29,13 @@ FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be02
|
||||
COPY scripts /scripts
|
||||
COPY pipeline /pipeline
|
||||
COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||
COPY app/core/build/libs/*.jar app.jar
|
||||
# first /app directory is for the build stage, second is for the final image
|
||||
COPY --from=build /app/app/core/build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
LABEL org.opencontainers.image.title="Stirling-PDF"
|
||||
LABEL org.opencontainers.image.description="A powerful locally hosted web-based PDF manipulation tool supporting 50+ operations including merging, splitting, conversion, OCR, watermarking, and more."
|
||||
LABEL org.opencontainers.image.title="Stirling-PDF Backend"
|
||||
LABEL org.opencontainers.image.description="Backend service for Stirling-PDF - Java Spring Boot with PDF processing capabilities"
|
||||
LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
LABEL org.opencontainers.image.vendor="Stirling-Tools"
|
||||
@@ -19,10 +44,10 @@ LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
|
||||
LABEL maintainer="Stirling-Tools"
|
||||
LABEL org.opencontainers.image.authors="Stirling-Tools"
|
||||
LABEL org.opencontainers.image.version="${VERSION_TAG}"
|
||||
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
|
||||
LABEL org.opencontainers.image.keywords="PDF, manipulation, backend, API, Spring Boot"
|
||||
|
||||
# Set Environment Variables
|
||||
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||
ENV DISABLE_ADDITIONAL_FEATURES=false \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||
JAVA_CUSTOM_OPTS="" \
|
||||
@@ -39,8 +64,7 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||
TEMP=/tmp/stirling-pdf \
|
||||
TMP=/tmp/stirling-pdf
|
||||
|
||||
|
||||
# JDK for app
|
||||
# JDK for app and all dependencies
|
||||
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
@@ -63,21 +87,19 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced features)
|
||||
unpaper \
|
||||
tesseract-ocr-data-eng \
|
||||
tesseract-ocr-data-chi_sim \
|
||||
tesseract-ocr-data-deu \
|
||||
tesseract-ocr-data-fra \
|
||||
tesseract-ocr-data-por \
|
||||
unpaper \
|
||||
ocrmypdf \
|
||||
# CV
|
||||
py3-opencv \
|
||||
python3 \
|
||||
ocrmypdf \
|
||||
py3-pip \
|
||||
py3-pillow@testing \
|
||||
py3-pdf2image@testing \
|
||||
# URW Base 35 fonts for better PDF rendering
|
||||
font-urw-base35 && \
|
||||
py3-pdf2image@testing && \
|
||||
python3 -m venv /opt/venv && \
|
||||
/opt/venv/bin/pip install --upgrade pip setuptools && \
|
||||
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||
@@ -86,8 +108,6 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
||||
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
|
||||
# Configure URW Base 35 fonts
|
||||
ln -s /usr/share/fontconfig/conf.avail/69-urw-*.conf /etc/fonts/conf.d/ && \
|
||||
fc-cache -f -v && \
|
||||
chmod +x /scripts/* && \
|
||||
chmod +x /scripts/init.sh && \
|
||||
@@ -100,4 +120,4 @@ EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
|
||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
|
||||
@@ -1,3 +1,4 @@
|
||||
# Backend fat Dockerfile - Java Spring Boot with all dependencies and build stage
|
||||
# Build the application
|
||||
FROM gradle:8.14-jdk21 AS build
|
||||
|
||||
@@ -53,7 +54,6 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||
TEMP=/tmp/stirling-pdf \
|
||||
TMP=/tmp/stirling-pdf
|
||||
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
@@ -77,17 +77,17 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||
unpaper \
|
||||
tesseract-ocr-data-eng \
|
||||
tesseract-ocr-data-chi_sim \
|
||||
tesseract-ocr-data-deu \
|
||||
tesseract-ocr-data-fra \
|
||||
tesseract-ocr-data-por \
|
||||
unpaper \
|
||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine font-urw-base35 \
|
||||
ocrmypdf \
|
||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \
|
||||
# CV
|
||||
py3-opencv \
|
||||
python3 \
|
||||
ocrmypdf \
|
||||
py3-pip \
|
||||
py3-pillow@testing \
|
||||
py3-pdf2image@testing && \
|
||||
@@ -99,8 +99,6 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
||||
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
|
||||
# Configure URW Base 35 fonts
|
||||
ln -s /usr/share/fontconfig/conf.avail/69-urw-*.conf /etc/fonts/conf.d/ && \
|
||||
fc-cache -f -v && \
|
||||
chmod +x /scripts/* && \
|
||||
chmod +x /scripts/init.sh && \
|
||||
@@ -112,4 +110,4 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a
|
||||
EXPOSE 8080/tcp
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
|
||||
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
|
||||
@@ -1,4 +1,28 @@
|
||||
# use alpine
|
||||
# Backend ultra-lite Dockerfile - Java Spring Boot with minimal dependencies and build stage
|
||||
# Build the application
|
||||
FROM gradle:8.14-jdk21 AS build
|
||||
|
||||
COPY build.gradle .
|
||||
COPY settings.gradle .
|
||||
COPY gradlew .
|
||||
COPY gradle gradle/
|
||||
COPY app/core/build.gradle core/.
|
||||
COPY app/common/build.gradle common/.
|
||||
COPY app/proprietary/build.gradle proprietary/.
|
||||
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the entire project to the working directory
|
||||
COPY . .
|
||||
|
||||
# Build the application with DISABLE_ADDITIONAL_FEATURES=true
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=true \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715
|
||||
|
||||
ARG VERSION_TAG
|
||||
@@ -18,11 +42,10 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||
TMP=/tmp/stirling-pdf
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||
COPY pipeline /pipeline
|
||||
COPY app/core/build/libs/*.jar app.jar
|
||||
COPY --from=build /app/app/core/build/libs/*.jar app.jar
|
||||
|
||||
# Set up necessary directories and permissions
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
@@ -0,0 +1,64 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/backend/Dockerfile.fat
|
||||
container_name: stirling-pdf-backend-fat
|
||||
restart: on-failure:5
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 6G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080" # TODO: Remove in production - for debugging only
|
||||
expose:
|
||||
- "8080"
|
||||
volumes:
|
||||
- ../../stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ../../stirling/latest/config:/configs:rw
|
||||
- ../../stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
FAT_DOCKER: "true"
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS: "false"
|
||||
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Full-featured Stirling-PDF with all capabilities
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Fat
|
||||
SYSTEM_MAXFILESIZE: "200"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
networks:
|
||||
- stirling-network
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/frontend/Dockerfile
|
||||
container_name: stirling-pdf-frontend-fat
|
||||
restart: on-failure:5
|
||||
ports:
|
||||
- "3000:80"
|
||||
environment:
|
||||
BACKEND_URL: http://backend:8080
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- stirling-network
|
||||
|
||||
networks:
|
||||
stirling-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
stirling-data:
|
||||
stirling-config:
|
||||
stirling-logs:
|
||||
@@ -0,0 +1,54 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/backend/Dockerfile.ultra-lite
|
||||
container_name: stirling-pdf-backend-ultra-lite
|
||||
restart: on-failure:5
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080" # TODO: Remove in production - for debugging only
|
||||
expose:
|
||||
- "8080"
|
||||
volumes:
|
||||
- ../../stirling/latest/config:/configs:rw
|
||||
- ../../stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
ENDPOINTS_GROUPS_TO_REMOVE: "CLI"
|
||||
LANGS: "en_GB,en_US"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Ultra-lite version of Stirling-PDF
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Ultra-lite
|
||||
SYSTEM_MAXFILESIZE: "50"
|
||||
networks:
|
||||
- stirling-network
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/frontend/Dockerfile
|
||||
container_name: stirling-pdf-frontend-ultra-lite
|
||||
restart: on-failure:5
|
||||
ports:
|
||||
- "3000:80"
|
||||
environment:
|
||||
BACKEND_URL: http://backend:8080
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- stirling-network
|
||||
|
||||
networks:
|
||||
stirling-network:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,62 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/backend/Dockerfile
|
||||
container_name: stirling-pdf-backend
|
||||
restart: on-failure:5
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080" # TODO: Remove in production - for debugging only
|
||||
expose:
|
||||
- "8080"
|
||||
volumes:
|
||||
- ../../stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ../../stirling/latest/config:/configs:rw
|
||||
- ../../stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
networks:
|
||||
- stirling-network
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: docker/frontend/Dockerfile
|
||||
container_name: stirling-pdf-frontend
|
||||
restart: on-failure:5
|
||||
ports:
|
||||
- "3000:80"
|
||||
environment:
|
||||
BACKEND_URL: http://backend:8080
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- stirling-network
|
||||
|
||||
networks:
|
||||
stirling-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
stirling-data:
|
||||
stirling-config:
|
||||
stirling-logs:
|
||||
@@ -0,0 +1,38 @@
|
||||
# Frontend Dockerfile - React/Vite application
|
||||
FROM node:20-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY frontend/package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY frontend .
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built files from build stage
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy nginx configuration and entrypoint
|
||||
COPY docker/frontend/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/frontend/entrypoint.sh /entrypoint.sh
|
||||
|
||||
# Make entrypoint executable
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Expose port 80 (standard HTTP port)
|
||||
EXPOSE 80
|
||||
|
||||
# Environment variables for flexibility
|
||||
ENV VITE_API_BASE_URL=http://backend:8080
|
||||
|
||||
# Use custom entrypoint
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Set default backend URL if not provided
|
||||
VITE_API_BASE_URL=${VITE_API_BASE_URL:-"http://backend:8080"}
|
||||
|
||||
# Replace the placeholder in nginx.conf with the actual backend URL
|
||||
sed -i "s|\${VITE_API_BASE_URL}|${VITE_API_BASE_URL}|g" /etc/nginx/nginx.conf
|
||||
|
||||
# Start nginx
|
||||
exec nginx -g "daemon off;"
|
||||
@@ -0,0 +1,67 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
|
||||
# Global settings for file uploads
|
||||
client_max_body_size 100m;
|
||||
|
||||
# Handle client-side routing - support subpaths
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API calls to backend
|
||||
location /api/ {
|
||||
proxy_pass ${VITE_API_BASE_URL}/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
|
||||
# Additional headers for proper API proxying
|
||||
proxy_set_header Connection '';
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# Timeout settings for large file uploads
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# Request size limits for file uploads
|
||||
client_max_body_size 100m;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Fat-Disable-Endpoints
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
- ../testing/allEndpointsRemovedSettings.yml:/configs/settings.yml:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with all Endpoints Disabled
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -1,64 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security-Fat-Postgres
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat-postgres
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
depends_on:
|
||||
- db
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'" ]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security and PostgreSQL
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat-PostgreSQL
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "true"
|
||||
SYSTEM_DATASOURCE_CUSTOMDATABASEURL: "jdbc:postgresql://db:5432/stirling_pdf"
|
||||
SYSTEM_DATASOURCE_USERNAME: "admin"
|
||||
SYSTEM_DATASOURCE_PASSWORD: "stirling"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
|
||||
db:
|
||||
image: 'postgres:17.2-alpine'
|
||||
restart: on-failure:5
|
||||
container_name: db
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: "stirling_pdf"
|
||||
POSTGRES_USER: "admin"
|
||||
POSTGRES_PASSWORD: "stirling"
|
||||
shm_size: "512mb"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512m
|
||||
cpus: "0.5"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U admin stirling_pdf" ]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
volumes:
|
||||
- ./stirling/latest/data:/pgdata
|
||||
@@ -1,34 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security-Fat
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -1,42 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
SECURITY_OAUTH2_ENABLED: "true"
|
||||
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF
|
||||
SECURITY_OAUTH2_ISSUER: "https://accounts.google.com" # Change with any other provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||
SECURITY_OAUTH2_CLIENTID: "<YOUR CLIENT ID>.apps.googleusercontent.com" # Client ID from your provider
|
||||
SECURITY_OAUTH2_CLIENTSECRET: "<YOUR CLIENT SECRET>" # Client Secret from your provider
|
||||
SECURITY_OAUTH2_SCOPES: "openid,profile,email" # Expected OAuth2 Scope
|
||||
SECURITY_OAUTH2_USEASUSERNAME: "email" # Default is 'email'; custom fields can be used as the username
|
||||
SECURITY_OAUTH2_PROVIDER: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -1,34 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -1,31 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Ultra-Lite-Security
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-ultra-lite
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF-Lite
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -1,31 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SHOW_SURVEY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -1,34 +0,0 @@
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security-Fat-with-login
|
||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
|
||||
restart: on-failure:5
|
||||
@@ -0,0 +1,24 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,74 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Docker Setup
|
||||
|
||||
For Docker deployments and configuration, see the [Docker README](../docker/README.md).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,700,0,0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using Vite"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+6346
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
|
||||
"proxy": "http://localhost:8080",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mantine/core": "^8.0.1",
|
||||
"@mantine/dropzone": "^8.0.1",
|
||||
"@mantine/hooks": "^8.0.1",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^1.9.0",
|
||||
"i18next": "^25.2.1",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"generate-licenses": "node scripts/generate-licenses.js"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.1.4",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user