Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
086daf8351 | ||
|
|
2741094a8a | ||
|
|
b4dc766f7a | ||
|
|
75e2cfb234 | ||
|
|
c5f7000e72 | ||
|
|
ca0432e0b4 | ||
|
|
5799e61385 | ||
|
|
f2784c85d6 | ||
|
|
01a3fa8cfe | ||
|
|
41138cb2be | ||
|
|
995de6abc3 | ||
|
|
36deb32f07 | ||
|
|
6a38c55867 | ||
|
|
96e390c98d | ||
|
|
52978ec9ad | ||
|
|
fcd4af2d09 | ||
|
|
963c1f4874 | ||
|
|
aa42806a9e | ||
|
|
19c564a6f7 | ||
|
|
6afbd8bd24 | ||
|
|
76bfc09a44 | ||
|
|
6df412c576 | ||
|
|
37bb890cb9 | ||
|
|
9bd15d7ef3 | ||
|
|
b0671943f7 | ||
|
|
7cbad4df4f | ||
|
|
e27651826e | ||
|
|
5b0de9eac1 | ||
|
|
b646d8c481 | ||
|
|
cd2f628168 | ||
|
|
aef0d32b5b | ||
|
|
e761ad8e51 | ||
|
|
059296d444 | ||
|
|
9c1de1cb10 | ||
|
|
ebba39ce10 | ||
|
|
361b4c2db4 | ||
|
|
d221654121 | ||
|
|
7ac41d7863 | ||
|
|
f1476d197f | ||
|
|
4a53195c25 | ||
|
|
e2bed6f6af | ||
|
|
5d6e23d4b7 | ||
|
|
dfb8ba857f | ||
|
|
1572404e6f | ||
|
|
76dc90d587 | ||
|
|
316b4e42af | ||
|
|
f61bbd312f | ||
|
|
65b9544942 | ||
|
|
7e8b86e6eb | ||
|
|
2ab5bc1e18 | ||
|
|
01b2613efe | ||
|
|
50fc13b30c | ||
|
|
b7dc248f93 | ||
|
|
8b05204047 | ||
|
|
18680f2847 | ||
|
|
6c790299aa | ||
|
|
65d662588e | ||
|
|
ab7acb5db3 | ||
|
|
dde0f5cd10 | ||
|
|
5d70217961 | ||
|
|
a0f0a446de | ||
|
|
cd6f3862f6 | ||
|
|
0f4eb8398a | ||
|
|
c9a3f48e5a | ||
|
|
a7f67961e7 | ||
|
|
32209534a0 | ||
|
|
7196f0f970 | ||
|
|
64d0be5ffa | ||
|
|
31be5baf3d | ||
|
|
4781fd515b | ||
|
|
11497f52d4 | ||
|
|
fa934f06ab | ||
|
|
3d78e01559 | ||
|
|
65f9438639 | ||
|
|
6ffa80c386 | ||
|
|
8eb7b18089 | ||
|
|
9041441c46 | ||
|
|
502a4b1cc3 | ||
|
|
ce13648075 | ||
|
|
9644557a9e | ||
|
|
01964add79 | ||
|
|
822e771f45 | ||
|
|
c0888fb938 | ||
|
|
5cdb3bee21 | ||
|
|
4cce6c1c21 | ||
|
|
b928e294d1 | ||
|
|
ec3aa17f65 | ||
|
|
851d77de8e | ||
|
|
137fdaca6a | ||
|
|
7371f4e87f | ||
|
|
6529eb6b61 | ||
|
|
729af56d1b | ||
|
|
9f4a600eba | ||
|
|
ddb2528ecf | ||
|
|
eb5aeb4595 | ||
|
|
b93bff5cad | ||
|
|
3ae891c62e | ||
|
|
48bd060d6e | ||
|
|
5dee64ab7b | ||
|
|
a2f66493ea | ||
|
|
37a0103699 | ||
|
|
2dabf8955d | ||
|
|
9ab471fb63 | ||
|
|
801fd8bb21 | ||
|
|
5e9c780d31 | ||
|
|
bbaaaf7ae6 | ||
|
|
befd0974f3 | ||
|
|
250c317155 | ||
|
|
2c148eb0c0 | ||
|
|
ead8010bd1 | ||
|
|
0962159523 | ||
|
|
cbb4ccd4b7 | ||
|
|
41e73e4fd1 | ||
|
|
86a6ea5a26 | ||
|
|
ce5af5ddde | ||
|
|
0d193cd235 | ||
|
|
f06d755899 | ||
|
|
4dcf2f5870 | ||
|
|
c2179ccd63 | ||
|
|
17ef2e9b5d | ||
|
|
94445bceb1 | ||
|
|
801dcdb463 | ||
|
|
7b49d85804 | ||
|
|
4190aa20a6 | ||
|
|
5a832198b4 | ||
|
|
2066bb2ae8 | ||
|
|
5d64c97406 | ||
|
|
d648c6d4b4 | ||
|
|
435bfa3b3f | ||
|
|
4d0135d7b7 | ||
|
|
5975928e89 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://paypal.me/froodleplex?country.x=GB&locale.x=en_GB'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
custom: ['https://www.paypal.com/donate/?hosted_button_id=MN7JPG5G6G3JL'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -3,14 +3,8 @@ name: "Build repo"
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "**/*.md"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -36,7 +30,7 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
gradle-version: 8.7
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-build-cache
|
||||
|
||||
30
.github/workflows/push-docker.yml
vendored
30
.github/workflows/push-docker.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
gradle-version: 8.7
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: ./gradlew clean build
|
||||
@@ -110,3 +110,31 @@ jobs:
|
||||
labels: ${{ steps.meta2.outputs.labels }}
|
||||
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
|
||||
- name: Generate tags fat
|
||||
id: meta3
|
||||
uses: docker/metadata-action@v5
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||
tags: |
|
||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
|
||||
- name: Build and push main Dockerfile fat
|
||||
uses: docker/build-push-action@v5
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.meta3.outputs.tags }}
|
||||
labels: ${{ steps.meta3.outputs.labels }}
|
||||
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
2
.github/workflows/releaseArtifacts.yml
vendored
2
.github/workflows/releaseArtifacts.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
gradle-version: 8.7
|
||||
|
||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||
run: ./gradlew clean createExe
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -32,6 +32,15 @@ jobs:
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
# sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Pip requirements
|
||||
run: |
|
||||
pip install -r ./cucumber/requirements.txt
|
||||
|
||||
- name: Run Docker Compose Tests
|
||||
run: |
|
||||
chmod +x ./test.sh
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -124,4 +124,7 @@ watchedFolders/
|
||||
|
||||
# Ignore Mac DS_Store files
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
**/.DS_Store
|
||||
|
||||
#cucumber
|
||||
/cucumber/reports/**
|
||||
@@ -29,7 +29,7 @@ If you would like to add or modify a translation, please see [How to add new lan
|
||||
|
||||
## Docs
|
||||
|
||||
Documentation for Stirling-PDF is handled in a seperate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||
|
||||
## Fixing Bugs or Adding a New Feature
|
||||
|
||||
|
||||
25
Dockerfile
25
Dockerfile
@@ -1,5 +1,5 @@
|
||||
# Main stage
|
||||
FROM alpine:20240329
|
||||
FROM alpine:3.20.0
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
@@ -10,35 +10,33 @@ COPY build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
|
||||
# Set Environment Variables
|
||||
ENV DOCKER_ENABLE_SECURITY=false \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
PUID=1000 \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
UMASK=022
|
||||
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing 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 && \
|
||||
apk update && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
bash \
|
||||
curl \
|
||||
openjdk21-jre \
|
||||
su-exec \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
libreoffice@testing \
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||
@@ -60,10 +58,9 @@ openssl-dev \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||
tesseract --list-langs && \
|
||||
rm -rf /var/cache/apk/*
|
||||
tesseract --list-langs
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
|
||||
84
Dockerfile-fat
Normal file
84
Dockerfile-fat
Normal file
@@ -0,0 +1,84 @@
|
||||
# Build the application
|
||||
FROM gradle:7.6-jdk17 AS build
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the entire project to the working directory
|
||||
COPY . .
|
||||
|
||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
||||
RUN DOCKER_ENABLE_SECURITY=true \
|
||||
./gradlew clean build
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.20.0
|
||||
|
||||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
COPY pipeline /pipeline
|
||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||
COPY --from=build /app/build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
# Set Environment Variables
|
||||
ENV DOCKER_ENABLE_SECURITY=false \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
UMASK=022 \
|
||||
FAT_DOCKER=true \
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=true
|
||||
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing 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 && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
calibre@testing \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||
ocrmypdf \
|
||||
tesseract-ocr-data-eng \
|
||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||
# CV
|
||||
py3-opencv \
|
||||
# python3/pip
|
||||
python3 && \
|
||||
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||
fc-cache -f -v && \
|
||||
chmod +x /scripts/* && \
|
||||
chmod +x /scripts/init.sh && \
|
||||
# User permissions
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||
tesseract --list-langs
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
@@ -1,5 +1,5 @@
|
||||
# use alpine
|
||||
FROM alpine:3.19.1
|
||||
FROM alpine:3.20.0
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
@@ -8,7 +8,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
HOME=/home/stirlingpdfuser \
|
||||
VERSION_TAG=$VERSION_TAG \
|
||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||
PUID=1000 \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
UMASK=022
|
||||
|
||||
@@ -18,24 +18,23 @@ COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY pipeline /pipeline
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
|
||||
# Set up necessary directories and permissions
|
||||
|
||||
RUN mkdir /configs /logs /customFiles && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing 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 && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
su-exec \
|
||||
shadow \
|
||||
su-exec \
|
||||
openjdk21-jre && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing 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 && \
|
||||
# User permissions
|
||||
mkdir /configs /logs /customFiles && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
@@ -43,9 +42,8 @@ RUN mkdir /configs /logs /customFiles && \
|
||||
# Set environment variables
|
||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript |
|
||||
|---------------------|---------|---------|----------|-------|------|--------|--------|-------------|----------|----------|------------|
|
||||
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ |
|
||||
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | |
|
||||
| crop | ✔️ | | | | | | | | | ✔️ | |
|
||||
| extract-page | ✔️ | | | | | | | | | ✔️ | |
|
||||
| merge-pdfs | ✔️ | | | | | | | | | ✔️ | |
|
||||
| multi-page-layout | ✔️ | | | | | | | | | ✔️ | |
|
||||
| pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ |
|
||||
| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | |
|
||||
| remove-pages | ✔️ | | | | | | | | | ✔️ | |
|
||||
| rotate-pdf | ✔️ | | | | | | | | | ✔️ | |
|
||||
| scale-pages | ✔️ | | | | | | | | | ✔️ | |
|
||||
| split-pdfs | ✔️ | | | | | | | | | ✔️ | |
|
||||
| file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | |
|
||||
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-img | | ✔️ | | | | | | | | ✔️ | |
|
||||
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | |
|
||||
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | |
|
||||
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| xlsx-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| add-password | | | ✔️ | | | | | | | ✔️ | |
|
||||
| add-watermark | | | ✔️ | | | | | | | ✔️ | |
|
||||
| cert-sign | | | ✔️ | | | | | | | ✔️ | |
|
||||
| change-permissions | | | ✔️ | | | | | | | ✔️ | |
|
||||
| remove-password | | | ✔️ | | | | | | | ✔️ | |
|
||||
| sanitize-pdf | | | ✔️ | | | | | | | ✔️ | |
|
||||
| add-image | | | | ✔️ | | | | | | ✔️ | |
|
||||
| add-page-numbers | | | | ✔️ | | | | | | ✔️ | |
|
||||
| auto-rename | | | | ✔️ | | | | | | ✔️ | |
|
||||
| change-metadata | | | | ✔️ | | | | | | ✔️ | |
|
||||
| compare | | | | ✔️ | | | | | | | ✔️ |
|
||||
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
|
||||
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
|
||||
| extract-images | | | | ✔️ | | | | | | ✔️ | |
|
||||
| flatten | | | | ✔️ | | | | | | | ✔️ |
|
||||
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | |
|
||||
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
|
||||
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
|
||||
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | |
|
||||
| show-javascript | | | | ✔️ | | | | | | | ✔️ |
|
||||
| sign | | | | ✔️ | | | | | | | ✔️ |
|
||||
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript |
|
||||
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- |
|
||||
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ |
|
||||
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | |
|
||||
| crop | ✔️ | | | | | | | | | ✔️ | |
|
||||
| extract-page | ✔️ | | | | | | | | | ✔️ | |
|
||||
| merge-pdfs | ✔️ | | | | | | | | | ✔️ | |
|
||||
| multi-page-layout | ✔️ | | | | | | | | | ✔️ | |
|
||||
| pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ |
|
||||
| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | |
|
||||
| remove-pages | ✔️ | | | | | | | | | ✔️ | |
|
||||
| rotate-pdf | ✔️ | | | | | | | | | ✔️ | |
|
||||
| scale-pages | ✔️ | | | | | | | | | ✔️ | |
|
||||
| split-pdfs | ✔️ | | | | | | | | | ✔️ | |
|
||||
| file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | |
|
||||
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-img | | ✔️ | | | | | | | | ✔️ | |
|
||||
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | |
|
||||
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | |
|
||||
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| pdf-to-xml | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| xlsx-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
|
||||
| add-password | | | ✔️ | | | | | | | ✔️ | |
|
||||
| add-watermark | | | ✔️ | | | | | | | ✔️ | |
|
||||
| cert-sign | | | ✔️ | | | | | | | ✔️ | |
|
||||
| remove-cert-sign | | | ✔️ | | | | | | | ✔️ | |
|
||||
| change-permissions | | | ✔️ | | | | | | | ✔️ | |
|
||||
| remove-password | | | ✔️ | | | | | | | ✔️ | |
|
||||
| sanitize-pdf | | | ✔️ | | | | | | | ✔️ | |
|
||||
| add-image | | | | ✔️ | | | | | | ✔️ | |
|
||||
| add-page-numbers | | | | ✔️ | | | | | | ✔️ | |
|
||||
| auto-rename | | | | ✔️ | | | | | | ✔️ | |
|
||||
| change-metadata | | | | ✔️ | | | | | | ✔️ | |
|
||||
| compare | | | | ✔️ | | | | | | | ✔️ |
|
||||
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
|
||||
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
|
||||
| extract-images | | | | ✔️ | | | | | | ✔️ | |
|
||||
| flatten | | | | ✔️ | | | | | | | ✔️ |
|
||||
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | |
|
||||
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | |
|
||||
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | |
|
||||
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | |
|
||||
| show-javascript | | | | ✔️ | | | | | | | ✔️ |
|
||||
| sign | | | | ✔️ | | | | | | | ✔️ |
|
||||
71
README.md
71
README.md
@@ -159,38 +159,40 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Stirling PDF currently supports 27!
|
||||
Stirling PDF currently supports 28!
|
||||
|
||||
| Language | Progress |
|
||||
| ------------------------------------------- | -------------------------------------- |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
|
||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||
|
||||
@@ -212,10 +214,10 @@ For example in the settings.yml you have
|
||||
|
||||
```yaml
|
||||
system:
|
||||
defaultLocale: 'en-US'
|
||||
enableLogin: 'true'
|
||||
```
|
||||
|
||||
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
|
||||
To have this via an environment variable you would have ``SYSTEM_ENABLELOGIN``
|
||||
|
||||
The Current list of settings is
|
||||
|
||||
@@ -226,7 +228,7 @@ security:
|
||||
loginAttemptCount: 5 # lock user account after 5 tries
|
||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||
# initialLogin:
|
||||
# username: "admin" # Initial username for the first login (these are defaulted)
|
||||
# username: "admin" # Initial username for the first login
|
||||
# password: "stirling" # Initial password for the first login
|
||||
# oauth2:
|
||||
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||
@@ -237,6 +239,23 @@ security:
|
||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
# client:
|
||||
# google:
|
||||
# clientId: "" # Client ID for Google OAuth2
|
||||
# clientSecret: "" # Client Secret for Google OAuth2
|
||||
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||
# github:
|
||||
# clientId: "" # Client ID for GitHub OAuth2
|
||||
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||
# keycloak:
|
||||
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
|
||||
@@ -1,52 +1,53 @@
|
||||
| Technology | Ultra-Lite | Full |
|
||||
|----------------|:----------:|:----:|
|
||||
| Java | ✔️ | ✔️ |
|
||||
| JavaScript | ✔️ | ✔️ |
|
||||
| Libre | | ✔️ |
|
||||
| Python | | ✔️ |
|
||||
| OpenCV | | ✔️ |
|
||||
| OCRmyPDF | | ✔️ |
|
||||
| Technology | Ultra-Lite | Full |
|
||||
| ---------- | :--------: | :---: |
|
||||
| Java | ✔️ | ✔️ |
|
||||
| JavaScript | ✔️ | ✔️ |
|
||||
| Libre | | ✔️ |
|
||||
| Python | | ✔️ |
|
||||
| OpenCV | | ✔️ |
|
||||
| OCRmyPDF | | ✔️ |
|
||||
|
||||
Operation | Ultra-Lite | Full
|
||||
-------------------------|------------|-----
|
||||
add-page-numbers | ✔️ | ✔️
|
||||
add-password | ✔️ | ✔️
|
||||
add-image | ✔️ | ✔️
|
||||
add-watermark | ✔️ | ✔️
|
||||
adjust-contrast | ✔️ | ✔️
|
||||
auto-split-pdf | ✔️ | ✔️
|
||||
auto-redact | ✔️ | ✔️
|
||||
auto-rename | ✔️ | ✔️
|
||||
cert-sign | ✔️ | ✔️
|
||||
crop | ✔️ | ✔️
|
||||
change-metadata | ✔️ | ✔️
|
||||
change-permissions | ✔️ | ✔️
|
||||
compare | ✔️ | ✔️
|
||||
extract-page | ✔️ | ✔️
|
||||
extract-images | ✔️ | ✔️
|
||||
flatten | ✔️ | ✔️
|
||||
get-info-on-pdf | ✔️ | ✔️
|
||||
img-to-pdf | ✔️ | ✔️
|
||||
markdown-to-pdf | ✔️ | ✔️
|
||||
merge-pdfs | ✔️ | ✔️
|
||||
multi-page-layout | ✔️ | ✔️
|
||||
overlay-pdf | ✔️ | ✔️
|
||||
pdf-organizer | ✔️ | ✔️
|
||||
pdf-to-csv | ✔️ | ✔️
|
||||
pdf-to-img | ✔️ | ✔️
|
||||
pdf-to-single-page | ✔️ | ✔️
|
||||
remove-pages | ✔️ | ✔️
|
||||
remove-password | ✔️ | ✔️
|
||||
rotate-pdf | ✔️ | ✔️
|
||||
sanitize-pdf | ✔️ | ✔️
|
||||
scale-pages | ✔️ | ✔️
|
||||
sign | ✔️ | ✔️
|
||||
show-javascript | ✔️ | ✔️
|
||||
split-by-size-or-count | ✔️ | ✔️
|
||||
split-pdf-by-sections | ✔️ | ✔️
|
||||
split-pdfs | ✔️ | ✔️
|
||||
compress-pdf | | ✔️
|
||||
extract-image-scans | | ✔️
|
||||
ocr-pdf | | ✔️
|
||||
pdf-to-pdfa | | ✔️
|
||||
remove-blanks | | ✔️
|
||||
| Operation | Ultra-Lite | Full |
|
||||
| ---------------------- | ---------- | ---- |
|
||||
| add-page-numbers | ✔️ | ✔️ |
|
||||
| add-password | ✔️ | ✔️ |
|
||||
| add-image | ✔️ | ✔️ |
|
||||
| add-watermark | ✔️ | ✔️ |
|
||||
| adjust-contrast | ✔️ | ✔️ |
|
||||
| auto-split-pdf | ✔️ | ✔️ |
|
||||
| auto-redact | ✔️ | ✔️ |
|
||||
| auto-rename | ✔️ | ✔️ |
|
||||
| cert-sign | ✔️ | ✔️ |
|
||||
| remove-cert-sign | ✔️ | ✔️ |
|
||||
| crop | ✔️ | ✔️ |
|
||||
| change-metadata | ✔️ | ✔️ |
|
||||
| change-permissions | ✔️ | ✔️ |
|
||||
| compare | ✔️ | ✔️ |
|
||||
| extract-page | ✔️ | ✔️ |
|
||||
| extract-images | ✔️ | ✔️ |
|
||||
| flatten | ✔️ | ✔️ |
|
||||
| get-info-on-pdf | ✔️ | ✔️ |
|
||||
| img-to-pdf | ✔️ | ✔️ |
|
||||
| markdown-to-pdf | ✔️ | ✔️ |
|
||||
| merge-pdfs | ✔️ | ✔️ |
|
||||
| multi-page-layout | ✔️ | ✔️ |
|
||||
| overlay-pdf | ✔️ | ✔️ |
|
||||
| pdf-organizer | ✔️ | ✔️ |
|
||||
| pdf-to-csv | ✔️ | ✔️ |
|
||||
| pdf-to-img | ✔️ | ✔️ |
|
||||
| pdf-to-single-page | ✔️ | ✔️ |
|
||||
| remove-pages | ✔️ | ✔️ |
|
||||
| remove-password | ✔️ | ✔️ |
|
||||
| rotate-pdf | ✔️ | ✔️ |
|
||||
| sanitize-pdf | ✔️ | ✔️ |
|
||||
| scale-pages | ✔️ | ✔️ |
|
||||
| sign | ✔️ | ✔️ |
|
||||
| show-javascript | ✔️ | ✔️ |
|
||||
| split-by-size-or-count | ✔️ | ✔️ |
|
||||
| split-pdf-by-sections | ✔️ | ✔️ |
|
||||
| split-pdfs | ✔️ | ✔️ |
|
||||
| compress-pdf | | ✔️ |
|
||||
| extract-image-scans | | ✔️ |
|
||||
| ocr-pdf | | ✔️ |
|
||||
| pdf-to-pdfa | | ✔️ |
|
||||
| remove-blanks | | ✔️ |
|
||||
|
||||
11
build.gradle
11
build.gradle
@@ -12,7 +12,7 @@ plugins {
|
||||
import com.github.jk1.license.render.*
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.24.5'
|
||||
version = '0.25.1'
|
||||
|
||||
//17 is lowest but we support and recommend 21
|
||||
sourceCompatibility = '17'
|
||||
@@ -21,7 +21,6 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
||||
licenseReport {
|
||||
renderers = [new JsonReportRenderer()]
|
||||
}
|
||||
@@ -94,7 +93,13 @@ dependencies {
|
||||
implementation("io.github.pixee:java-security-toolkit:1.1.3")
|
||||
|
||||
implementation 'org.yaml:snakeyaml:2.2'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.4'
|
||||
|
||||
// Exclude Tomcat and include Jetty
|
||||
implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') {
|
||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||
}
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jetty:3.2.4'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4'
|
||||
|
||||
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.24.5
|
||||
appVersion: 0.25.1
|
||||
description: locally hosted web application that allows you to perform various operations
|
||||
on PDF files
|
||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||
|
||||
BIN
cucumber/exampleFiles/example.docx
Normal file
BIN
cucumber/exampleFiles/example.docx
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.odp
Normal file
BIN
cucumber/exampleFiles/example.odp
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.odt
Normal file
BIN
cucumber/exampleFiles/example.odt
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.pptx
Normal file
BIN
cucumber/exampleFiles/example.pptx
Normal file
Binary file not shown.
158
cucumber/exampleFiles/example.rtf
Normal file
158
cucumber/exampleFiles/example.rtf
Normal file
@@ -0,0 +1,158 @@
|
||||
{\rtf1\ansi\ansicpg1252\uc0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deff0\adeff0{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f2\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}}{\colortbl;\red0\green0\blue0;\red67\green67\blue67;
|
||||
\red102\green102\blue102;}{\stylesheet{\s0\snext0\sqformat\spriority0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Normal;}{\s1\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb400\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs40\ltrch\b0\i0\fs40\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 1;}{\s2\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb360\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs32\ltrch\b0\i0\fs32\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 2;}{\s3\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb320\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs28\ltrch\b0\i0\fs28\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf2 heading 3;}{\s4\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb280\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs24\ltrch\b0\i0\fs24\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 4;}{\s5\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 5;}{\s6\sbasedon0\snext0\styrsid15694742
|
||||
\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai\af2\afs22\ltrch\b0\i\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 6;}{\*\cs10\additive\ssemihidden\spriority0 Default Paragraph Font;
|
||||
}{\*\ts11\tsrowd\snext11\ssemihidden\spriority0\aspalpha\aspnum\adjustright\ltrpar\li0\lin0\ri0\rin0\ql\faauto\tsvertalt\tsbrdrl\tsbrdrr\tsbrdrt\tsbrdrb\tsbrdrdgr\tsbrdrdgl\tsbrdrh\tsbrdrv\trpaddl108\trpaddfl3\trwWidthB0\trftsWidthB3\trpaddt0\trpaddft3\trpaddb0
|
||||
\trpaddfb3\trpaddr108\trpaddfr3 Normal Table;}{\s15\sbasedon0\snext15\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa60\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs52\ltrch\b0\i0\fs52\loch\af2
|
||||
\dbch\af2\hich\f2\strike0\ulnone\cf1 Title;}{\s16\sbasedon0\snext16\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa320\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs30\ltrch\b0\i0\fs30
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 Subtitle;}}{\*\rsidtbl\rsid10976062\rsid13249109}{\*\generator Aspose.Words for Java 23.4.0;}{\info\version1\edmins0\nofpages1\nofwords0\nofchars0\nofcharsws0}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0{
|
||||
\mmathPr\mbrkBin0\mbrkBinSub0\mdefJc1\mdispDef1\minterSp0\mintLim0\mintraSp0\mlMargin0\mmathFont0\mnaryLim1\mpostSp0\mpreSp0\mrMargin0\msmallFrac0\mwrapIndent1440\mwrapRight0}\deflang1033\deflangfe2052\adeflang1025\jexpand\showxmlerrors1\validatexml1{
|
||||
\*\wgrffmtfilter 013f}\viewkind1\viewscale100\fet0\ftnbj\aenddoc\ftnrstcont\aftnrstcont\ftnnar\aftnnrlc\widowctrl\nospaceforul\nolnhtadjtbl\alntblind\lyttblrtgr\dntblnsbdb\noxlattoyen\wrppunct\nobrkwrptbl\expshrtn\snaptogridincell\asianbrkrule\htmautsp\noultrlspc
|
||||
\useltbaln\splytwnine\ftnlytwnine\lytcalctblwd\allowfieldendsel\lnbrkrule\nouicompat\nofeaturethrottle1\utinl\formshade\nojkernpunct\dghspace180\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1\dgmargin\pgbrdrhead\pgbrdrfoot\rsidroot10976062\sectd\sectlinegrid360\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\guttersxn0\headery720\footery720\colsx720\ltrsect\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar
|
||||
\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2\dbch\af2
|
||||
\hich\f2\strike0\ulnone\cf1 A}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar
|
||||
\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2
|
||||
\dbch\af2\hich\f2\strike0\ulnone\cf1 B}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar
|
||||
\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard
|
||||
\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033
|
||||
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 C}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}{
|
||||
\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef0{\lsdlockedexcept\lsdqformat1 Normal;\lsdqformat1 heading 1;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 2;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 3;
|
||||
\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 4;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 5;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 6;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 7;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 8;
|
||||
\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 9;\lsdsemihidden1\lsdunhideused1\lsdqformat1 caption;\lsdqformat1 Title;\lsdqformat1 Subtitle;\lsdqformat1 Strong;\lsdqformat1 Emphasis;\lsdsemihidden1\lsdpriority99 Placeholder Text;\lsdqformat1\lsdpriority1 No Spacing;
|
||||
\lsdpriority60 Light Shading;\lsdpriority61 Light List;\lsdpriority62 Light Grid;\lsdpriority63 Medium Shading 1;\lsdpriority64 Medium Shading 2;\lsdpriority65 Medium List 1;\lsdpriority66 Medium List 2;\lsdpriority67 Medium Grid 1;\lsdpriority68 Medium Grid 2;
|
||||
\lsdpriority69 Medium Grid 3;\lsdpriority70 Dark List;\lsdpriority71 Colorful Shading;\lsdpriority72 Colorful List;\lsdpriority73 Colorful Grid;\lsdpriority60 Light Shading Accent 1;\lsdpriority61 Light List Accent 1;\lsdpriority62 Light Grid Accent 1;\lsdpriority63 Medium Shading 1 Accent 1;
|
||||
\lsdpriority64 Medium Shading 2 Accent 1;\lsdpriority65 Medium List 1 Accent 1;\lsdsemihidden1\lsdpriority99 Revision;\lsdqformat1\lsdpriority34 List Paragraph;\lsdqformat1\lsdpriority29 Quote;\lsdqformat1\lsdpriority30 Intense Quote;\lsdpriority66 Medium List 2 Accent 1;
|
||||
\lsdpriority67 Medium Grid 1 Accent 1;\lsdpriority68 Medium Grid 2 Accent 1;\lsdpriority69 Medium Grid 3 Accent 1;\lsdpriority70 Dark List Accent 1;\lsdpriority71 Colorful Shading Accent 1;\lsdpriority72 Colorful List Accent 1;\lsdpriority73 Colorful Grid Accent 1;
|
||||
\lsdpriority60 Light Shading Accent 2;\lsdpriority61 Light List Accent 2;\lsdpriority62 Light Grid Accent 2;\lsdpriority63 Medium Shading 1 Accent 2;\lsdpriority64 Medium Shading 2 Accent 2;\lsdpriority65 Medium List 1 Accent 2;\lsdpriority66 Medium List 2 Accent 2;
|
||||
\lsdpriority67 Medium Grid 1 Accent 2;\lsdpriority68 Medium Grid 2 Accent 2;\lsdpriority69 Medium Grid 3 Accent 2;\lsdpriority70 Dark List Accent 2;\lsdpriority71 Colorful Shading Accent 2;\lsdpriority72 Colorful List Accent 2;\lsdpriority73 Colorful Grid Accent 2;
|
||||
\lsdpriority60 Light Shading Accent 3;\lsdpriority61 Light List Accent 3;\lsdpriority62 Light Grid Accent 3;\lsdpriority63 Medium Shading 1 Accent 3;\lsdpriority64 Medium Shading 2 Accent 3;\lsdpriority65 Medium List 1 Accent 3;\lsdpriority66 Medium List 2 Accent 3;
|
||||
\lsdpriority67 Medium Grid 1 Accent 3;\lsdpriority68 Medium Grid 2 Accent 3;\lsdpriority69 Medium Grid 3 Accent 3;\lsdpriority70 Dark List Accent 3;\lsdpriority71 Colorful Shading Accent 3;\lsdpriority72 Colorful List Accent 3;\lsdpriority73 Colorful Grid Accent 3;
|
||||
\lsdpriority60 Light Shading Accent 4;\lsdpriority61 Light List Accent 4;\lsdpriority62 Light Grid Accent 4;\lsdpriority63 Medium Shading 1 Accent 4;\lsdpriority64 Medium Shading 2 Accent 4;\lsdpriority65 Medium List 1 Accent 4;\lsdpriority66 Medium List 2 Accent 4;
|
||||
\lsdpriority67 Medium Grid 1 Accent 4;\lsdpriority68 Medium Grid 2 Accent 4;\lsdpriority69 Medium Grid 3 Accent 4;\lsdpriority70 Dark List Accent 4;\lsdpriority71 Colorful Shading Accent 4;\lsdpriority72 Colorful List Accent 4;\lsdpriority73 Colorful Grid Accent 4;
|
||||
\lsdpriority60 Light Shading Accent 5;\lsdpriority61 Light List Accent 5;\lsdpriority62 Light Grid Accent 5;\lsdpriority63 Medium Shading 1 Accent 5;\lsdpriority64 Medium Shading 2 Accent 5;\lsdpriority65 Medium List 1 Accent 5;\lsdpriority66 Medium List 2 Accent 5;
|
||||
\lsdpriority67 Medium Grid 1 Accent 5;\lsdpriority68 Medium Grid 2 Accent 5;\lsdpriority69 Medium Grid 3 Accent 5;\lsdpriority70 Dark List Accent 5;\lsdpriority71 Colorful Shading Accent 5;\lsdpriority72 Colorful List Accent 5;\lsdpriority73 Colorful Grid Accent 5;
|
||||
\lsdpriority60 Light Shading Accent 6;\lsdpriority61 Light List Accent 6;\lsdpriority62 Light Grid Accent 6;\lsdpriority63 Medium Shading 1 Accent 6;\lsdpriority64 Medium Shading 2 Accent 6;\lsdpriority65 Medium List 1 Accent 6;\lsdpriority66 Medium List 2 Accent 6;
|
||||
\lsdpriority67 Medium Grid 1 Accent 6;\lsdpriority68 Medium Grid 2 Accent 6;\lsdpriority69 Medium Grid 3 Accent 6;\lsdpriority70 Dark List Accent 6;\lsdpriority71 Colorful Shading Accent 6;\lsdpriority72 Colorful List Accent 6;\lsdpriority73 Colorful Grid Accent 6;
|
||||
\lsdqformat1\lsdpriority19 Subtle Emphasis;\lsdqformat1\lsdpriority21 Intense Emphasis;\lsdqformat1\lsdpriority31 Subtle Reference;\lsdqformat1\lsdpriority32 Intense Reference;\lsdqformat1\lsdpriority33 Book Title;\lsdsemihidden1\lsdunhideused1\lsdpriority37 Bibliography;
|
||||
\lsdsemihidden1\lsdunhideused1\lsdqformat1\lsdpriority39 TOC Heading;}}}
|
||||
16
cucumber/features/environment.py
Normal file
16
cucumber/features/environment.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
|
||||
def before_all(context):
|
||||
context.endpoint = None
|
||||
context.request_data = None
|
||||
context.files = {}
|
||||
context.response = None
|
||||
|
||||
def after_scenario(context, scenario):
|
||||
if hasattr(context, 'files'):
|
||||
for file in context.files.values():
|
||||
file.close()
|
||||
if os.path.exists('response_file'):
|
||||
os.remove('response_file')
|
||||
if hasattr(context, 'file_name') and os.path.exists(context.file_name):
|
||||
os.remove(context.file_name)
|
||||
130
cucumber/features/examples.feature
Normal file
130
cucumber/features/examples.feature
Normal file
@@ -0,0 +1,130 @@
|
||||
@example
|
||||
Feature: API Validation
|
||||
|
||||
@positive @password
|
||||
Scenario: Remove password
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the pdf is encrypted with password "password123"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| password | password123 |
|
||||
When I send the API request to the endpoint "/api/v1/security/remove-password"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF is not passworded
|
||||
And the response status code should be 200
|
||||
|
||||
@negative @password
|
||||
Scenario: Remove password wrong password
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the pdf is encrypted with password "password123"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| password | wrongPassword |
|
||||
When I send the API request to the endpoint "/api/v1/security/remove-password"
|
||||
Then the response status code should be 500
|
||||
And the response should contain error message "Internal Server Error"
|
||||
|
||||
@positive @info
|
||||
Scenario: Get info
|
||||
Given I generate a PDF file as "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/security/get-info-on-pdf"
|
||||
Then the response content type should be "application/json"
|
||||
And the response file should have size greater than 100
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @password
|
||||
Scenario: Add password
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| password | password123 |
|
||||
When I send the API request to the endpoint "/api/v1/security/add-password"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 100
|
||||
And the response PDF is passworded
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @password
|
||||
Scenario: Add password with other params
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| ownerPassword | ownerPass |
|
||||
| password | password123 |
|
||||
| keyLength | 256 |
|
||||
| canPrint | true |
|
||||
| canModify | false |
|
||||
When I send the API request to the endpoint "/api/v1/security/add-password"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 100
|
||||
And the response PDF is passworded
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @watermark
|
||||
Scenario: Add watermark
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| watermarkType | text |
|
||||
| watermarkText | Sample Watermark |
|
||||
| fontSize | 30 |
|
||||
| rotation | 45 |
|
||||
| opacity | 0.5 |
|
||||
| widthSpacer | 50 |
|
||||
| heightSpacer | 50 |
|
||||
When I send the API request to the endpoint "/api/v1/security/add-watermark"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 100
|
||||
And the response status code should be 200
|
||||
|
||||
@positive
|
||||
Scenario: Remove blank pages
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 blank pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| threshold | 90 |
|
||||
| whitePercent | 99.9 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/remove-blanks"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF should contain 0 pages
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @flatten
|
||||
Scenario: Flatten PDF
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| flattenOnlyForms | false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/flatten"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
@positive @metadata
|
||||
Scenario: Update metadata
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| author | John Doe |
|
||||
| title | Sample Title |
|
||||
| subject | Sample Subject |
|
||||
| keywords | sample, test |
|
||||
| producer | Test Producer |
|
||||
When I send the API request to the endpoint "/api/v1/misc/update-metadata"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response PDF metadata should include "Author" as "John Doe"
|
||||
And the response PDF metadata should include "Keywords" as "sample, test"
|
||||
And the response PDF metadata should include "Subject" as "Sample Subject"
|
||||
And the response PDF metadata should include "Title" as "Sample Title"
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
228
cucumber/features/external.feature
Normal file
228
cucumber/features/external.feature
Normal file
@@ -0,0 +1,228 @@
|
||||
Feature: API Validation
|
||||
|
||||
|
||||
@libre @positive
|
||||
Scenario: Repair PDF
|
||||
Given I generate a PDF file as "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/misc/repair"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Process PDF with OCR
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | false |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Normal |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Extract Image Scans
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 images on 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| angleThreshold | 5 |
|
||||
| tolerance | 20 |
|
||||
| minArea | 8000 |
|
||||
| minContourArea | 500 |
|
||||
| borderSize | 1 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/extract-image-scans"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response file should have extension ".zip"
|
||||
And the response ZIP should contain 2 files
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
|
||||
@ocr @negative
|
||||
Scenario: Process PDF with text and OCR with type normal
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | false |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Normal |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response status code should be 500
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Process PDF with OCR
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | false |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Force |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
@ocr @positive
|
||||
Scenario: Process PDF with OCR with sidecar
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| languages | eng |
|
||||
| sidecar | true |
|
||||
| deskew | true |
|
||||
| clean | true |
|
||||
| cleanFinal | true |
|
||||
| ocrType | Force |
|
||||
| ocrRenderType | hocr |
|
||||
| removeImagesAfter| false |
|
||||
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response file should have extension ".zip"
|
||||
And the response ZIP should contain 2 files
|
||||
And the response file should have size greater than 0
|
||||
And the response status code should be 200
|
||||
|
||||
|
||||
@libre @positive
|
||||
Scenario Outline: Convert PDF to various word formats
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | <format> |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/word"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension "<extension>"
|
||||
|
||||
Examples:
|
||||
| format | extension |
|
||||
| docx | .docx |
|
||||
| odt | .odt |
|
||||
| doc | .doc |
|
||||
|
||||
@ocr
|
||||
Scenario: PDFA
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | pdfa |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@ocr
|
||||
Scenario: PDFA1
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | pdfa-1 |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 4 |
|
||||
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 1 |
|
||||
| expectedOutputSize | 5KB |
|
||||
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
|
||||
@compress @ghostscript @positive
|
||||
Scenario: Compress
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| optimizeLevel | 1 |
|
||||
| expectedOutputSize | 5KB |
|
||||
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@libre @positive
|
||||
Scenario Outline: Convert PDF to various types
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 3 pages with random text
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| outputFormat | <format> |
|
||||
When I send the API request to the endpoint "/api/v1/convert/pdf/<type>"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension "<extension>"
|
||||
|
||||
Examples:
|
||||
| type | format | extension |
|
||||
| text | rtf | .rtf |
|
||||
| text | txt | .txt |
|
||||
| presentation | ppt | .ppt |
|
||||
| presentation | pptx | .pptx |
|
||||
| presentation | odp | .odp |
|
||||
| html | html | .zip |
|
||||
|
||||
|
||||
@libre @positive @topdf
|
||||
Scenario Outline: Convert PDF to various types
|
||||
Given I use an example file at "exampleFiles/example<extension>" as parameter "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/convert/file/pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension ".pdf"
|
||||
|
||||
Examples:
|
||||
| extension |
|
||||
| .docx |
|
||||
| .odp |
|
||||
| .odt |
|
||||
| .pptx |
|
||||
| .rtf |
|
||||
|
||||
|
||||
|
||||
96
cucumber/features/general.feature
Normal file
96
cucumber/features/general.feature
Normal file
@@ -0,0 +1,96 @@
|
||||
@general
|
||||
Feature: API Validation
|
||||
|
||||
|
||||
@split-pdf-by-sections @positive
|
||||
Scenario Outline: split-pdf-by-sections with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| horizontalDivisions | <horizontalDivisions> |
|
||||
| verticalDivisions | <verticalDivisions> |
|
||||
| merge | true |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 200
|
||||
And the response status code should be 200
|
||||
And the response PDF should contain <page_count> pages
|
||||
|
||||
Examples:
|
||||
| horizontalDivisions | verticalDivisions | page_count |
|
||||
| 0 | 1 | 4 |
|
||||
| 1 | 1 | 8 |
|
||||
| 1 | 2 | 12 |
|
||||
| 2 | 2 | 18 |
|
||||
|
||||
@split-pdf-by-sections @positive
|
||||
Scenario Outline: split-pdf-by-sections with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 2 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| horizontalDivisions | <horizontalDivisions> |
|
||||
| verticalDivisions | <verticalDivisions> |
|
||||
| merge | true |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections"
|
||||
Then the response content type should be "application/pdf"
|
||||
And the response file should have size greater than 200
|
||||
And the response status code should be 200
|
||||
And the response PDF should contain <page_count> pages
|
||||
|
||||
Examples:
|
||||
| horizontalDivisions | verticalDivisions | page_count |
|
||||
| 0 | 1 | 4 |
|
||||
| 1 | 1 | 8 |
|
||||
| 1 | 2 | 12 |
|
||||
| 2 | 2 | 18 |
|
||||
|
||||
|
||||
|
||||
@split-pdf-by-pages @positive
|
||||
Scenario Outline: split-pdf-by-pages with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 20 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| fileInput | fileInput |
|
||||
| pageNumbers | <pageNumbers> |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-pages"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response status code should be 200
|
||||
And the response file should have size greater than 200
|
||||
And the response ZIP should contain <file_count> files
|
||||
|
||||
Examples:
|
||||
| pageNumbers | file_count |
|
||||
| 1,3,5-9 | 8 |
|
||||
| all | 20 |
|
||||
| 2n+1 | 11 |
|
||||
| 3n | 7 |
|
||||
|
||||
|
||||
|
||||
@split-pdf-by-size-or-count @positive
|
||||
Scenario Outline: split-pdf-by-size-or-count with different parameters
|
||||
Given I generate a PDF file as "fileInput"
|
||||
And the pdf contains 20 pages
|
||||
And the request data includes
|
||||
| parameter | value |
|
||||
| fileInput | fileInput |
|
||||
| splitType | <splitType> |
|
||||
| splitValue | <splitValue> |
|
||||
When I send the API request to the endpoint "/api/v1/general/split-by-size-or-count"
|
||||
Then the response content type should be "application/octet-stream"
|
||||
And the response status code should be 200
|
||||
And the response file should have size greater than 200
|
||||
And the response ZIP file should contain <doc_count> documents each having <pages_per_doc> pages
|
||||
|
||||
Examples:
|
||||
| splitType | splitValue | doc_count | pages_per_doc |
|
||||
| 1 | 5 | 4 | 5 |
|
||||
| 2 | 2 | 2 | 10 |
|
||||
| 2 | 4 | 4 | 5 |
|
||||
| 1 | 10 | 2 | 10 |
|
||||
|
||||
|
||||
307
cucumber/features/steps/step_definitions.py
Normal file
307
cucumber/features/steps/step_definitions.py
Normal file
@@ -0,0 +1,307 @@
|
||||
import os
|
||||
import requests
|
||||
from behave import given, when, then
|
||||
from PyPDF2 import PdfWriter, PdfReader
|
||||
import io
|
||||
import random
|
||||
import string
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.pdfgen import canvas
|
||||
import mimetypes
|
||||
import requests
|
||||
import zipfile
|
||||
import shutil
|
||||
|
||||
#########
|
||||
# GIVEN #
|
||||
#########
|
||||
|
||||
@given('I generate a PDF file as "{fileInput}"')
|
||||
def step_generate_pdf(context, fileInput):
|
||||
context.param_name = fileInput
|
||||
context.file_name = "genericNonCustomisableName.pdf"
|
||||
writer = PdfWriter()
|
||||
writer.add_blank_page(width=72, height=72) # Single blank page
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
@given('I use an example file at "{filePath}" as parameter "{fileInput}"')
|
||||
def step_use_example_file(context, filePath, fileInput):
|
||||
context.param_name = fileInput
|
||||
context.file_name = filePath.split('/')[-1]
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
|
||||
# Ensure the file exists before opening
|
||||
try:
|
||||
example_file = open(filePath, 'rb')
|
||||
context.files[context.param_name] = example_file
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"The example file '{filePath}' does not exist.")
|
||||
|
||||
|
||||
|
||||
@given('the pdf contains {page_count:d} pages')
|
||||
def step_pdf_contains_pages(context, page_count):
|
||||
writer = PdfWriter()
|
||||
for i in range(page_count):
|
||||
writer.add_blank_page(width=72, height=72)
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
# Duplicate for now...
|
||||
@given('the pdf contains {page_count:d} blank pages')
|
||||
def step_pdf_contains_blank_pages(context, page_count):
|
||||
writer = PdfWriter()
|
||||
for i in range(page_count):
|
||||
writer.add_blank_page(width=72, height=72)
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
|
||||
def create_black_box_image(file_name, size):
|
||||
can = canvas.Canvas(file_name, pagesize=size)
|
||||
width, height = size
|
||||
can.setFillColorRGB(0, 0, 0)
|
||||
can.rect(0, 0, width, height, fill=1)
|
||||
can.showPage()
|
||||
can.save()
|
||||
|
||||
def create_pdf_with_black_boxes(file_name, image_count, page_count):
|
||||
page_width, page_height = letter
|
||||
box_size = 72 # 1 inch by 1 inch black box
|
||||
boxes_per_page = image_count // page_count + (1 if image_count % page_count != 0 else 0)
|
||||
|
||||
writer = PdfWriter()
|
||||
box_counter = 0
|
||||
|
||||
for page in range(page_count):
|
||||
packet = io.BytesIO()
|
||||
can = canvas.Canvas(packet, pagesize=letter)
|
||||
|
||||
for i in range(boxes_per_page):
|
||||
if box_counter >= image_count:
|
||||
break
|
||||
x = (i % (page_width // box_size)) * box_size
|
||||
y = page_height - ((i // (page_width // box_size) + 1) * box_size)
|
||||
can.setFillColorRGB(0, 0, 0)
|
||||
can.rect(x, y, box_size, box_size, fill=1)
|
||||
box_counter += 1
|
||||
|
||||
can.showPage()
|
||||
can.save()
|
||||
packet.seek(0)
|
||||
new_pdf = PdfReader(packet)
|
||||
writer.add_page(new_pdf.pages[0])
|
||||
|
||||
with open(file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
|
||||
@given('the pdf contains {image_count:d} images on {page_count:d} pages')
|
||||
def step_pdf_contains_images(context, image_count, page_count):
|
||||
if not hasattr(context, 'param_name'):
|
||||
context.param_name = "default"
|
||||
context.file_name = "genericNonCustomisableName.pdf"
|
||||
create_pdf_with_black_boxes(context.file_name, image_count, page_count)
|
||||
if not hasattr(context, 'files'):
|
||||
context.files = {}
|
||||
if context.param_name in context.files:
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
|
||||
@given('the pdf contains {page_count:d} pages with random text')
|
||||
def step_pdf_contains_pages_with_random_text(context, page_count):
|
||||
buffer = io.BytesIO()
|
||||
c = canvas.Canvas(buffer, pagesize=letter)
|
||||
width, height = letter
|
||||
|
||||
for _ in range(page_count):
|
||||
text = ''.join(random.choices(string.ascii_letters + string.digits, k=100))
|
||||
c.drawString(100, height - 100, text)
|
||||
c.showPage()
|
||||
|
||||
c.save()
|
||||
|
||||
with open(context.file_name, 'wb') as f:
|
||||
f.write(buffer.getvalue())
|
||||
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
@given('the pdf pages all contain the text "{text}"')
|
||||
def step_pdf_pages_contain_text(context, text):
|
||||
buffer = io.BytesIO()
|
||||
c = canvas.Canvas(buffer, pagesize=letter)
|
||||
width, height = letter
|
||||
|
||||
for _ in range(len(PdfReader(context.file_name).pages)):
|
||||
c.drawString(100, height - 100, text)
|
||||
c.showPage()
|
||||
|
||||
c.save()
|
||||
|
||||
with open(context.file_name, 'wb') as f:
|
||||
f.write(buffer.getvalue())
|
||||
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
@given('the pdf is encrypted with password "{password}"')
|
||||
def step_encrypt_pdf(context, password):
|
||||
writer = PdfWriter()
|
||||
reader = PdfReader(context.file_name)
|
||||
for i in range(len(reader.pages)):
|
||||
writer.add_page(reader.pages[i])
|
||||
writer.encrypt(password)
|
||||
with open(context.file_name, 'wb') as f:
|
||||
writer.write(f)
|
||||
context.files[context.param_name].close()
|
||||
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||
|
||||
@given('the request data is')
|
||||
def step_request_data(context):
|
||||
context.request_data = eval(context.text)
|
||||
|
||||
@given('the request data includes')
|
||||
def step_request_data_table(context):
|
||||
context.request_data = {row['parameter']: row['value'] for row in context.table}
|
||||
|
||||
@given('save the generated PDF file as "{filename}" for debugging')
|
||||
def save_generated_pdf(context, filename):
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(context.files[context.param_name].read())
|
||||
print(f"Saved generated PDF content to {filename}")
|
||||
|
||||
########
|
||||
# WHEN #
|
||||
########
|
||||
|
||||
@when('I send the API request to the endpoint "{endpoint}"')
|
||||
def step_send_api_request(context, endpoint):
|
||||
url = f"http://localhost:8080{endpoint}"
|
||||
files = context.files if hasattr(context, 'files') else {}
|
||||
|
||||
if not hasattr(context, 'request_data') or context.request_data is None:
|
||||
context.request_data = {}
|
||||
|
||||
form_data = []
|
||||
for key, value in context.request_data.items():
|
||||
form_data.append((key, (None, value)))
|
||||
|
||||
for key, file in files.items():
|
||||
mime_type, _ = mimetypes.guess_type(file.name)
|
||||
mime_type = mime_type or 'application/octet-stream'
|
||||
print(f"form_data {file.name} with {mime_type}")
|
||||
form_data.append((key, (file.name, file, mime_type)))
|
||||
|
||||
response = requests.post(url, files=form_data)
|
||||
context.response = response
|
||||
|
||||
########
|
||||
# THEN #
|
||||
########
|
||||
|
||||
@then('the response content type should be "{content_type}"')
|
||||
def step_check_response_content_type(context, content_type):
|
||||
actual_content_type = context.response.headers.get('Content-Type', '')
|
||||
assert actual_content_type.startswith(content_type), f"Expected {content_type} but got {actual_content_type}. Response content: {context.response.content}"
|
||||
|
||||
@then('the response file should have size greater than {size:d}')
|
||||
def step_check_response_file_size(context, size):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
assert len(response_file.getvalue()) > size
|
||||
|
||||
@then('the response PDF is not passworded')
|
||||
def step_check_response_pdf_not_passworded(context):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(response_file)
|
||||
assert not reader.is_encrypted
|
||||
|
||||
@then('the response PDF is passworded')
|
||||
def step_check_response_pdf_passworded(context):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
try:
|
||||
reader = PdfReader(response_file)
|
||||
assert reader.is_encrypted
|
||||
except PdfReadError as e:
|
||||
raise AssertionError(f"Failed to read PDF: {str(e)}. Response content: {context.response.content}")
|
||||
except Exception as e:
|
||||
raise AssertionError(f"An error occurred: {str(e)}. Response content: {context.response.content}")
|
||||
|
||||
@then('the response status code should be {status_code:d}')
|
||||
def step_check_response_status_code(context, status_code):
|
||||
assert context.response.status_code == status_code, f"Expected status code {status_code} but got {context.response.status_code}"
|
||||
|
||||
@then('the response should contain error message "{message}"')
|
||||
def step_check_response_error_message(context, message):
|
||||
response_json = context.response.json()
|
||||
assert response_json.get('error') == message, f"Expected error message '{message}' but got '{response_json.get('error')}'"
|
||||
|
||||
@then('the response PDF should contain {page_count:d} pages')
|
||||
def step_check_response_pdf_page_count(context, page_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(response_file)
|
||||
assert len(reader.pages) == page_count, f"Expected {page_count} pages but got {len(reader.pages)} pages"
|
||||
|
||||
@then('the response PDF metadata should include "{metadata_key}" as "{metadata_value}"')
|
||||
def step_check_response_pdf_metadata(context, metadata_key, metadata_value):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(response_file)
|
||||
metadata = reader.metadata
|
||||
assert metadata.get("/" + metadata_key) == metadata_value, f"Expected {metadata_key} to be '{metadata_value}' but got '{metadata.get(metadata_key)}'"
|
||||
|
||||
@then('the response file should have extension "{extension}"')
|
||||
def step_check_response_file_extension(context, extension):
|
||||
content_disposition = context.response.headers.get('Content-Disposition', '')
|
||||
filename = ""
|
||||
if content_disposition:
|
||||
parts = content_disposition.split(';')
|
||||
for part in parts:
|
||||
if part.strip().startswith('filename'):
|
||||
filename = part.split('=')[1].strip().strip('"')
|
||||
break
|
||||
assert filename.endswith(extension), f"Expected file extension {extension} but got {filename}. Response content: {context.response.content}"
|
||||
|
||||
@then('save the response file as "{filename}" for debugging')
|
||||
def step_save_response_file(context, filename):
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(context.response.content)
|
||||
print(f"Saved response content to {filename}")
|
||||
|
||||
|
||||
@then('the response PDF should contain {page_count:d} pages')
|
||||
def step_check_response_pdf_page_count(context, page_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
reader = PdfReader(io.BytesIO(response_file.getvalue()))
|
||||
actual_page_count = len(reader.pages)
|
||||
assert actual_page_count == page_count, f"Expected {page_count} pages but got {actual_page_count} pages"
|
||||
|
||||
@then('the response ZIP should contain {file_count:d} files')
|
||||
def step_check_response_zip_file_count(context, file_count):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||
actual_file_count = len(zip_file.namelist())
|
||||
assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files"
|
||||
|
||||
@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages')
|
||||
def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc):
|
||||
response_file = io.BytesIO(context.response.content)
|
||||
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||
actual_doc_count = len(zip_file.namelist())
|
||||
assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents"
|
||||
|
||||
for file_name in zip_file.namelist():
|
||||
with zip_file.open(file_name) as pdf_file:
|
||||
reader = PdfReader(pdf_file)
|
||||
actual_pages_per_doc = len(reader.pages)
|
||||
assert actual_pages_per_doc == pages_per_doc, f"Expected {pages_per_doc} pages per document but got {actual_pages_per_doc} pages in document {file_name}"
|
||||
5
cucumber/requirements.txt
Normal file
5
cucumber/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
behave
|
||||
requests
|
||||
PyPDF2
|
||||
reportlab
|
||||
PyCryptodome
|
||||
34
exampleYmlFiles/docker-compose-latest-fat-security.yml
Normal file
34
exampleYmlFiles/docker-compose-latest-fat-security.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security-Fat
|
||||
image: frooodle/s-pdf:latest-fat
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
restart: on-failure:5
|
||||
@@ -27,6 +27,8 @@ services:
|
||||
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"
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -11,14 +11,16 @@ if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -
|
||||
fi
|
||||
umask "$UMASK" || true
|
||||
|
||||
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
|
||||
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" && "$FAT_DOCKER" != "true" ]]; then
|
||||
apk add --no-cache calibre@testing
|
||||
fi
|
||||
|
||||
/scripts/download-security-jar.sh
|
||||
if [[ "$FAT_DOCKER" != "true" ]]; then
|
||||
/scripts/download-security-jar.sh
|
||||
fi
|
||||
|
||||
if [[ -n "$LANGS" ]]; then
|
||||
/scripts/installFonts.sh $LANGS
|
||||
/scripts/installFonts.sh $LANGS
|
||||
fi
|
||||
|
||||
echo "Setting permissions and ownership for necessary directories..."
|
||||
|
||||
@@ -13,6 +13,11 @@ ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[cs_CZ]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[de_DE]
|
||||
ignore = [
|
||||
'AddStampRequest.alphabet',
|
||||
|
||||
@@ -2,8 +2,10 @@ package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
@@ -108,4 +110,26 @@ public class AppConfig {
|
||||
public boolean missingActivSecurity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Bean(name = "watchedFoldersDir")
|
||||
public String watchedFoldersDir() {
|
||||
return "./pipeline/watchedFolders/";
|
||||
}
|
||||
|
||||
@Bean(name = "finishedFoldersDir")
|
||||
public String finishedFoldersDir() {
|
||||
return "./pipeline/finishedFolders/";
|
||||
}
|
||||
|
||||
@Bean(name = "directoryFilter")
|
||||
public Predicate<Path> processPDFOnlyFilter() {
|
||||
return path -> {
|
||||
if (Files.isDirectory(path)) {
|
||||
return !path.toString().contains("processing");
|
||||
} else {
|
||||
String fileName = path.getFileName().toString();
|
||||
return fileName.endsWith(".pdf");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -49,20 +47,32 @@ public class ConfigInitializer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Path templatePath =
|
||||
Paths.get(
|
||||
getClass()
|
||||
.getClassLoader()
|
||||
.getResource("settings.yml.template")
|
||||
.toURI());
|
||||
Path userPath = Paths.get("configs", "settings.yml");
|
||||
// Load the template content from classpath
|
||||
List<String> templateLines;
|
||||
try (InputStream in =
|
||||
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||
if (in == null) {
|
||||
throw new FileNotFoundException(
|
||||
"Resource file not found: settings.yml.template");
|
||||
}
|
||||
templateLines = new ArrayList<>();
|
||||
try (var reader = new InputStreamReader(in)) {
|
||||
try (var bufferedReader = new BufferedReader(reader)) {
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
templateLines.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> templateLines = Files.readAllLines(templatePath);
|
||||
// Read the user settings file if it exists
|
||||
Path userPath = Paths.get("configs", "settings.yml");
|
||||
List<String> userLines =
|
||||
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
|
||||
|
||||
List<String> resultLines = new ArrayList<>();
|
||||
|
||||
int position = 0;
|
||||
for (String templateLine : templateLines) {
|
||||
// Check if the line is a comment
|
||||
if (templateLine.trim().startsWith("#")) {
|
||||
@@ -70,7 +80,7 @@ public class ConfigInitializer
|
||||
if (!entry.isEmpty()) {
|
||||
// Check if this comment has been uncommented in userLines
|
||||
String key = entry.split(":")[0].trim();
|
||||
addLine(resultLines, userLines, templateLine, key);
|
||||
addLine(resultLines, userLines, templateLine, key, position);
|
||||
} else {
|
||||
resultLines.add(templateLine);
|
||||
}
|
||||
@@ -78,32 +88,38 @@ public class ConfigInitializer
|
||||
// Check if the line is a key-value pair
|
||||
else if (templateLine.contains(":")) {
|
||||
String key = templateLine.split(":")[0].trim();
|
||||
addLine(resultLines, userLines, templateLine, key);
|
||||
addLine(resultLines, userLines, templateLine, key, position);
|
||||
}
|
||||
// Handle empty lines
|
||||
else if (templateLine.trim().length() == 0) {
|
||||
resultLines.add("");
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
// Write the result to the user settings file
|
||||
|
||||
// Write the result to the user settings file
|
||||
Files.write(userPath, resultLines);
|
||||
}
|
||||
|
||||
// Ensure the custom settings file exists
|
||||
Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
|
||||
if (!Files.exists(customSettingsPath)) {
|
||||
Files.createFile(customSettingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
|
||||
private static void addLine(List<String> resultLines, List<String> userLines, String templateLine, String key) {
|
||||
|
||||
// TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
|
||||
private static void addLine(
|
||||
List<String> resultLines,
|
||||
List<String> userLines,
|
||||
String templateLine,
|
||||
String key,
|
||||
int position) {
|
||||
boolean added = false;
|
||||
int templateIndentationLevel = getIndentationLevel(templateLine);
|
||||
int pos = 0;
|
||||
for (String settingsLine : userLines) {
|
||||
if (settingsLine.trim().startsWith(key + ":")) {
|
||||
if (settingsLine.trim().startsWith(key + ":") && position == pos) {
|
||||
int settingsIndentationLevel = getIndentationLevel(settingsLine);
|
||||
// Check if it is correct settingsLine and has the same parent as templateLine
|
||||
if (settingsIndentationLevel == templateIndentationLevel) {
|
||||
@@ -112,17 +128,18 @@ public class ConfigInitializer
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (!added) {
|
||||
resultLines.add(templateLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getIndentationLevel(String line) {
|
||||
int indentationLevel = 0;
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith("#")) {
|
||||
line = trimmedLine.substring(1);
|
||||
line = trimmedLine.substring(1);
|
||||
}
|
||||
for (char c : line.toCharArray()) {
|
||||
if (c == ' ') {
|
||||
|
||||
@@ -116,6 +116,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Security", "change-permissions");
|
||||
addEndpointToGroup("Security", "add-watermark");
|
||||
addEndpointToGroup("Security", "cert-sign");
|
||||
addEndpointToGroup("Security", "remove-cert-sign");
|
||||
addEndpointToGroup("Security", "sanitize-pdf");
|
||||
addEndpointToGroup("Security", "auto-redact");
|
||||
|
||||
@@ -200,6 +201,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "extract-images");
|
||||
addEndpointToGroup("Java", "change-metadata");
|
||||
addEndpointToGroup("Java", "cert-sign");
|
||||
addEndpointToGroup("Java", "remove-cert-sign");
|
||||
addEndpointToGroup("Java", "multi-page-layout");
|
||||
addEndpointToGroup("Java", "scale-pages");
|
||||
addEndpointToGroup("Java", "add-page-numbers");
|
||||
|
||||
@@ -2,6 +2,8 @@ package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -36,7 +38,11 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationS
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
@@ -47,6 +53,8 @@ public class SecurityConfiguration {
|
||||
|
||||
@Autowired private CustomUserDetailsService userDetailsService;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
@@ -140,6 +148,7 @@ public class SecurityConfiguration {
|
||||
|| trimmedUri.startsWith("/images/")
|
||||
|| trimmedUri.startsWith("/public/")
|
||||
|| trimmedUri.startsWith("/css/")
|
||||
|| trimmedUri.startsWith("/fonts/")
|
||||
|| trimmedUri.startsWith("/js/")
|
||||
|| trimmedUri.startsWith(
|
||||
"/api/v1/info/status");
|
||||
@@ -150,7 +159,8 @@ public class SecurityConfiguration {
|
||||
.authenticationProvider(authenticationProvider());
|
||||
|
||||
// Handle OAUTH2 Logins
|
||||
if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||
if (applicationProperties.getSecurity().getOAUTH2() != null
|
||||
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||
|
||||
http.oauth2Login(
|
||||
oauth2 ->
|
||||
@@ -181,9 +191,10 @@ public class SecurityConfiguration {
|
||||
.logout(
|
||||
logout ->
|
||||
logout.logoutSuccessHandler(
|
||||
new CustomOAuth2LogoutSuccessHandler(
|
||||
this.applicationProperties,
|
||||
sessionRegistry())));
|
||||
new CustomOAuth2LogoutSuccessHandler(
|
||||
this.applicationProperties,
|
||||
sessionRegistry()))
|
||||
.invalidateHttpSession(true));
|
||||
}
|
||||
} else {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
@@ -200,19 +211,127 @@ public class SecurityConfiguration {
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
return new InMemoryClientRegistrationRepository(this.oidcClientRegistration());
|
||||
List<ClientRegistration> registrations = new ArrayList<>();
|
||||
|
||||
githubClientRegistration().ifPresent(registrations::add);
|
||||
oidcClientRegistration().ifPresent(registrations::add);
|
||||
googleClientRegistration().ifPresent(registrations::add);
|
||||
keycloakClientRegistration().ifPresent(registrations::add);
|
||||
|
||||
if (registrations.isEmpty()) {
|
||||
logger.error("At least one OAuth2 provider must be configured");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
return new InMemoryClientRegistrationRepository(registrations);
|
||||
}
|
||||
|
||||
private ClientRegistration oidcClientRegistration() {
|
||||
private Optional<ClientRegistration> googleClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
return ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||
.registrationId("oidc")
|
||||
.clientId(oauth.getClientId())
|
||||
.clientSecret(oauth.getClientSecret())
|
||||
.scope(oauth.getScopes())
|
||||
.userNameAttributeName(oauth.getUseAsUsername())
|
||||
.clientName("OIDC")
|
||||
.build();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GoogleProvider google = client.getGoogle();
|
||||
return google != null && google.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId("google")
|
||||
.clientId(google.getClientId())
|
||||
.clientSecret(google.getClientSecret())
|
||||
.scope(google.getScopes())
|
||||
.authorizationUri(google.getAuthorizationuri())
|
||||
.tokenUri(google.getTokenuri())
|
||||
.userInfoUri(google.getUserinfouri())
|
||||
.userNameAttributeName(google.getUseAsUsername())
|
||||
.clientName("Google")
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/google")
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
KeycloakProvider keycloak = client.getKeycloak();
|
||||
|
||||
return keycloak != null && keycloak.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||
.registrationId("keycloak")
|
||||
.clientId(keycloak.getClientId())
|
||||
.clientSecret(keycloak.getClientSecret())
|
||||
.scope(keycloak.getScopes())
|
||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||
.clientName("Keycloak")
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> githubClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GithubProvider github = client.getGithub();
|
||||
return github != null && github.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId("github")
|
||||
.clientId(github.getClientId())
|
||||
.clientSecret(github.getClientSecret())
|
||||
.scope(github.getScopes())
|
||||
.authorizationUri(github.getAuthorizationuri())
|
||||
.tokenUri(github.getTokenuri())
|
||||
.userInfoUri(github.getUserinfouri())
|
||||
.userNameAttributeName(github.getUseAsUsername())
|
||||
.clientName("GitHub")
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/github")
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth == null
|
||||
|| oauth.getIssuer() == null
|
||||
|| oauth.getIssuer().isEmpty()
|
||||
|| oauth.getClientId() == null
|
||||
|| oauth.getClientId().isEmpty()
|
||||
|| oauth.getClientSecret() == null
|
||||
|| oauth.getClientSecret().isEmpty()
|
||||
|| oauth.getScopes() == null
|
||||
|| oauth.getScopes().isEmpty()
|
||||
|| oauth.getUseAsUsername() == null
|
||||
|| oauth.getUseAsUsername().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||
.registrationId("oidc")
|
||||
.clientId(oauth.getClientId())
|
||||
.clientSecret(oauth.getClientSecret())
|
||||
.scope(oauth.getScopes())
|
||||
.userNameAttributeName(oauth.getUseAsUsername())
|
||||
.clientName("OIDC")
|
||||
.build());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -101,6 +101,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
contextPath + "/images/",
|
||||
contextPath + "/public/",
|
||||
contextPath + "/css/",
|
||||
contextPath + "/fonts/",
|
||||
contextPath + "/js/",
|
||||
contextPath + "/pdfjs/",
|
||||
contextPath + "/api/v1/info/status",
|
||||
|
||||
@@ -41,6 +41,7 @@ public class CustomOAuth2AuthenticationFailureHandler
|
||||
} else if (exception instanceof LockedException) {
|
||||
logger.error("Account locked: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||
return;
|
||||
} else {
|
||||
logger.error("Unhandled authentication exception", exception);
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
@@ -14,6 +15,8 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.Provider;
|
||||
import stirling.software.SPDF.utils.UrlUtils;
|
||||
|
||||
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
|
||||
@@ -33,11 +36,30 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
||||
public void onLogoutSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String param = "logout=true";
|
||||
String registrationId = null;
|
||||
String issuer = null;
|
||||
String clientId = null;
|
||||
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
String provider = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||
|
||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||
|
||||
try {
|
||||
Provider provider = oauth.getClient().get(registrationId);
|
||||
issuer = provider.getIssuer();
|
||||
clientId = provider.getClientId();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||
issuer = oauth.getIssuer();
|
||||
clientId = oauth.getClientId();
|
||||
}
|
||||
|
||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||
@@ -49,36 +71,46 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
||||
param = "error=oauth2AutoCreateDisabled";
|
||||
}
|
||||
|
||||
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
String sessionId = session.getId();
|
||||
sessionRegistry.removeSessionInformation(sessionId);
|
||||
session.invalidate();
|
||||
logger.debug("Session invalidated: " + sessionId);
|
||||
logger.info("Session invalidated: " + sessionId);
|
||||
}
|
||||
|
||||
switch (provider) {
|
||||
switch (registrationId) {
|
||||
case "keycloak":
|
||||
// Add Keycloak specific logout URL if needed
|
||||
String logoutUrl =
|
||||
oauth.getIssuer()
|
||||
issuer
|
||||
+ "/protocol/openid-connect/logout"
|
||||
+ "?client_id="
|
||||
+ oauth.getClientId()
|
||||
+ clientId
|
||||
+ "&post_logout_redirect_uri="
|
||||
+ response.encodeRedirectURL(
|
||||
request.getScheme()
|
||||
+ "://"
|
||||
+ request.getHeader("host")
|
||||
+ "/login?"
|
||||
+ param);
|
||||
logger.debug("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||
+ response.encodeRedirectURL(redirect_url);
|
||||
logger.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||
response.sendRedirect(logoutUrl);
|
||||
break;
|
||||
case "github":
|
||||
// Add GitHub specific logout URL if needed
|
||||
String githubLogoutUrl = "https://github.com/logout";
|
||||
logger.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||
response.sendRedirect(githubLogoutUrl);
|
||||
break;
|
||||
case "google":
|
||||
// Add Google specific logout URL if needed
|
||||
// String googleLogoutUrl =
|
||||
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||
// + response.encodeRedirectURL(redirect_url);
|
||||
// logger.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||
// response.sendRedirect(googleLogoutUrl);
|
||||
// break;
|
||||
default:
|
||||
String redirectUrl = request.getContextPath() + "/login?" + param;
|
||||
logger.debug("Redirecting to default logout URL: " + redirectUrl);
|
||||
logger.info("Redirecting to default logout URL: " + redirectUrl);
|
||||
response.sendRedirect(redirectUrl);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package stirling.software.SPDF.config.security.oauth2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
public class CustomOAuthUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomOAuthUserService.class);
|
||||
|
||||
private final OidcUserService delegate = new OidcUserService();
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
|
||||
public CustomOAuthUserService(ApplicationProperties applicationProperties) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
String usernameAttribute =
|
||||
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
|
||||
try {
|
||||
|
||||
OidcUser user = delegate.loadUser(userRequest);
|
||||
Map<String, Object> attributes = new HashMap<>(user.getAttributes());
|
||||
|
||||
// Ensure the preferred username attribute is present
|
||||
if (!attributes.containsKey(usernameAttribute)) {
|
||||
attributes.put(usernameAttribute, attributes.getOrDefault("email", ""));
|
||||
usernameAttribute = "email";
|
||||
logger.info("Adjusted username attribute to use email");
|
||||
}
|
||||
|
||||
// Return a new OidcUser with adjusted attributes
|
||||
return new DefaultOidcUser(
|
||||
user.getAuthorities(),
|
||||
userRequest.getIdToken(),
|
||||
user.getUserInfo(),
|
||||
usernameAttribute);
|
||||
} catch (java.lang.IllegalArgumentException e) {
|
||||
throw new OAuth2AuthenticationException(
|
||||
new OAuth2Error(e.getMessage()), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,9 @@ public class MergeController {
|
||||
throw ex;
|
||||
} finally {
|
||||
for (File file : filesToDelete) {
|
||||
file.delete();
|
||||
if (file != null) {
|
||||
Files.deleteIfExists(file.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ public class PdfOverlayController {
|
||||
} finally {
|
||||
for (File overlayPdfFile : overlayPdfFiles) {
|
||||
if (overlayPdfFile != null) {
|
||||
overlayPdfFile.delete();
|
||||
Files.deleteIfExists(overlayPdfFile.toPath());
|
||||
}
|
||||
}
|
||||
for (File tempFile : tempFiles) { // Delete temporary files
|
||||
if (tempFile != null) {
|
||||
tempFile.delete();
|
||||
Files.deleteIfExists(tempFile.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public class SplitPDFController {
|
||||
|
||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||
byte[] data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
|
||||
// return the Resource in the response
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
|
||||
@@ -63,10 +63,7 @@ public class SplitPdfBySectionsController {
|
||||
MergeController mergeController = new MergeController();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
mergeController.mergeDocuments(splitDocuments).save(baos);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
baos.toByteArray(),
|
||||
filename + "_split.pdf",
|
||||
MediaType.APPLICATION_OCTET_STREAM);
|
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");
|
||||
}
|
||||
for (PDDocument doc : splitDocuments) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
@@ -98,7 +95,7 @@ public class SplitPdfBySectionsController {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ConvertImgPDFController {
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
|
||||
result =
|
||||
PdfUtils.convertFromPdf(
|
||||
pdfBytes,
|
||||
@@ -65,10 +65,9 @@ public class ConvertImgPDFController {
|
||||
singleImage,
|
||||
Integer.valueOf(dpi),
|
||||
filename);
|
||||
|
||||
|
||||
if(result == null || result.length == 0) {
|
||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||
if (result == null || result.length == 0) {
|
||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||
}
|
||||
if (singleImage) {
|
||||
String docName = filename + "." + imageFormat;
|
||||
|
||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.converters;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -41,34 +40,35 @@ public class ConvertOfficeController {
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile =
|
||||
Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
|
||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Run the LibreOffice command
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"unoconv",
|
||||
"-vvv",
|
||||
"-f",
|
||||
"pdf",
|
||||
"-o",
|
||||
tempOutputFile.toString(),
|
||||
tempInputFile.toString()));
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
try {
|
||||
// Run the LibreOffice command
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"unoconv",
|
||||
"-vvv",
|
||||
"-f",
|
||||
"pdf",
|
||||
"-o",
|
||||
tempOutputFile.toString(),
|
||||
tempInputFile.toString()));
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the converted PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
|
||||
return pdfBytes;
|
||||
// Read the converted PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
return pdfBytes;
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidFileExtension(String fileExtension) {
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -26,6 +38,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
public class ConvertPDFToPDFA {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
||||
@Operation(
|
||||
summary = "Convert a PDF to a PDF/A",
|
||||
@@ -36,9 +50,39 @@ public class ConvertPDFToPDFA {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String outputFormat = request.getOutputFormat();
|
||||
|
||||
// Save the uploaded file to a temporary location
|
||||
// Convert MultipartFile to byte[]
|
||||
byte[] pdfBytes = inputFile.getBytes();
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||
|
||||
// Get the document catalog
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
|
||||
// Get the AcroForm
|
||||
PDAcroForm acroForm = catalog.getAcroForm();
|
||||
if (acroForm != null) {
|
||||
// Remove signature fields safely
|
||||
List<PDField> fieldsToRemove =
|
||||
acroForm.getFields().stream()
|
||||
.filter(field -> field instanceof PDSignatureField)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!fieldsToRemove.isEmpty()) {
|
||||
acroForm.flatten(fieldsToRemove, false);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
pdfBytes = baos.toByteArray();
|
||||
}
|
||||
}
|
||||
document.close();
|
||||
|
||||
// Save the uploaded (and possibly modified) file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
||||
outputStream.write(pdfBytes);
|
||||
}
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
@@ -58,17 +102,17 @@ public class ConvertPDFToPDFA {
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the optimized PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
byte[] optimizedPdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_PDFA.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
return WebResponseUtils.bytesToWebResponse(optimizedPdfBytes, outputFilename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class ConvertWebsiteToPDF {
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempOutputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL);
|
||||
|
||||
@@ -118,7 +118,7 @@ public class AutoSplitPdfController {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
|
||||
@@ -67,7 +67,7 @@ public class BlankPageController {
|
||||
String pageText = textStripper.getText(document);
|
||||
boolean hasText = !pageText.trim().isEmpty();
|
||||
|
||||
Boolean blank = false;
|
||||
Boolean blank = true;
|
||||
if (hasText) {
|
||||
logger.info("page " + pageIndex + " has text, not blank");
|
||||
blank = false;
|
||||
|
||||
@@ -136,10 +136,10 @@ public class CompressController {
|
||||
// Increase optimization level for next iteration
|
||||
optimizeLevel++;
|
||||
if (autoMode && optimizeLevel > 4) {
|
||||
System.out.println("Skipping level 5 due to bad results in auto mode");
|
||||
logger.info("Skipping level 5 due to bad results in auto mode");
|
||||
sizeMet = true;
|
||||
} else {
|
||||
System.out.println(
|
||||
logger.info(
|
||||
"Increasing ghostscript optimisation level to " + optimizeLevel);
|
||||
}
|
||||
}
|
||||
@@ -230,10 +230,10 @@ public class CompressController {
|
||||
if (currentSize > expectedOutputSize) {
|
||||
// Log the current file size and scaleFactor
|
||||
|
||||
System.out.println(
|
||||
logger.info(
|
||||
"Current file size: "
|
||||
+ FileUtils.byteCountToDisplaySize(currentSize));
|
||||
System.out.println("Current scale factor: " + scaleFactor);
|
||||
logger.info("Current scale factor: " + scaleFactor);
|
||||
|
||||
// The file is still too large, reduce scaleFactor and try again
|
||||
scaleFactor *= 0.9f; // reduce scaleFactor by 10%
|
||||
@@ -256,7 +256,6 @@ public class CompressController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
@@ -269,17 +268,18 @@ public class CompressController {
|
||||
// Read the original file again
|
||||
pdfBytes = Files.readAllBytes(tempInputFile);
|
||||
}
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_Optimized.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
// deleted by multipart file handler deu to transferTo?
|
||||
// Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_Optimized.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -103,10 +102,7 @@ public class ExtractImageScansController {
|
||||
}
|
||||
} else {
|
||||
tempInputFile = Files.createTempFile("input_", "." + extension);
|
||||
Files.copy(
|
||||
form.getFileInput().getInputStream(),
|
||||
tempInputFile,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
form.getFileInput().transferTo(tempInputFile);
|
||||
// Add input file path to images list
|
||||
images.add(tempInputFile.toString());
|
||||
}
|
||||
@@ -176,11 +172,15 @@ public class ExtractImageScansController {
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.delete(tempZipFile);
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
if (processedImageBytes.size() == 0) {
|
||||
throw new IllegalArgumentException("No images detected");
|
||||
} else {
|
||||
|
||||
// Return the processed image as a response
|
||||
byte[] imageBytes = processedImageBytes.get(0);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
@@ -201,7 +201,7 @@ public class ExtractImageScansController {
|
||||
|
||||
if (tempZipFile != null && Files.exists(tempZipFile)) {
|
||||
try {
|
||||
Files.delete(tempZipFile);
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -91,139 +90,145 @@ public class OCRController {
|
||||
}
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Prepare the output file path
|
||||
Path sidecarTextPath = null;
|
||||
|
||||
// Run OCR Command
|
||||
String languageOption = String.join("+", selectedLanguages);
|
||||
try {
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"ocrmypdf",
|
||||
"--verbose",
|
||||
"2",
|
||||
"--output-type",
|
||||
"pdf",
|
||||
"--pdf-renderer",
|
||||
ocrRenderType));
|
||||
// Run OCR Command
|
||||
String languageOption = String.join("+", selectedLanguages);
|
||||
|
||||
if (sidecar != null && sidecar) {
|
||||
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
||||
command.add("--sidecar");
|
||||
command.add(sidecarTextPath.toString());
|
||||
}
|
||||
|
||||
if (deskew != null && deskew) {
|
||||
command.add("--deskew");
|
||||
}
|
||||
if (clean != null && clean) {
|
||||
command.add("--clean");
|
||||
}
|
||||
if (cleanFinal != null && cleanFinal) {
|
||||
command.add("--clean-final");
|
||||
}
|
||||
if (ocrType != null && !"".equals(ocrType)) {
|
||||
if ("skip-text".equals(ocrType)) {
|
||||
command.add("--skip-text");
|
||||
} else if ("force-ocr".equals(ocrType)) {
|
||||
command.add("--force-ocr");
|
||||
} else if ("Normal".equals(ocrType)) {
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
"ocrmypdf",
|
||||
"--verbose",
|
||||
"2",
|
||||
"--output-type",
|
||||
"pdf",
|
||||
"--pdf-renderer",
|
||||
ocrRenderType));
|
||||
|
||||
if (sidecar != null && sidecar) {
|
||||
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
||||
command.add("--sidecar");
|
||||
command.add(sidecarTextPath.toString());
|
||||
}
|
||||
}
|
||||
|
||||
command.addAll(
|
||||
Arrays.asList(
|
||||
"--language",
|
||||
languageOption,
|
||||
tempInputFile.toString(),
|
||||
tempOutputFile.toString()));
|
||||
if (deskew != null && deskew) {
|
||||
command.add("--deskew");
|
||||
}
|
||||
if (clean != null && clean) {
|
||||
command.add("--clean");
|
||||
}
|
||||
if (cleanFinal != null && cleanFinal) {
|
||||
command.add("--clean-final");
|
||||
}
|
||||
if (ocrType != null && !"".equals(ocrType)) {
|
||||
if ("skip-text".equals(ocrType)) {
|
||||
command.add("--skip-text");
|
||||
} else if ("force-ocr".equals(ocrType)) {
|
||||
command.add("--force-ocr");
|
||||
} else if ("Normal".equals(ocrType)) {
|
||||
|
||||
// Run CLI command
|
||||
ProcessExecutorResult result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||
.runCommandWithOutputHandling(command);
|
||||
if (result.getRc() != 0
|
||||
&& result.getMessages().contains("multiprocessing/synchronize.py")
|
||||
&& result.getMessages().contains("OSError: [Errno 38] Function not implemented")) {
|
||||
command.add("--jobs");
|
||||
command.add("1");
|
||||
result =
|
||||
}
|
||||
}
|
||||
|
||||
command.addAll(
|
||||
Arrays.asList(
|
||||
"--language",
|
||||
languageOption,
|
||||
tempInputFile.toString(),
|
||||
tempOutputFile.toString()));
|
||||
|
||||
// Run CLI command
|
||||
ProcessExecutorResult result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||
.runCommandWithOutputHandling(command);
|
||||
}
|
||||
|
||||
// Remove images from the OCR processed PDF if the flag is set to true
|
||||
if (removeImagesAfter != null && removeImagesAfter) {
|
||||
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
|
||||
|
||||
List<String> gsCommand =
|
||||
Arrays.asList(
|
||||
"gs",
|
||||
"-sDEVICE=pdfwrite",
|
||||
"-dFILTERIMAGE",
|
||||
"-o",
|
||||
tempPdfWithoutImages.toString(),
|
||||
tempOutputFile.toString());
|
||||
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||
.runCommandWithOutputHandling(gsCommand);
|
||||
tempOutputFile = tempPdfWithoutImages;
|
||||
}
|
||||
// Read the OCR processed PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
|
||||
// Return the OCR processed PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_OCR.pdf";
|
||||
|
||||
if (sidecar != null && sidecar) {
|
||||
// Create a zip file containing both the PDF and the text file
|
||||
String outputZipFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_OCR.zip";
|
||||
Path tempZipFile = Files.createTempFile("output_", ".zip");
|
||||
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
Files.copy(tempOutputFile, zipOut);
|
||||
zipOut.closeEntry();
|
||||
|
||||
// Add text file to the zip
|
||||
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
|
||||
zipOut.putNextEntry(txtEntry);
|
||||
Files.copy(sidecarTextPath, zipOut);
|
||||
zipOut.closeEntry();
|
||||
if (result.getRc() != 0
|
||||
&& result.getMessages().contains("multiprocessing/synchronize.py")
|
||||
&& result.getMessages()
|
||||
.contains("OSError: [Errno 38] Function not implemented")) {
|
||||
command.add("--jobs");
|
||||
command.add("1");
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||
.runCommandWithOutputHandling(command);
|
||||
}
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
// Remove images from the OCR processed PDF if the flag is set to true
|
||||
if (removeImagesAfter != null && removeImagesAfter) {
|
||||
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.delete(tempZipFile);
|
||||
Files.delete(tempOutputFile);
|
||||
Files.delete(sidecarTextPath);
|
||||
List<String> gsCommand =
|
||||
Arrays.asList(
|
||||
"gs",
|
||||
"-sDEVICE=pdfwrite",
|
||||
"-dFILTERIMAGE",
|
||||
"-o",
|
||||
tempPdfWithoutImages.toString(),
|
||||
tempOutputFile.toString());
|
||||
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||
.runCommandWithOutputHandling(gsCommand);
|
||||
tempOutputFile = tempPdfWithoutImages;
|
||||
}
|
||||
// Read the OCR processed PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Return the zip file containing both the PDF and the text file
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
} else {
|
||||
// Return the OCR processed PDF as a response
|
||||
Files.delete(tempOutputFile);
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_OCR.pdf";
|
||||
|
||||
if (sidecar != null && sidecar) {
|
||||
// Create a zip file containing both the PDF and the text file
|
||||
String outputZipFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_OCR.zip";
|
||||
Path tempZipFile = Files.createTempFile("output_", ".zip");
|
||||
|
||||
try (ZipOutputStream zipOut =
|
||||
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
||||
// Add PDF file to the zip
|
||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
Files.copy(tempOutputFile, zipOut);
|
||||
zipOut.closeEntry();
|
||||
|
||||
// Add text file to the zip
|
||||
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
|
||||
zipOut.putNextEntry(txtEntry);
|
||||
Files.copy(sidecarTextPath, zipOut);
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
|
||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||
|
||||
// Clean up the temporary zip file
|
||||
Files.deleteIfExists(tempZipFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
Files.deleteIfExists(sidecarTextPath);
|
||||
|
||||
// Return the zip file containing both the PDF and the text file
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
} else {
|
||||
// Return the OCR processed PDF as a response
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
}
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
// Comment out as transferTo makes multipart handle cleanup
|
||||
// Files.deleteIfExists(tempInputFile);
|
||||
if (sidecarTextPath != null) {
|
||||
Files.deleteIfExists(sidecarTextPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,34 +41,35 @@ public class RepairController {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
byte[] pdfBytes = null;
|
||||
inputFile.transferTo(tempInputFile.toFile());
|
||||
try {
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("gs");
|
||||
command.add("-o");
|
||||
command.add(tempOutputFile.toString());
|
||||
command.add("-sDEVICE=pdfwrite");
|
||||
command.add(tempInputFile.toString());
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("gs");
|
||||
command.add("-o");
|
||||
command.add(tempOutputFile.toString());
|
||||
command.add("-sDEVICE=pdfwrite");
|
||||
command.add(tempInputFile.toString());
|
||||
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
// Read the optimized PDF file
|
||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||
|
||||
// Clean up the temporary files
|
||||
Files.delete(tempInputFile);
|
||||
Files.delete(tempOutputFile);
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_repaired.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_repaired.pdf";
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,10 +185,12 @@ public class StampController {
|
||||
try (InputStream is = classPathResource.getInputStream();
|
||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
IOUtils.copy(is, os);
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
} finally {
|
||||
if (tempFile != null) {
|
||||
Files.deleteIfExists(tempFile.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
tempFile.deleteOnExit();
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.stream.Stream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -28,6 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.SPDF.model.PipelineConfig;
|
||||
import stirling.software.SPDF.model.PipelineOperation;
|
||||
import stirling.software.SPDF.utils.FileMonitor;
|
||||
|
||||
@Service
|
||||
public class PipelineDirectoryProcessor {
|
||||
@@ -35,11 +37,18 @@ public class PipelineDirectoryProcessor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class);
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
@Autowired private ApiDocService apiDocService;
|
||||
|
||||
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
||||
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
||||
|
||||
@Autowired PipelineProcessor processor;
|
||||
@Autowired FileMonitor fileMonitor;
|
||||
|
||||
final String watchedFoldersDir;
|
||||
final String finishedFoldersDir;
|
||||
|
||||
public PipelineDirectoryProcessor(
|
||||
@Qualifier("watchedFoldersDir") String watchedFoldersDir,
|
||||
@Qualifier("finishedFoldersDir") String finishedFoldersDir) {
|
||||
this.watchedFoldersDir = watchedFoldersDir;
|
||||
this.finishedFoldersDir = finishedFoldersDir;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void scanFolders() {
|
||||
@@ -130,7 +139,11 @@ public class PipelineDirectoryProcessor {
|
||||
throws IOException {
|
||||
try (Stream<Path> paths = Files.list(dir)) {
|
||||
if ("automated".equals(operation.getParameters().get("fileInput"))) {
|
||||
return paths.filter(path -> !Files.isDirectory(path) && !path.equals(jsonFile))
|
||||
return paths.filter(
|
||||
path ->
|
||||
!Files.isDirectory(path)
|
||||
&& !path.equals(jsonFile)
|
||||
&& fileMonitor.isFileReadyForProcessing(path))
|
||||
.map(Path::toFile)
|
||||
.toArray(File[]::new);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/security")
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class RemoveCertSignController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
|
||||
@Operation(
|
||||
summary = "Remove digital signature from PDF",
|
||||
description =
|
||||
"This endpoint accepts a PDF file and returns the PDF file without the digital signature. Input: PDF, Output: PDF")
|
||||
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
|
||||
throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
|
||||
// Convert MultipartFile to byte[]
|
||||
byte[] pdfBytes = pdf.getBytes();
|
||||
|
||||
// Create a ByteArrayOutputStream to hold the resulting PDF
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
||||
|
||||
// Get the document catalog
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
|
||||
// Get the AcroForm
|
||||
PDAcroForm acroForm = catalog.getAcroForm();
|
||||
if (acroForm != null) {
|
||||
// Remove signature fields safely
|
||||
List<PDField> fieldsToRemove =
|
||||
acroForm.getFields().stream()
|
||||
.filter(field -> field instanceof PDSignatureField)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!fieldsToRemove.isEmpty()) {
|
||||
acroForm.flatten(fieldsToRemove, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the modified document to the ByteArrayOutputStream
|
||||
document.save(baos);
|
||||
document.close();
|
||||
|
||||
// Return the modified PDF as a response
|
||||
return WebResponseUtils.boasToWebResponse(
|
||||
baos,
|
||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||
+ "_unsigned.pdf");
|
||||
}
|
||||
}
|
||||
@@ -150,10 +150,10 @@ public class WatermarkController {
|
||||
try (InputStream is = classPathResource.getInputStream();
|
||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
IOUtils.copy(is, os);
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
} finally {
|
||||
if (tempFile != null) Files.deleteIfExists(tempFile.toPath());
|
||||
}
|
||||
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
tempFile.deleteOnExit();
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package stirling.software.SPDF.controller.web;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -21,6 +24,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
@@ -31,6 +39,7 @@ import stirling.software.SPDF.repository.UserRepository;
|
||||
public class AccountWebController {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccountWebController.class);
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
@@ -38,6 +47,33 @@ public class AccountWebController {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
Map<String, String> providerList = new HashMap<>();
|
||||
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||
if (oauth != null) {
|
||||
if (oauth.isSettingsValid()) {
|
||||
providerList.put("oidc", "OpenID Connect");
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client != null) {
|
||||
GoogleProvider google = client.getGoogle();
|
||||
if (google.isSettingsValid()) {
|
||||
providerList.put("google", "Google");
|
||||
}
|
||||
|
||||
GithubProvider github = client.getGithub();
|
||||
if (github.isSettingsValid()) {
|
||||
providerList.put("github", "Github");
|
||||
}
|
||||
|
||||
KeycloakProvider keycloak = client.getKeycloak();
|
||||
if (keycloak.isSettingsValid()) {
|
||||
providerList.put("keycloak", "Keycloak");
|
||||
}
|
||||
}
|
||||
}
|
||||
model.addAttribute("providerlist", providerList);
|
||||
|
||||
model.addAttribute(
|
||||
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||
|
||||
@@ -80,6 +116,21 @@ public class AccountWebController {
|
||||
break;
|
||||
case "invalid_token_response":
|
||||
erroroauth = "login.oauth2InvalidTokenResponse";
|
||||
break;
|
||||
case "authorization_request_not_found":
|
||||
erroroauth = "login.oauth2RequestNotFound";
|
||||
break;
|
||||
case "access_denied":
|
||||
erroroauth = "login.oauth2AccessDenied";
|
||||
break;
|
||||
case "invalid_user_info_response":
|
||||
erroroauth = "login.oauth2InvalidUserInfoResponse";
|
||||
break;
|
||||
case "invalid_request":
|
||||
erroroauth = "login.oauth2invalidRequest";
|
||||
break;
|
||||
case "invalid_id_token":
|
||||
erroroauth = "login.oauth2InvalidIdToken";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,13 @@ public class SecurityWebController {
|
||||
return "security/cert-sign";
|
||||
}
|
||||
|
||||
@GetMapping("/remove-cert-sign")
|
||||
@Hidden
|
||||
public String certUnSignForm(Model model) {
|
||||
model.addAttribute("currentPage", "remove-cert-sign");
|
||||
return "security/remove-cert-sign";
|
||||
}
|
||||
|
||||
@GetMapping("/sanitize-pdf")
|
||||
@Hidden
|
||||
public String sanitizeForm(Model model) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
@@ -23,6 +25,7 @@ public class ApplicationProperties {
|
||||
private Metrics metrics;
|
||||
private AutomaticallyGenerated automaticallyGenerated;
|
||||
private AutoPipeline autoPipeline;
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
|
||||
|
||||
public AutoPipeline getAutoPipeline() {
|
||||
return autoPipeline != null ? autoPipeline : new AutoPipeline();
|
||||
@@ -182,13 +185,12 @@ public class ApplicationProperties {
|
||||
+ oauth2
|
||||
+ ", initialLogin="
|
||||
+ initialLogin
|
||||
+ ", csrfDisabled="
|
||||
+ ", csrfDisabled="
|
||||
+ csrfDisabled
|
||||
+ "]";
|
||||
}
|
||||
|
||||
public static class InitialLogin {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
@@ -219,22 +221,21 @@ public class ApplicationProperties {
|
||||
}
|
||||
|
||||
public static class OAUTH2 {
|
||||
|
||||
private boolean enabled;
|
||||
private Boolean enabled = false;
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private boolean autoCreateUser;
|
||||
private Boolean autoCreateUser = false;
|
||||
private String useAsUsername;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String provider;
|
||||
private Client client = new Client();
|
||||
|
||||
private Collection<String> scopes = new ArrayList<String>();
|
||||
|
||||
public boolean getEnabled() {
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@@ -262,19 +263,16 @@ public class ApplicationProperties {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public boolean getAutoCreateUser() {
|
||||
public Boolean getAutoCreateUser() {
|
||||
return autoCreateUser;
|
||||
}
|
||||
|
||||
public void setAutoCreateUser(boolean autoCreateUser) {
|
||||
public void setAutoCreateUser(Boolean autoCreateUser) {
|
||||
this.autoCreateUser = autoCreateUser;
|
||||
}
|
||||
|
||||
public String getUseAsUsername() {
|
||||
if (useAsUsername != null && useAsUsername.trim().length() > 0) {
|
||||
return useAsUsername;
|
||||
}
|
||||
return "email";
|
||||
return useAsUsername;
|
||||
}
|
||||
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
@@ -293,14 +291,44 @@ public class ApplicationProperties {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(String scpoes) {
|
||||
public void setScopes(String scopes) {
|
||||
List<String> scopesList =
|
||||
Arrays.stream(scpoes.split(","))
|
||||
Arrays.stream(scopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toList());
|
||||
this.scopes.addAll(scopesList);
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
protected boolean isValid(String value, String name) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isValid(Collection<String> value, String name) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return isValid(this.getIssuer(), "issuer")
|
||||
&& isValid(this.getClientId(), "clientId")
|
||||
&& isValid(this.getClientSecret(), "clientSecret")
|
||||
&& isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OAUTH2 [enabled="
|
||||
@@ -315,12 +343,353 @@ public class ApplicationProperties {
|
||||
+ autoCreateUser
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ ", provider"
|
||||
+ ", provider="
|
||||
+ provider
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ "]";
|
||||
}
|
||||
|
||||
public static class Client {
|
||||
private GoogleProvider google = new GoogleProvider();
|
||||
private GithubProvider github = new GithubProvider();
|
||||
private KeycloakProvider keycloak = new KeycloakProvider();
|
||||
|
||||
public Provider get(String registrationId) throws Exception {
|
||||
switch (registrationId) {
|
||||
case "google":
|
||||
return getGoogle();
|
||||
case "github":
|
||||
return getGithub();
|
||||
case "keycloak":
|
||||
return getKeycloak();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new Exception("Provider not supported, use custom setting.");
|
||||
}
|
||||
|
||||
public GoogleProvider getGoogle() {
|
||||
return google;
|
||||
}
|
||||
|
||||
public void setGoogle(GoogleProvider google) {
|
||||
this.google = google;
|
||||
}
|
||||
|
||||
public GithubProvider getGithub() {
|
||||
return github;
|
||||
}
|
||||
|
||||
public void setGithub(GithubProvider github) {
|
||||
this.github = github;
|
||||
}
|
||||
|
||||
public KeycloakProvider getKeycloak() {
|
||||
return keycloak;
|
||||
}
|
||||
|
||||
public void setKeycloak(KeycloakProvider keycloak) {
|
||||
this.keycloak = keycloak;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Client [google="
|
||||
+ google
|
||||
+ ", github="
|
||||
+ github
|
||||
+ ", keycloak="
|
||||
+ keycloak
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GoogleProvider extends Provider {
|
||||
|
||||
private static final String authorizationUri =
|
||||
"https://accounts.google.com/o/oauth2/v2/auth";
|
||||
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token";
|
||||
private static final String userInfoUri =
|
||||
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Google [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "google";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class GithubProvider extends Provider {
|
||||
private static final String authorizationUri = "https://github.com/login/oauth/authorize";
|
||||
private static final String tokenUri = "https://github.com/login/oauth/access_token";
|
||||
private static final String userInfoUri = "https://api.github.com/user";
|
||||
|
||||
public String getAuthorizationuri() {
|
||||
return authorizationUri;
|
||||
}
|
||||
|
||||
public String getTokenuri() {
|
||||
return tokenUri;
|
||||
}
|
||||
|
||||
public String getUserinfouri() {
|
||||
return userInfoUri;
|
||||
}
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "login";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return new String();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes.add("read:user");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GitHub [clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "github";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return super.isValid(this.getClientId(), "clientId")
|
||||
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||
&& super.isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeycloakProvider extends Provider {
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private Collection<String> scopes = new ArrayList<>();
|
||||
private String useAsUsername = "email";
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
if (scopes == null || scopes.isEmpty()) {
|
||||
scopes.add("openid");
|
||||
scopes.add("profile");
|
||||
scopes.add("email");
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(String scopes) {
|
||||
this.scopes =
|
||||
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
return this.useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
this.useAsUsername = useAsUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Keycloak [issuer="
|
||||
+ issuer
|
||||
+ ", clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", scopes="
|
||||
+ scopes
|
||||
+ ", useAsUsername="
|
||||
+ useAsUsername
|
||||
+ "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "keycloak";
|
||||
}
|
||||
|
||||
public boolean isSettingsValid() {
|
||||
return isValid(this.getIssuer(), "issuer")
|
||||
&& isValid(this.getClientId(), "clientId")
|
||||
&& isValid(this.getClientSecret(), "clientSecret")
|
||||
&& isValid(this.getScopes(), "scopes")
|
||||
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,9 +750,6 @@ public class ApplicationProperties {
|
||||
this.googlevisibility = googlevisibility;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "System [defaultLocale="
|
||||
|
||||
87
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
87
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class Provider implements ProviderInterface {
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected boolean isValid(String value, String name) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||
}
|
||||
|
||||
protected boolean isValid(Collection<String> value, String name) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopes() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScopes(String scopes) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUseAsUsername() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseAsUsername(String useAsUsername) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIssuer() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIssuer(String issuer) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientSecret(String clientSecret) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface ProviderInterface {
|
||||
|
||||
public Collection<String> getScopes();
|
||||
|
||||
public void setScopes(String scopes);
|
||||
|
||||
public String getUseAsUsername();
|
||||
|
||||
public void setUseAsUsername(String useAsUsername);
|
||||
|
||||
public String getIssuer();
|
||||
|
||||
public void setIssuer(String issuer);
|
||||
|
||||
public String getClientSecret();
|
||||
|
||||
public void setClientSecret(String clientSecret);
|
||||
|
||||
public String getClientId();
|
||||
|
||||
public void setClientId(String clientId);
|
||||
}
|
||||
168
src/main/java/stirling/software/SPDF/utils/FileMonitor.java
Normal file
168
src/main/java/stirling/software/SPDF/utils/FileMonitor.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import static java.nio.file.StandardWatchEventKinds.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class FileMonitor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileMonitor.class);
|
||||
private final Map<Path, WatchKey> path2KeyMapping;
|
||||
private final Set<Path> newlyDiscoveredFiles;
|
||||
private final ConcurrentHashMap.KeySetView<Path, Boolean> readyForProcessingFiles;
|
||||
private final WatchService watchService;
|
||||
private final Predicate<Path> pathFilter;
|
||||
private final Path rootDir;
|
||||
private Set<Path> stagingFiles;
|
||||
|
||||
/**
|
||||
* @param rootDirectory the root directory to monitor
|
||||
* @param pathFilter the filter to apply to the paths, return true if the path should be
|
||||
* monitored, false otherwise
|
||||
*/
|
||||
@Autowired
|
||||
public FileMonitor(
|
||||
@Qualifier("watchedFoldersDir") String rootDirectory,
|
||||
@Qualifier("directoryFilter") Predicate<Path> pathFilter)
|
||||
throws IOException {
|
||||
this.newlyDiscoveredFiles = new HashSet<>();
|
||||
this.path2KeyMapping = new HashMap<>();
|
||||
this.stagingFiles = new HashSet<>();
|
||||
this.pathFilter = pathFilter;
|
||||
this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();
|
||||
this.watchService = FileSystems.getDefault().newWatchService();
|
||||
this.rootDir = Path.of(rootDirectory);
|
||||
}
|
||||
|
||||
private boolean shouldNotProcess(Path path) {
|
||||
return !pathFilter.test(path);
|
||||
}
|
||||
|
||||
private void recursivelyRegisterEntry(Path dir) throws IOException {
|
||||
WatchKey key = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
||||
path2KeyMapping.put(dir, key);
|
||||
logger.info("Registered directory: {}", dir);
|
||||
|
||||
try (Stream<Path> directoryVisitor = Files.walk(dir, 1)) {
|
||||
final Iterator<Path> iterator = directoryVisitor.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Path path = iterator.next();
|
||||
if (path.equals(dir) || shouldNotProcess(path)) continue;
|
||||
|
||||
if (Files.isDirectory(path)) {
|
||||
recursivelyRegisterEntry(path);
|
||||
} else if (Files.isRegularFile(path)) {
|
||||
handleFileCreation(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 5000)
|
||||
public void trackFiles() {
|
||||
/*
|
||||
All files observed changes in the last iteration will be considered as staging files.
|
||||
If those files are not modified in current iteration, they will be considered as ready for processing.
|
||||
*/
|
||||
stagingFiles = new HashSet<>(newlyDiscoveredFiles);
|
||||
readyForProcessingFiles.clear();
|
||||
|
||||
if (path2KeyMapping.isEmpty()) {
|
||||
logger.warn(
|
||||
"not monitoring any directory, even the root directory itself: {}", rootDir);
|
||||
if (Files.exists(
|
||||
rootDir)) { // if the root directory exists, re-register the root directory
|
||||
try {
|
||||
recursivelyRegisterEntry(rootDir);
|
||||
} catch (IOException e) {
|
||||
logger.error("unable to register monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WatchKey key;
|
||||
while ((key = watchService.poll()) != null) {
|
||||
final Path watchingDir = (Path) key.watchable();
|
||||
key.pollEvents()
|
||||
.forEach(
|
||||
(evt) -> {
|
||||
final Path path = (Path) evt.context();
|
||||
final WatchEvent.Kind<?> kind = evt.kind();
|
||||
if (shouldNotProcess(path)) return;
|
||||
|
||||
try {
|
||||
if (Files.isDirectory(path)) {
|
||||
if (kind == ENTRY_CREATE) {
|
||||
handleDirectoryCreation(path);
|
||||
}
|
||||
/*
|
||||
we don't need to handle directory deletion or modification
|
||||
- directory deletion will be handled by key.reset()
|
||||
- directory modification indicates a new file creation or deletion, which is handled by below
|
||||
*/
|
||||
}
|
||||
Path relativePathFromRoot = watchingDir.resolve(path);
|
||||
if (kind == ENTRY_CREATE) {
|
||||
handleFileCreation(relativePathFromRoot);
|
||||
} else if (kind == ENTRY_DELETE) {
|
||||
handleFileRemoval(relativePathFromRoot);
|
||||
} else if (kind == ENTRY_MODIFY) {
|
||||
handleFileModification(relativePathFromRoot);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error while processing file: {}", path, e);
|
||||
}
|
||||
});
|
||||
|
||||
boolean isKeyValid = key.reset();
|
||||
if (!isKeyValid) { // key is invalid when the directory itself is no longer exists
|
||||
path2KeyMapping.remove((Path) key.watchable());
|
||||
}
|
||||
}
|
||||
readyForProcessingFiles.addAll(stagingFiles);
|
||||
}
|
||||
|
||||
private void handleDirectoryCreation(Path dir) throws IOException {
|
||||
WatchKey key = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
|
||||
path2KeyMapping.put(dir, key);
|
||||
}
|
||||
|
||||
private void handleFileRemoval(Path path) {
|
||||
newlyDiscoveredFiles.remove(path);
|
||||
stagingFiles.remove(path);
|
||||
}
|
||||
|
||||
private void handleFileCreation(Path path) {
|
||||
newlyDiscoveredFiles.add(path);
|
||||
stagingFiles.remove(path);
|
||||
}
|
||||
|
||||
private void handleFileModification(Path path) {
|
||||
// the logic is the same
|
||||
handleFileCreation(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is ready for processing.
|
||||
*
|
||||
* <p>A file is ready for processing if it is not being modified for 5000ms.
|
||||
*
|
||||
* @param path the path of the file
|
||||
* @return true if the file is ready for processing, false otherwise
|
||||
*/
|
||||
public boolean isFileReadyForProcessing(Path path) {
|
||||
return readyForProcessingFiles.contains(path);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public class FileToPdf {
|
||||
List<String> command = new ArrayList<>();
|
||||
if (!htmlFormatsInstalled) {
|
||||
command.add("weasyprint");
|
||||
command.add("-e utf-8");
|
||||
command.add(tempInputFile.toString());
|
||||
command.add(tempOutputFile.toString());
|
||||
|
||||
@@ -79,8 +80,8 @@ public class FileToPdf {
|
||||
} finally {
|
||||
|
||||
// Clean up temporary files
|
||||
Files.delete(tempOutputFile);
|
||||
Files.delete(tempInputFile);
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
}
|
||||
|
||||
return pdfBytes;
|
||||
|
||||
@@ -38,14 +38,14 @@ public class GeneralUtils {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
Files.delete(file);
|
||||
Files.deleteIfExists(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
||||
throws IOException {
|
||||
Files.delete(dir);
|
||||
Files.deleteIfExists(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -34,7 +33,10 @@ public class PDFToFile {
|
||||
|
||||
// Get the original PDF file name without the extension
|
||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
String pdfBaseName = originalPdfFileName;
|
||||
if (originalPdfFileName.contains(".")) {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
Path tempInputFile = null;
|
||||
Path tempOutputDir = null;
|
||||
@@ -44,8 +46,7 @@ public class PDFToFile {
|
||||
try {
|
||||
// Save the uploaded file to a temporary location
|
||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
Files.copy(
|
||||
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Prepare the output directory
|
||||
tempOutputDir = Files.createTempDirectory("output_");
|
||||
@@ -82,7 +83,7 @@ public class PDFToFile {
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.delete(tempInputFile);
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
|
||||
@@ -100,8 +101,15 @@ public class PDFToFile {
|
||||
|
||||
// Get the original PDF file name without the extension
|
||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
|
||||
if (originalPdfFileName == null || "".equals(originalPdfFileName.trim())) {
|
||||
originalPdfFileName = "output.pdf";
|
||||
}
|
||||
// Assume file is pdf if no extension
|
||||
String pdfBaseName = originalPdfFileName;
|
||||
if (originalPdfFileName.contains(".")) {
|
||||
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||
}
|
||||
// Validate output format
|
||||
List<String> allowedFormats =
|
||||
Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "xml", "txt:Text");
|
||||
@@ -117,8 +125,7 @@ public class PDFToFile {
|
||||
try {
|
||||
// Save the uploaded file to a temporary location
|
||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
Files.copy(
|
||||
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
inputFile.transferTo(tempInputFile);
|
||||
|
||||
// Prepare the output directory
|
||||
tempOutputDir = Files.createTempDirectory("output_");
|
||||
@@ -170,9 +177,10 @@ public class PDFToFile {
|
||||
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.delete(tempInputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
System.out.println("fileBytes=" + fileBytes.length);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ public class RequestUriUtils {
|
||||
public static boolean isStaticResource(String requestURI) {
|
||||
|
||||
return requestURI.startsWith("/css/")
|
||||
|| requestURI.startsWith("/fonts/")
|
||||
|| requestURI.startsWith("/js/")
|
||||
|| requestURI.startsWith("/images/")
|
||||
|| requestURI.startsWith("/public/")
|
||||
|
||||
15
src/main/java/stirling/software/SPDF/utils/UrlUtils.java
Normal file
15
src/main/java/stirling/software/SPDF/utils/UrlUtils.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public class UrlUtils {
|
||||
|
||||
public static String getOrigin(HttpServletRequest request) {
|
||||
String scheme = request.getScheme(); // http or https
|
||||
String serverName = request.getServerName(); // localhost
|
||||
int serverPort = request.getServerPort(); // 8080
|
||||
String contextPath = request.getContextPath(); // /myapp
|
||||
|
||||
return scheme + "://" + serverName + ":" + serverPort + contextPath;
|
||||
}
|
||||
}
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Sign with Certificate
|
||||
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
||||
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=الاسم
|
||||
certSign.submit=تسجيل PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=إزالة الفراغات
|
||||
removeBlanks.header=إزالة الصفحات الفارغة
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF / A.
|
||||
pdfToPDFA.submit=تحويل
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -59,8 +59,8 @@ deleteCurrentUserMessage=Не може да се изтрие вписания
|
||||
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
|
||||
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
|
||||
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
|
||||
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
||||
userAlreadyExistsWebMessage=The user already exists as an web user.
|
||||
userAlreadyExistsOAuthMessage=Потребителят вече съществува като OAuth2 потребител.
|
||||
userAlreadyExistsWebMessage=Потребителят вече съществува като уеб-потребител.
|
||||
error=Грешка
|
||||
oops=Опаа!
|
||||
help=Помощ
|
||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=Валидирай
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.favorite=Любими
|
||||
navbar.darkmode=Тъмна тема
|
||||
navbar.language=Languages
|
||||
navbar.language=Езици
|
||||
navbar.settings=Настройки
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.allTools=Инструменти
|
||||
navbar.multiTool=Мулти инструменти
|
||||
navbar.sections.organize=Организирайте
|
||||
navbar.sections.convertTo=Преобразуване в PDF
|
||||
navbar.sections.convertFrom=Преобразуване от PDF
|
||||
navbar.sections.security=Подписване и сигурност
|
||||
navbar.sections.advance=Разширено
|
||||
navbar.sections.edit=Преглед и редактиране
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -185,7 +185,7 @@ adminUserSettings.internalApiUser=Вътрешен API потребител
|
||||
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
|
||||
adminUserSettings.submit=Съхранете потребителя
|
||||
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
adminUserSettings.authenticated=Удостоверен
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -267,7 +267,7 @@ home.fileToPDF.desc=Преобразуване почти всеки файл к
|
||||
fileToPDF.tags=трансформация,формат,документ,изображение,слайд,текст,преобразуване,офис,документи,word,excel,powerpoint
|
||||
|
||||
home.ocr.title=OCR / Почистващи сканирания
|
||||
home.ocr.desc=Почистване, сканира и открива текст от изображения към PDF и го добавя отново като текст.
|
||||
home.ocr.desc=Почиства, сканира и открива текст от изображения в PDF и го добавя отново като текст.
|
||||
ocr.tags=разпознаване,текст,изображение,сканиране,четене,идентифициране,откриване,редактиране
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ home.flatten.desc=Премахнете всички интерактивни е
|
||||
flatten.tags=статичен,деактивиран,неинтерактивен,рационализиран
|
||||
|
||||
home.repair.title=Поправи
|
||||
home.repair.desc=Опитва се да поправи повреден/счупен PDF
|
||||
home.repair.desc=Опитва се да поправи повреден PDF
|
||||
repair.tags=поправка,възстановяване,корекция,възстановяване
|
||||
|
||||
home.removeBlanks.title=Премахване на празни страници
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Подпишете със сертификат
|
||||
home.certSign.desc=Подписва PDF със сертификат/ключ (PEM/P12)
|
||||
certSign.tags=удостоверяване,PEM,P12,официален,шифроване
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Оформление с няколко страници
|
||||
home.pageLayout.desc=Слейте няколко страници от PDF документ в една страница
|
||||
pageLayout.tags=сливане,комбиниран,единичен изглед,организиране
|
||||
@@ -364,7 +368,7 @@ home.autoSplitPDF.title=Автоматично разделяне на стра
|
||||
home.autoSplitPDF.desc=Автоматично разделяне на сканиран PDF файл с QR код за разделяне на физически сканирани страници
|
||||
autoSplitPDF.tags=QR-базиран,отделен,сканиране-сегмент,организиране
|
||||
|
||||
home.sanitizePdf.title=Дезинфенкцирам
|
||||
home.sanitizePdf.title=Обеззаразяване
|
||||
home.sanitizePdf.desc=Премахване на скриптове и други елементи от PDF файлове
|
||||
sanitizePdf.tags=чисти,сигурни,безопасни,премахване-заплахи
|
||||
|
||||
@@ -382,8 +386,8 @@ home.MarkdownToPDF.desc=Преобразува всеки Markdown файл къ
|
||||
MarkdownToPDF.tags=маркиране,уеб-съдържание,трансформация,преобразуване
|
||||
|
||||
|
||||
home.getPdfInfo.title=Вземете ЦЯЛАТА информация към PDF
|
||||
home.getPdfInfo.desc=Взема всяка възможна информация от PDF файлове
|
||||
home.getPdfInfo.title=Вземете ЦЯЛАТА информация от PDF
|
||||
home.getPdfInfo.desc=Взима всяка възможна информация от PDF файлове
|
||||
getPdfInfo.tags=информация,данни,статистики,статистика
|
||||
|
||||
|
||||
@@ -405,7 +409,7 @@ home.autoRedact.title=Автоматично редактиране
|
||||
home.autoRedact.desc=Автоматично редактира (зачернява) текст в PDF въз основа на въведен текст
|
||||
autoRedact.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
|
||||
|
||||
home.tableExtraxt.title=PDF to CSV
|
||||
home.tableExtraxt.title=PDF в CSV
|
||||
home.tableExtraxt.desc=Извлича таблици от PDF, като ги конвертира в CSV
|
||||
tableExtraxt.tags=CSV,извличане на таблица,извличане,конвертиране
|
||||
|
||||
@@ -452,6 +456,12 @@ login.locked=Вашият акаунт е заключен.
|
||||
login.signinTitle=Моля впишете се
|
||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
||||
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
||||
login.oauth2invalidRequest=Невалидна заявка
|
||||
login.oauth2AccessDenied=Отказан достъп
|
||||
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
||||
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Изтегли
|
||||
|
||||
#crop
|
||||
crop.title=Изрязване
|
||||
crop.header=Изрязване на изображение
|
||||
crop.header=Изрязване на PDF
|
||||
crop.submit=Подайте
|
||||
|
||||
|
||||
@@ -632,7 +642,7 @@ scalePages.submit=Подайте
|
||||
|
||||
|
||||
#certSign
|
||||
certSign.title=Подписване на сертификат
|
||||
certSign.title=Подписване със сертификат
|
||||
certSign.header=Подпишете PDF с вашия сертификат (В процес на работа)
|
||||
certSign.selectPDF=Изберете PDF файл за подписване:
|
||||
certSign.jksNote=Забележка: Ако вашият тип сертификат не е в списъка по-долу, моля, конвертирайте го във файл на Java Keystore (.jks) с помощта на инструмента за команден ред keytool. След това изберете опцията за .jks файл по-долу.
|
||||
@@ -649,6 +659,13 @@ certSign.name=Име
|
||||
certSign.submit=Подпишете PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Премахване на празни места
|
||||
removeBlanks.header=Премахване на празни страници
|
||||
@@ -752,7 +769,7 @@ extractImages.submit=Извличане
|
||||
fileToPDF.title=Файл към PDF
|
||||
fileToPDF.header=Конвертирайте всеки файл към PDF
|
||||
fileToPDF.credit=Тази услуга използва LibreOffice и Unoconv за преобразуване на файлове.
|
||||
fileToPDF.supportedFileTypesInfo=Supported File types
|
||||
fileToPDF.supportedFileTypesInfo=Поддържание файлови типове
|
||||
fileToPDF.supportedFileTypes=Поддържаните типове файлове трябва да включват по-долу, но за пълен актуализиран списък на поддържаните формати, моля, вижте документацията на LibreOffice
|
||||
fileToPDF.submit=Преобразуване към PDF
|
||||
|
||||
@@ -761,10 +778,10 @@ fileToPDF.submit=Преобразуване към PDF
|
||||
compress.title=Компресиране
|
||||
compress.header=Компресиране на PDF
|
||||
compress.credit=Тази услуга използва Ghostscript за PDF компресиране/оптимизиране.
|
||||
compress.selectText.1=Ръчен режим - От 1 до 4
|
||||
compress.selectText.1=Ръчен режим - от 1 до 4
|
||||
compress.selectText.2=Ниво на оптимизация:
|
||||
compress.selectText.3=4 (Ужасно за текстови изображения)
|
||||
compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF точен размер
|
||||
compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF с точен размер
|
||||
compress.selectText.5=Очакван PDF размер (напр. 25МБ, 10.8МБ, 25КБ)
|
||||
compress.submit=Компресиране
|
||||
|
||||
@@ -805,7 +822,7 @@ pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
||||
#multiTool
|
||||
multiTool.title=PDF Мулти инструмент
|
||||
multiTool.header=PDF Мулти инструмент
|
||||
multiTool.uploadPrompts=File Name
|
||||
multiTool.uploadPrompts=Име на файл
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=Преглед на PDF
|
||||
@@ -904,8 +921,8 @@ watermark.selectText.7=Непрозрачност (0% - 100%):
|
||||
watermark.selectText.8=Тип воден знак:
|
||||
watermark.selectText.9=Изображение за воден знак:
|
||||
watermark.submit=Добавяне на воден знак
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
watermark.type.1=Текст
|
||||
watermark.type.2=Изображение
|
||||
|
||||
|
||||
#Change permissions
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A пр
|
||||
pdfToPDFA.submit=Преобразуване
|
||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||
pdfToPDFA.outputFormat=Изходен формат
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Signa amb Certificat
|
||||
home.certSign.desc=Sign PDF amb Certificate/Clau (PEM/P12)
|
||||
certSign.tags=authentica,PEM,P12,official,encripta
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Compte bloquejat
|
||||
login.signinTitle=Autenticat
|
||||
login.ssoSignIn=Inicia sessió mitjançant l'inici de sessió ún
|
||||
login.oauth2AutoCreateDisabled=L'usuari de creació automàtica OAUTH2 està desactivat
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Talla
|
||||
crop.header=Talla Imatge
|
||||
crop.header=Talla PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nom
|
||||
certSign.submit=Firma PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Elimina els espais en blanc
|
||||
removeBlanks.header=Elimina les pàgines en blanc
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A
|
||||
pdfToPDFA.submit=Converteix
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -59,8 +59,8 @@ deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht w
|
||||
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
|
||||
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
|
||||
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
|
||||
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
||||
userAlreadyExistsWebMessage=The user already exists as an web user.
|
||||
userAlreadyExistsOAuthMessage=Der Benutzer ist bereits als OAuth2-Benutzer vorhanden.
|
||||
userAlreadyExistsWebMessage=Der Benutzer ist bereits als Webbenutzer vorhanden.
|
||||
error=Fehler
|
||||
oops=Hoppla!
|
||||
help=Hilfe
|
||||
@@ -71,7 +71,7 @@ visitGithub=GitHub-Repository besuchen
|
||||
donate=Spenden
|
||||
color=Farbe
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
info=Die Info
|
||||
|
||||
|
||||
|
||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=Validieren
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.favorite=Favoriten
|
||||
navbar.darkmode=Dunkler Modus
|
||||
navbar.language=Languages
|
||||
navbar.language=Sprachen
|
||||
navbar.settings=Einstellungen
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.allTools=Werkzeuge
|
||||
navbar.multiTool=Multitools
|
||||
navbar.sections.organize=Organisieren
|
||||
navbar.sections.convertTo=In PDF konvertieren
|
||||
navbar.sections.convertFrom=Konvertieren von PDF
|
||||
navbar.sections.security=Zeichen und Sicherheit
|
||||
navbar.sections.advance=Fortschrittlich
|
||||
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -185,7 +185,7 @@ adminUserSettings.internalApiUser=Interner API-Benutzer
|
||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
||||
adminUserSettings.submit=Benutzer speichern
|
||||
adminUserSettings.changeUserRole=Benutzerrolle ändern
|
||||
adminUserSettings.authenticated=Authenticated
|
||||
adminUserSettings.authenticated=Authentifiziert
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Mit Zertifikat signieren
|
||||
home.certSign.desc=Ein PDF mit einem Zertifikat/Schlüssel (PEM/P12) signieren
|
||||
certSign.tags=authentifizieren,pem,p12,offiziell,verschlüsseln
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Mehrseitiges Layout
|
||||
home.pageLayout.desc=Mehrere Seiten eines PDF zu einer Seite zusammenführen
|
||||
pageLayout.tags=zusammenführen,zusammensetzen,einzelansicht,organisieren
|
||||
@@ -452,6 +456,12 @@ login.locked=Ihr Konto wurde gesperrt.
|
||||
login.signinTitle=Bitte melden Sie sich an.
|
||||
login.ssoSignIn=Anmeldung per Single Sign-On
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
||||
login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
|
||||
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
||||
login.oauth2invalidRequest=ungültige Anfrage
|
||||
login.oauth2AccessDenied=Zugriff abgelehnt
|
||||
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
|
||||
login.oauth2InvalidIdToken=Ungültiges ID-Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Herunterladen
|
||||
|
||||
#crop
|
||||
crop.title=Zuschneiden
|
||||
crop.header=Bild zuschneiden
|
||||
crop.header=PDF zuschneiden
|
||||
crop.submit=Abschicken
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Name
|
||||
certSign.submit=PDF signieren
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Leere Seiten entfernen
|
||||
removeBlanks.header=Leere Seiten entfernen
|
||||
@@ -704,7 +721,7 @@ repair.submit=Reparieren
|
||||
#flatten
|
||||
flatten.title=Abflachen
|
||||
flatten.header=PDFs reduzieren
|
||||
flatten.flattenOnlyForms=Flatten only forms
|
||||
flatten.flattenOnlyForms=Nur Formulare abflachen
|
||||
flatten.submit=Abflachen
|
||||
|
||||
|
||||
@@ -752,7 +769,7 @@ extractImages.submit=Extrahieren
|
||||
fileToPDF.title=Datei in PDF
|
||||
fileToPDF.header=Beliebige Dateien in PDF konvertieren
|
||||
fileToPDF.credit=Dieser Dienst verwendet LibreOffice und Unoconv für die Dateikonvertierung.
|
||||
fileToPDF.supportedFileTypesInfo=Supported File types
|
||||
fileToPDF.supportedFileTypesInfo=Unterstützte Dateitypen
|
||||
fileToPDF.supportedFileTypes=Unterstützte Dateitypen sollten die folgenden enthalten, eine vollständige aktualisierte Liste der unterstützten Formate finden Sie jedoch in der LibreOffice-Dokumentation
|
||||
fileToPDF.submit=In PDF konvertieren
|
||||
|
||||
@@ -805,7 +822,7 @@ pdfOrganiser.placeholder=(z.B. 1,3,2 oder 4-8,2,10-12 oder 2n-1)
|
||||
#multiTool
|
||||
multiTool.title=PDF-Multitool
|
||||
multiTool.header=PDF-Multitool
|
||||
multiTool.uploadPrompts=File Name
|
||||
multiTool.uploadPrompts=Dateiname
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=PDF anzeigen
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung
|
||||
pdfToPDFA.submit=Konvertieren
|
||||
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
||||
pdfToPDFA.outputFormat=Ausgabeformat
|
||||
pdfToPDFA.pdfWithDigitalSignature=Das PDF enthält eine digitale Signatur. Sie wird im nächsten Schritt entfernt.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Υπογραφή με Πιστοποιητικό
|
||||
home.certSign.desc=Υπογραφή ενός PDF αρχείου με ένα Πιστοποιητικό/Κλειδί (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Διάταξη πολλών σελίδων
|
||||
home.pageLayout.desc=Συγχώνευση πολλαπλών σελίδων ενός εγγράφου PDF σε μία μόνο σελίδα
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
||||
login.signinTitle=Παρακαλώ, συνδεθείτε
|
||||
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
||||
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Λήψη
|
||||
|
||||
#crop
|
||||
crop.title=Κοπή
|
||||
crop.header=Κοπή Εικόνας
|
||||
crop.header=Περικοπή PDF
|
||||
crop.submit=Υποβολή
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Όνομα
|
||||
certSign.submit=Υπογραφή PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Αφαίρεση Κενών
|
||||
removeBlanks.header=Αφαίρεση Κενών Σελίδων
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF
|
||||
pdfToPDFA.submit=Μετατροπή
|
||||
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Sign with Certificate
|
||||
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Name
|
||||
certSign.submit=Sign PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Remove Blanks
|
||||
removeBlanks.header=Remove Blank Pages
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Sign with Certificate
|
||||
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Name
|
||||
certSign.submit=Sign PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Remove Blanks
|
||||
removeBlanks.header=Remove Blank Pages
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Firmar con certificado
|
||||
home.certSign.desc=Firmar un PDF con un Certificado/Clave (PEM/P12)
|
||||
certSign.tags=autentificar,PEM,P12,oficial,encriptar
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Diseño de varias páginas
|
||||
home.pageLayout.desc=Unir varias páginas de un documento PDF en una sola página
|
||||
pageLayout.tags=unir,compuesto,vista única,organizar
|
||||
@@ -452,6 +456,12 @@ login.locked=Su cuenta se ha bloqueado.
|
||||
login.signinTitle=Por favor, inicie sesión
|
||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
||||
login.oauth2AutoCreateDisabled=Usuario DE creación automática de OAUTH2 DESACTIVADO
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Descargar
|
||||
|
||||
#crop
|
||||
crop.title=Recortar
|
||||
crop.header=Recortar Imagen
|
||||
crop.header=Recortar PDF
|
||||
crop.submit=Entregar
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nombre
|
||||
certSign.submit=Firmar PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Eliminar espacios en blanco
|
||||
removeBlanks.header=Eliminar páginas en blanco
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Sinatu ziurtagiriarekin
|
||||
home.certSign.desc=Sinatu PDF bat Ziurtagiri/Gako batekin (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Zenbait orrialderen diseinua
|
||||
home.pageLayout.desc=Elkartu orri bakar batean PDF dokumentu baten zenbait orrialde
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Zure kontua blokeatu egin da.
|
||||
login.signinTitle=Mesedez, hasi saioa
|
||||
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Distira
|
||||
|
||||
#crop
|
||||
crop.title=Moztu
|
||||
crop.header=Irudia Moztu
|
||||
crop.header=Moztu PDF
|
||||
crop.submit=Bidali
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Izena
|
||||
certSign.submit=Sinatu PDFa
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Ezabatu zuriuneak
|
||||
removeBlanks.header=Ezabatu orrialde zuriak
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko
|
||||
pdfToPDFA.submit=Bihurtu
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -17,23 +17,23 @@ true=Vrai
|
||||
false=Faux
|
||||
unknown=Inconnu
|
||||
save=Enregistrer
|
||||
saveToBrowser=Enregistrer dans le navigateur
|
||||
saveToBrowser=Enregistrer dans le navigateur
|
||||
close=Fermer
|
||||
filesSelected=fichiers sélectionnés
|
||||
noFavourites=Aucun favori ajouté
|
||||
downloadComplete=Téléchargement terminé
|
||||
bored=Ennuyé d’attendre ?
|
||||
bored=Marre d’attendre ?
|
||||
alphabet=Alphabet
|
||||
downloadPdf=Télécharger le PDF
|
||||
text=Texte
|
||||
font=Police
|
||||
selectFillter=-- Sélectionnez --
|
||||
pageNum=numéro de page
|
||||
pageNum=Numéro de page
|
||||
sizes.small=Petit
|
||||
sizes.medium=Moyen
|
||||
sizes.large=Grand
|
||||
sizes.x-large=Très grand
|
||||
error.pdfPassword=Le document PDF est protégé par un mot de passe et le mot de passe n’a pas été fourni ou était incorrect
|
||||
error.pdfPassword=Le document PDF est protégé par un mot de passe qui n’a pas été fourni ou était incorrect
|
||||
delete=Supprimer
|
||||
username=Nom d’utilisateur
|
||||
password=Mot de passe
|
||||
@@ -78,13 +78,13 @@ info=Info
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
pipeline.header=Menu Pipeline (Alpha)
|
||||
pipeline.header=Menu Pipeline (Beta)
|
||||
pipeline.uploadButton=Charger une personnalisation
|
||||
pipeline.configureButton=Configurer
|
||||
pipeline.defaultOption=Personnaliser
|
||||
pipeline.submitButton=Soumettre
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp=Folder Scanning Help
|
||||
pipeline.help=Aide Pipeline
|
||||
pipeline.scanHelp=Aide analyse de dossier
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
@@ -107,16 +107,16 @@ pipelineOptions.validateButton=Valider
|
||||
#############
|
||||
navbar.favorite=Favoris
|
||||
navbar.darkmode=Mode sombre
|
||||
navbar.language=Langages
|
||||
navbar.language=Langues
|
||||
navbar.settings=Paramètres
|
||||
navbar.allTools=OUtils
|
||||
navbar.multiTool=Multi Outils
|
||||
navbar.sections.organize=Oragnisation
|
||||
navbar.allTools=Outils
|
||||
navbar.multiTool=Outils Multiples
|
||||
navbar.sections.organize=Organisation
|
||||
navbar.sections.convertTo=Convertir en PDF
|
||||
navbar.sections.convertFrom=Convertir depuis PDF
|
||||
navbar.sections.security=Signalisation et sécurité
|
||||
navbar.sections.security=Signature et sécurité
|
||||
navbar.sections.advance=Mode avancé
|
||||
navbar.sections.edit=Voir la modification
|
||||
navbar.sections.edit=Voir et modifier
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -132,7 +132,7 @@ settings.downloadOption.3=Télécharger le fichier
|
||||
settings.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse
|
||||
settings.signOut=Déconnexion
|
||||
settings.accountSettings=Paramètres du compte
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.bored.help=Activer les jeux cachés
|
||||
settings.cacheInputs.name=Sauvegarder les entrées du formulaire
|
||||
settings.cacheInputs.help=Permet de stocker les entrées précédemment utilisées pour les exécutions futures
|
||||
|
||||
@@ -181,7 +181,7 @@ adminUserSettings.apiUser=Utilisateur API limité
|
||||
adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l’API
|
||||
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
||||
adminUserSettings.demoUser=Demo User (Paramètres par défaut)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.internalApiUser=Utilisateur de l'API interne
|
||||
adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
|
||||
adminUserSettings.submit=Ajouter
|
||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Signer avec un certificat
|
||||
home.certSign.desc=Signez un PDF avec un certificat ou une clé (PEM/P12).
|
||||
certSign.tags=signer,chiffrer,certificat,authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Fusionner des pages
|
||||
home.pageLayout.desc=Fusionnez plusieurs pages d’un PDF en une seule.
|
||||
pageLayout.tags=fusionner,merge,composite,single-view,organize
|
||||
@@ -357,7 +361,7 @@ home.adjust-contrast.desc=Ajustez le contraste, la saturation et la luminosité
|
||||
adjust-contrast.tags=ajuster,couleurs,amélioration,color-correction,tune,modify,enhance
|
||||
|
||||
home.crop.title=Redimensionner
|
||||
home.crop.desc=Redimmensionnez un PDF pour réduire sa taille (en conservant le texte !).
|
||||
home.crop.desc=Redimensionnez un PDF pour réduire sa taille (en conservant le texte !).
|
||||
crop.tags=redimensionner,trim,shrink,edit,shape
|
||||
|
||||
home.autoSplitPDF.title=Séparer automatiquement les pages
|
||||
@@ -428,11 +432,11 @@ home.AddStampRequest.desc=Ajouter un texte ou l’image d’un tampon à un empl
|
||||
AddStampRequest.tags=Tampon,Ajouter,Stamp,Add image,center image,Watermark,PDF,Embed,Customize
|
||||
|
||||
|
||||
home.PDFToBook.title=PDF to Book
|
||||
home.PDFToBook.title=PDF vers eBook
|
||||
home.PDFToBook.desc=Convertit le PDF en formats livre/bande dessinée à l'aide de calibre
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.title=eBook vers PDF
|
||||
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
@@ -452,6 +456,12 @@ login.locked=Votre compte a été verrouillé.
|
||||
login.signinTitle=Veuillez vous connecter
|
||||
login.ssoSignIn=Se connecter via l'authentification unique
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -475,8 +485,8 @@ showJS.submit=Afficher
|
||||
|
||||
|
||||
#pdfToSinglePage
|
||||
pdfToSinglePage.title=Fusionner des pages
|
||||
pdfToSinglePage.header=Fusionner des pages
|
||||
pdfToSinglePage.title=Fusionner les pages
|
||||
pdfToSinglePage.header=Fusionner les pages
|
||||
pdfToSinglePage.submit=Convertir en une seule page
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nom
|
||||
certSign.submit=Signer
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Supprimer les pages vierges
|
||||
removeBlanks.header=Supprimer les pages vierges
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A.
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
||||
pdfToPDFA.outputFormat=Format de sortie
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=प्रमाणपत्र के साथ हस्त
|
||||
home.certSign.desc=पीडीएफ़ को प्रमाणपत्र/कुंजी (PEM/P12) के साथ हस्ताक्षरित करता है।
|
||||
certSign.tags=प्रमाणीकरण, PEM, P12, आधिकारिक, एन्क्रिप्ट
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=मल्टी-पेज लेआउट
|
||||
home.pageLayout.desc=पीडीएफ़ दस्तावेज़ के कई पेजों को एक ही पेज में मर्ज करता है।
|
||||
pageLayout.tags=मर्ज, संयोजित, एकल दृश्य, संगठित
|
||||
@@ -452,6 +456,12 @@ login.locked=आपका खाता लॉक कर दिया गया
|
||||
login.signinTitle=कृपया साइन इन करें
|
||||
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=डाउनलोड
|
||||
|
||||
#crop
|
||||
crop.title=कटौती
|
||||
crop.header=छवि काटो
|
||||
crop.header=क्रॉप पीडीएफ़
|
||||
crop.submit=प्रस्तुत करें
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=नाम
|
||||
certSign.submit=पीडीएफ़ पर हस्ताक्षर करें
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=खाली पेज हटाएं
|
||||
removeBlanks.header=खाली पेज हटाएं
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन
|
||||
pdfToPDFA.submit=परिवर्तित करें
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
1089
src/main/resources/messages_hr_HR.properties
Normal file
1089
src/main/resources/messages_hr_HR.properties
Normal file
File diff suppressed because it is too large
Load Diff
@@ -332,6 +332,10 @@ home.certSign.title=Aláírás Tanúsítvánnyal
|
||||
home.certSign.desc=PDF aláírása tanúsítvánnyal/kulccsal (PEM/P12)
|
||||
certSign.tags=hitelesítés,PEM,P12,hivatalos,segitít,álca
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Több oldal elrendezése
|
||||
home.pageLayout.desc=Több oldal egyesítése egy PDF dokumentumban egyetlen oldallá
|
||||
pageLayout.tags=egyesítés,kompozit,egy oldal,megszervez
|
||||
@@ -452,6 +456,12 @@ login.locked=A fiókja zárolva lett!
|
||||
login.signinTitle=Kérjük, jelentkezzen be!
|
||||
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Felhasználó automatikus létrehozása letiltva
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Letöltés
|
||||
|
||||
#crop
|
||||
crop.title=Körülvágás
|
||||
crop.header=Kép körülvégésa
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Elküldés
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Név
|
||||
certSign.submit=PDF aláírása
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Üres oldalak eltávolítása
|
||||
removeBlanks.header=Üres oldalak eltávolítása
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Ez a szolgáltatás az OCRmyPDF-t használja a PDF/A konverzió
|
||||
pdfToPDFA.submit=Konvertálás
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Tanda tangani dengan Sertifikat
|
||||
home.certSign.desc=Menandatangani PDF dengan Certificate/Key (PEM/P12)
|
||||
certSign.tags=mengotentikasi, PEM, P12, resmi, mengenkripsi
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Tata Letak Multi-Halaman
|
||||
home.pageLayout.desc=Menggabungkan beberapa halaman dokumen PDF menjadi satu halaman
|
||||
pageLayout.tags=menggabungkan, komposit, tampilan tunggal, mengatur
|
||||
@@ -452,6 +456,12 @@ login.locked=Akun Anda telah dikunci.
|
||||
login.signinTitle=Silakan masuk
|
||||
login.ssoSignIn=Masuk melalui Single Sign - on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Unduh
|
||||
|
||||
#crop
|
||||
crop.title=Pangkas
|
||||
crop.header=Pangkas Gambar
|
||||
crop.header=Pangkas PDF
|
||||
crop.submit=Kirim
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nama
|
||||
certSign.submit=Tanda tangani PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Hapus Halaman Kosong
|
||||
removeBlanks.header=Remove Blank Pages
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Layanan ini menggunakan OCRmyPDF untuk konversi PDF/A.
|
||||
pdfToPDFA.submit=Konversi
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Firma con certificato
|
||||
home.certSign.desc=Firma un PDF con un certificato/chiave (PEM/P12)
|
||||
certSign.tags=autenticare,PEM,P12,ufficiale,crittografare
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Layout multipagina
|
||||
home.pageLayout.desc=Unisci più pagine di un documento PDF in un'unica pagina
|
||||
pageLayout.tags=unire,comporre,visualizzazione singola,organizzare
|
||||
@@ -417,7 +421,7 @@ autoSizeSplitPDF.tags=pdf,diviso,documento,organizzazione
|
||||
|
||||
home.overlay-pdfs.title=Sovrapposizione di PDF
|
||||
home.overlay-pdfs.desc=Sovrappone i PDF sopra un altro PDF
|
||||
overlay-pdfs.tags=Svrapponi
|
||||
overlay-pdfs.tags=Sovrapponi
|
||||
|
||||
home.split-by-sections.title=Dividi PDF per sezioni
|
||||
home.split-by-sections.desc=Dividi ciascuna pagina di un PDF in sezioni orizzontali e verticali più piccole
|
||||
@@ -452,6 +456,12 @@ login.locked=Il tuo account è stato bloccato.
|
||||
login.signinTitle=Per favore accedi
|
||||
login.ssoSignIn=Accedi tramite Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
||||
login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
|
||||
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
||||
login.oauth2invalidRequest=Richiesta non valida
|
||||
login.oauth2AccessDenied=Accesso negato
|
||||
login.oauth2InvalidTokenResponse=Risposta token non valida
|
||||
login.oauth2InvalidIdToken=Id Token non valido
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Ritaglia
|
||||
crop.header=Ritaglia l'immagine
|
||||
crop.header=Ritaglia PDF
|
||||
crop.submit=Invia
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nome
|
||||
certSign.submit=Firma PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Rimuovi certificato della firma
|
||||
removeCertSign.header=Rimuovere il certificato digitale dal PDF
|
||||
removeCertSign.selectPDF=Seleziona un file PDF:
|
||||
removeCertSign.submit=Rimuovi firma
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Rimuovi spazi vuoti
|
||||
removeBlanks.header=Rimuovi pagine vuote
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A.
|
||||
pdfToPDFA.submit=Converti
|
||||
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
|
||||
pdfToPDFA.outputFormat=Formato di output
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=証明書による署名
|
||||
home.certSign.desc=証明書/キーを使用してPDFに署名します。 (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=マルチページレイアウト
|
||||
home.pageLayout.desc=PDFの複数のページを1ページに結合します。
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=あなたのアカウントはロックされています。
|
||||
login.signinTitle=サインインしてください
|
||||
login.ssoSignIn=シングルサインオンでログイン
|
||||
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=ダウンロード
|
||||
|
||||
#crop
|
||||
crop.title=切り抜き
|
||||
crop.header=画像の切り抜き
|
||||
crop.header=PDFのトリミング
|
||||
crop.submit=送信
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=名前
|
||||
certSign.submit=PDFに署名
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=空白の削除
|
||||
removeBlanks.header=空白ページの削除
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用してい
|
||||
pdfToPDFA.submit=変換
|
||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=인증서로 서명
|
||||
home.certSign.desc=PDF 문서에 인증서 또는 키로 서명합니다. (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=다중 페이지 레이아웃
|
||||
home.pageLayout.desc=PDF 문서의 여러 페이지를 한 페이지로 합칩니다.
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=계정이 잠겼습니다.
|
||||
login.signinTitle=로그인해 주세요.
|
||||
login.ssoSignIn=싱글사인온을 통한 로그인
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 사용자 자동 생성 비활성화됨
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=다운로드
|
||||
|
||||
#crop
|
||||
crop.title=잘라내기
|
||||
crop.header=잘라내기
|
||||
crop.header=PDF 잘라내기
|
||||
crop.submit=확인
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=이름
|
||||
certSign.submit=PDF 서명
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=빈 페이지 제거
|
||||
removeBlanks.header=빈 페이지 제거
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 OCRmyPDF 문서를 사
|
||||
pdfToPDFA.submit=변환
|
||||
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Ondertekenen met certificaat
|
||||
home.certSign.desc=Ondertekent een PDF met een certificaat/sleutel (PEM/P12)
|
||||
certSign.tags=authenticeren,PEM,P12,officieel,versleutelen
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-pagina indeling
|
||||
home.pageLayout.desc=Voeg meerdere pagina's van een PDF-document samen op één pagina
|
||||
pageLayout.tags=samenvoegen,composiet,enkel-zicht,organiseren
|
||||
@@ -452,6 +456,12 @@ login.locked=Je account is geblokkeerd.
|
||||
login.signinTitle=Gelieve in te loggen
|
||||
login.ssoSignIn=Inloggen via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Downloaden
|
||||
|
||||
#crop
|
||||
crop.title=Bijwerken
|
||||
crop.header=Afbeelding bijwerken
|
||||
crop.header=PDF bijsnijden
|
||||
crop.submit=Indienen
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Naam
|
||||
certSign.submit=PDF ondertekenen
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Verwijder blanco's
|
||||
removeBlanks.header=Verwijder lege pagina's
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie
|
||||
pdfToPDFA.submit=Converteren
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Podpisz certyfikatem
|
||||
home.certSign.desc=Podpisz dokument PDF za pomocą certyfikatu/klucza prywatnego (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Układ wielu stron
|
||||
home.pageLayout.desc=Scal wiele stron dokumentu PDF w jedną stronę
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nazwa
|
||||
certSign.submit=Podpisz PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Usuń puste
|
||||
removeBlanks.header=Usuń puste strony
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Ta usługa używa OCRmyPDF do konwersji PDF/A
|
||||
pdfToPDFA.submit=Konwertuj
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Assinar com Certificado
|
||||
home.certSign.desc=Assinar um PDF com um Certificado/Chave (PEM/P12)
|
||||
certSign.tags=autenticar,PEM,P12,oficial,criptografar
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Layout de Múltiplas Páginas
|
||||
home.pageLayout.desc=Mesclar várias páginas de um documento PDF em uma única página
|
||||
pageLayout.tags=mesclar,composto,vista-única,organizar
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Criar Usuário Desativado
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Cortar
|
||||
crop.header=Cortar Imagem
|
||||
crop.header=Cortar PDF
|
||||
crop.submit=Enviar
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nome
|
||||
certSign.submit=Assinar PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Remover Páginas em Branco
|
||||
removeBlanks.header=Remover Páginas em Branco
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
|
||||
pdfToPDFA.submit=Converter
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Assinar com Certificado
|
||||
home.certSign.desc=Assinar um PDF com um Certificado/Chave (PEM/P12)
|
||||
certSign.tags=autenticar,PEM,P12,oficial,criptografar
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Layout de Múltiplas Páginas
|
||||
home.pageLayout.desc=Juntar várias páginas de um documento PDF em uma única página
|
||||
pageLayout.tags=juntar,composto,vista-única,organizar
|
||||
@@ -452,6 +456,12 @@ login.locked=A sua conta foi bloqueada.
|
||||
login.signinTitle=Introduza os seus dados de acesso
|
||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Criação Automática de Utilizador Desativada
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Cortar
|
||||
crop.header=Cortar Imagem
|
||||
crop.header=Cortar PDF
|
||||
crop.submit=Enviar
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Nome
|
||||
certSign.submit=Assinar PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Remover Páginas em Branco
|
||||
removeBlanks.header=Remover Páginas em Branco
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
|
||||
pdfToPDFA.submit=Converter
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Semnare cu certificat
|
||||
home.certSign.desc=Semnează un PDF cu un certificat/cheie (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Conectare prin conectare unică
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Numele
|
||||
certSign.submit=Semnează PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Elimină pagini goale
|
||||
removeBlanks.header=Elimină pagini goale
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Acest serviciu utilizează OCRmyPDF pentru conversia în PDF/A
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Подписать сертификатом
|
||||
home.certSign.desc=Подписать PDF сертификатом/ключом (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Объединить страницы
|
||||
home.pageLayout.desc=Объединение нескольких страниц документа PDF в одну страницу
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Ваша учетная запись заблокирована.
|
||||
login.signinTitle=Пожалуйста, войдите
|
||||
login.ssoSignIn=Вход через единый вход
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматическое создание пользователя отключено
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Скачать
|
||||
|
||||
#crop
|
||||
crop.title=Обрезать
|
||||
crop.header=Обрезать изображение
|
||||
crop.header=Обрезать PDF-файл
|
||||
crop.submit=Отправить
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Имя
|
||||
certSign.submit=Подписать PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Удалить Пустые
|
||||
removeBlanks.header=Удалить Пустые Страницы
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Этот сервис использует OCRmyPDF для пр
|
||||
pdfToPDFA.submit=Конвертировать
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Podpísať s certifikátom
|
||||
home.certSign.desc=Podpísať PDF s certifikátom/kľúčom (PEM/P12)
|
||||
certSign.tags=autentifikovať,PEM,P12,oficiálne,šifrovať
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Viacstranové usporiadanie
|
||||
home.pageLayout.desc=Zlúčte viacero stránok PDF dokumentu do jednej stránky
|
||||
pageLayout.tags=zlúčiť,zjednotiť,jednostranový pohľad,organizovať
|
||||
@@ -452,6 +456,12 @@ login.locked=Váš účet bol uzamknutý.
|
||||
login.signinTitle=Prosím, prihláste sa
|
||||
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Stiahnuť
|
||||
|
||||
#crop
|
||||
crop.title=Orezať
|
||||
crop.header=Orezať obrázok
|
||||
crop.header=Orezať PDF
|
||||
crop.submit=Odoslať
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Meno
|
||||
certSign.submit=Podpísať PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Odstrániť prázdne stránky
|
||||
removeBlanks.header=Odstrániť prázdne stránky
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Táto služba používa OCRmyPDF na konverziu PDF/A
|
||||
pdfToPDFA.submit=Konvertovať
|
||||
pdfToPDFA.tip=Momentálne nefunguje pre viacero vstupov naraz
|
||||
pdfToPDFA.outputFormat=Výstupný formát
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Potpis sa sertifikatom
|
||||
home.certSign.desc=Potpisuje PDF sa sertifikatom/ključem (PEM/P12)
|
||||
certSign.tags=autentifikacija,PEM,P12,zvanično,šifrovanje
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Višestruki prikaz stranica
|
||||
home.pageLayout.desc=Spaja više stranica PDF dokumenta u jednu stranicu
|
||||
pageLayout.tags=spajanje,kompozit,pojedinačan-prikaz,organizacija
|
||||
@@ -452,6 +456,12 @@ login.locked=Vaš nalog je zaključan.
|
||||
login.signinTitle=Molimo vas da se prijavite
|
||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Preuzmi
|
||||
|
||||
#crop
|
||||
crop.title=Iseci
|
||||
crop.header=Iseci Sliku
|
||||
crop.header=Skraćivanje PDF-a
|
||||
crop.submit=Potvrdi
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Ime
|
||||
certSign.submit=Potpiši PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Ukloni prazne stranice
|
||||
removeBlanks.header=Ukloni prazne stranice
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za konverziju u PDF/A format
|
||||
pdfToPDFA.submit=Konvertuj
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Sign with Certificate
|
||||
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Logga in via enkel inloggning
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User inaktiverad
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Download
|
||||
|
||||
#crop
|
||||
crop.title=Crop
|
||||
crop.header=Crop Image
|
||||
crop.header=Crop PDF
|
||||
crop.submit=Submit
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Namn
|
||||
certSign.submit=Skriv under PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Ta bort tomrum
|
||||
removeBlanks.header=Ta bort tomma sidor
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Denna tjänst använder OCRmyPDF för PDF/A-konvertering
|
||||
pdfToPDFA.submit=Konvertera
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Sertifika ile İmzala
|
||||
home.certSign.desc=Bir PDF'i Sertifika/Anahtar (PEM/P12) ile imzalar
|
||||
certSign.tags=doğrula,PEM,P12,resmi,şifrele
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Çoklu-Sayfa Düzeni
|
||||
home.pageLayout.desc=Bir PDF belgesinin çoklu sayfalarını tek bir sayfada birleştirir
|
||||
pageLayout.tags=birleştir,kompozit,tek-görünüm,düzenle
|
||||
@@ -452,6 +456,12 @@ login.locked=Hesabınız kilitlendi.
|
||||
login.signinTitle=Lütfen giriş yapınız.
|
||||
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=İndir
|
||||
|
||||
#crop
|
||||
crop.title=Kırp
|
||||
crop.header=Resmi Kırp
|
||||
crop.header=PDF'i Kırp
|
||||
crop.submit=Gönder
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=İsim
|
||||
certSign.submit=PDF'i İmzala
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Boşları Kaldır
|
||||
removeBlanks.header=Boş Sayfaları Kaldır
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için OCRmyPDF kullanır
|
||||
pdfToPDFA.submit=Dönüştür
|
||||
pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor
|
||||
pdfToPDFA.outputFormat=Çıkış formatı
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=Підписати сертифікатом
|
||||
home.certSign.desc=Підписати PDF сертифікатом/ключем (PEM/P12)
|
||||
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=Об'єднати сторінки
|
||||
home.pageLayout.desc=Об'єднання кількох сторінок документа PDF в одну сторінку
|
||||
pageLayout.tags=merge,composite,single-view,organize
|
||||
@@ -452,6 +456,12 @@ login.locked=Ваш обліковий запис заблоковано.
|
||||
login.signinTitle=Будь ласка, увійдіть
|
||||
login.ssoSignIn=Увійти через єдиний вхід
|
||||
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=Завантажити
|
||||
|
||||
#crop
|
||||
crop.title=Обрізати
|
||||
crop.header=Обрізати зображення
|
||||
crop.header=Обрізати PDF-файл
|
||||
crop.submit=Надіслати
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=Ім'я
|
||||
certSign.submit=Підписати PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=Видалити порожні
|
||||
removeBlanks.header=Видалити порожні сторінки
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=Цей сервіс використовує OCRmyPDF для п
|
||||
pdfToPDFA.submit=Конвертувати
|
||||
pdfToPDFA.tip=Наразі не працює для кількох вхідних файлів одночасно
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=验证
|
||||
#############
|
||||
# NAVBAR #
|
||||
#############
|
||||
navbar.favorite=Favorites
|
||||
navbar.darkmode=暗模式
|
||||
navbar.language=Languages
|
||||
navbar.favorite=收藏
|
||||
navbar.darkmode=暗黑模式
|
||||
navbar.language=语言
|
||||
navbar.settings=设置
|
||||
navbar.allTools=Tools
|
||||
navbar.multiTool=Multi Tools
|
||||
navbar.sections.organize=Organize
|
||||
navbar.sections.convertTo=Convert to PDF
|
||||
navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.allTools=工具箱
|
||||
navbar.multiTool=多功能工具
|
||||
navbar.sections.organize=组织
|
||||
navbar.sections.convertTo=转换成PDF
|
||||
navbar.sections.convertFrom=从PDF转换
|
||||
navbar.sections.security=签名和安全
|
||||
navbar.sections.advance=高级功能
|
||||
navbar.sections.edit=查看和编辑
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=使用证书签署
|
||||
home.certSign.desc=使用证书/密钥(PEM/P12)对PDF进行签署
|
||||
certSign.tags=身份验证、PEM、P12、官方、加密
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=多页布局
|
||||
home.pageLayout.desc=将PDF文档的多个页面合并成一页
|
||||
pageLayout.tags=合并、组合、单视图、整理
|
||||
@@ -452,6 +456,12 @@ login.locked=您的账户已被锁定。
|
||||
login.signinTitle=请登录
|
||||
login.ssoSignIn=通过单点登录登录
|
||||
login.oauth2AutoCreateDisabled=OAUTH2自动创建用户已禁用
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=下载
|
||||
|
||||
#crop
|
||||
crop.title=裁剪
|
||||
crop.header=裁剪图像
|
||||
crop.header=裁剪PDF
|
||||
crop.submit=提交
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=名称
|
||||
certSign.submit=签署 PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=删除空白
|
||||
removeBlanks.header=删除空白页
|
||||
@@ -805,7 +822,7 @@ pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
|
||||
#multiTool
|
||||
multiTool.title=PDF多功能工具
|
||||
multiTool.header=PDF多功能工具
|
||||
multiTool.uploadPrompts=File Name
|
||||
multiTool.uploadPrompts=文件名
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=浏览PDF
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换
|
||||
pdfToPDFA.submit=转换
|
||||
pdfToPDFA.tip=目前不支持上传多个
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -332,6 +332,10 @@ home.certSign.title=使用憑證簽章
|
||||
home.certSign.desc=使用憑證/金鑰(PEM/P12)簽章 PDF
|
||||
certSign.tags=驗證,PEM,P12,官方,加密
|
||||
|
||||
home.removeCertSign.title=Remove Certificate Sign
|
||||
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||
|
||||
home.pageLayout.title=多頁面版面配置
|
||||
home.pageLayout.desc=將 PDF 檔案的多個頁面合併到單一頁面
|
||||
pageLayout.tags=合併,複合,單一檢視,組織
|
||||
@@ -452,6 +456,12 @@ login.locked=您的帳戶已被鎖定。
|
||||
login.signinTitle=請登入
|
||||
login.ssoSignIn=透過織網單一簽入
|
||||
login.oauth2AutoCreateDisabled=OAUTH2自動建立使用者已停用
|
||||
login.oauth2RequestNotFound=Authorization request not found
|
||||
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||
login.oauth2invalidRequest=Invalid Request
|
||||
login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
|
||||
|
||||
#auto-redact
|
||||
@@ -592,7 +602,7 @@ adjustContrast.download=下載
|
||||
|
||||
#crop
|
||||
crop.title=裁剪
|
||||
crop.header=裁剪影像
|
||||
crop.header=裁剪 PDF
|
||||
crop.submit=送出
|
||||
|
||||
|
||||
@@ -649,6 +659,13 @@ certSign.name=名稱
|
||||
certSign.submit=簽章 PDF
|
||||
|
||||
|
||||
#removeCertSign
|
||||
removeCertSign.title=Remove Certificate Signature
|
||||
removeCertSign.header=Remove the digital certificate from the PDF
|
||||
removeCertSign.selectPDF=Select a PDF file:
|
||||
removeCertSign.submit=Remove Signature
|
||||
|
||||
|
||||
#removeBlanks
|
||||
removeBlanks.title=移除空白頁面
|
||||
removeBlanks.header=移除空白頁面
|
||||
@@ -959,6 +976,7 @@ pdfToPDFA.credit=此服務使用 OCRmyPDF 進行 PDF/A 轉換
|
||||
pdfToPDFA.submit=轉換
|
||||
pdfToPDFA.tip=目前不支援上傳多個
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
||||
@@ -19,12 +19,29 @@ security:
|
||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||
# client:
|
||||
# google:
|
||||
# clientId: "" # Client ID for Google OAuth2
|
||||
# clientSecret: "" # Client Secret for Google OAuth2
|
||||
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||
# github:
|
||||
# clientId: "" # Client ID for GitHub OAuth2
|
||||
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||
# keycloak:
|
||||
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||
showUpdate: true # see when a new update is available
|
||||
showUpdate: false # see when a new update is available
|
||||
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user