Compare commits
173 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd39f28e46 | ||
|
|
710125852a | ||
|
|
e8ec208390 | ||
|
|
4584562607 | ||
|
|
2c1412a088 | ||
|
|
0a65382979 | ||
|
|
4f404f66e5 | ||
|
|
89505ada00 | ||
|
|
374a30ac5a | ||
|
|
cee4ee4128 | ||
|
|
58fc5e2ffa | ||
|
|
951ea43f8b | ||
|
|
ca16ecef24 | ||
|
|
bb025dc2a1 | ||
|
|
d797169bd0 | ||
|
|
891f9e2252 | ||
|
|
4a579c00ce | ||
|
|
9cb4d8e088 | ||
|
|
5d3ee7755a | ||
|
|
c047c46587 | ||
|
|
54f53be5b5 | ||
|
|
8bb9e5b22f | ||
|
|
379791a326 | ||
|
|
38ec68b303 | ||
|
|
a5095b04ad | ||
|
|
a27ddb40be | ||
|
|
bc36be8a5e | ||
|
|
1e35556034 | ||
|
|
882cd41d4b | ||
|
|
724fb4bf8f | ||
|
|
b07437dbfa | ||
|
|
96f05cd518 | ||
|
|
77411e94a4 | ||
|
|
0da9c62ef8 | ||
|
|
52a7885f3c | ||
|
|
f98f089d63 | ||
|
|
6b618f3abe | ||
|
|
0732ffa76e | ||
|
|
b5b4636e56 | ||
|
|
ca12d040e1 | ||
|
|
3388b9fafa | ||
|
|
954b36e14c | ||
|
|
4d43814220 | ||
|
|
f6262c82e1 | ||
|
|
7ead12922f | ||
|
|
33a6a7869c | ||
|
|
bf995f989c | ||
|
|
21de6c6520 | ||
|
|
c14aa6851e | ||
|
|
8260eced2d | ||
|
|
d028465dc5 | ||
|
|
29dab5e47d | ||
|
|
9e655631b4 | ||
|
|
179c7b80bb | ||
|
|
349bf29122 | ||
|
|
295357f12b | ||
|
|
940f8d999e | ||
|
|
5605d53a5f | ||
|
|
116d103119 | ||
|
|
2fd8c643af | ||
|
|
4367ae7934 | ||
|
|
749461334d | ||
|
|
e83a027023 | ||
|
|
1883b477a3 | ||
|
|
37e2cd40da | ||
|
|
81a9329975 | ||
|
|
0eb019fc3c | ||
|
|
4129c75475 | ||
|
|
3d66f03f58 | ||
|
|
7b83104fd6 | ||
|
|
794aede27f | ||
|
|
08eb39b206 | ||
|
|
2566c7f3d7 | ||
|
|
a8522bb3b5 | ||
|
|
92b9142902 | ||
|
|
d07e3e6522 | ||
|
|
29aabdfba8 | ||
|
|
9af1b0cfdc | ||
|
|
6e32c7fe85 | ||
|
|
ddf5915c6a | ||
|
|
cdbf1fa73a | ||
|
|
5d926b022b | ||
|
|
50bcca10e2 | ||
|
|
a5528c06ee | ||
|
|
94526de04b | ||
|
|
1ddf7abe6f | ||
|
|
a742c1b034 | ||
|
|
6e726ac2a6 | ||
|
|
5877b40be5 | ||
|
|
a3c7f5aa46 | ||
|
|
4e28bf03bd | ||
|
|
f92482d89e | ||
|
|
3c54429fe0 | ||
|
|
4c4c22e861 | ||
|
|
00e27d9327 | ||
|
|
f082278041 | ||
|
|
09dde64c57 | ||
|
|
b52a6357f6 | ||
|
|
f14a566d06 | ||
|
|
b352ec6888 | ||
|
|
4ec1bad03d | ||
|
|
f20bbc119d | ||
|
|
8555c3d422 | ||
|
|
71f9d03b19 | ||
|
|
0b31379078 | ||
|
|
29c204b2c2 | ||
|
|
657e881963 | ||
|
|
ef6bdc70a4 | ||
|
|
e4a36115a2 | ||
|
|
325f86832c | ||
|
|
e3dbdd6b09 | ||
|
|
919041e879 | ||
|
|
182231a183 | ||
|
|
46d4ae8fc5 | ||
|
|
73ab1936a3 | ||
|
|
59c72527b5 | ||
|
|
5ea3bcc1dd | ||
|
|
c140052822 | ||
|
|
f7832774d9 | ||
|
|
279d25c03a | ||
|
|
f1984047a8 | ||
|
|
eae7c1bd60 | ||
|
|
d7f592ebda | ||
|
|
1798ce002a | ||
|
|
57a0cca595 | ||
|
|
b26fbd7693 | ||
|
|
43b0e25bdb | ||
|
|
3377af1305 | ||
|
|
c81c1006b7 | ||
|
|
f313857f96 | ||
|
|
d5fbe02149 | ||
|
|
159cee0b39 | ||
|
|
5da4dd6cca | ||
|
|
e9daf05f16 | ||
|
|
a12643194a | ||
|
|
25d8fc08f7 | ||
|
|
a72378dd4d | ||
|
|
9aed70408b | ||
|
|
5ae2c71c3a | ||
|
|
4edce515b8 | ||
|
|
67ff664eb8 | ||
|
|
71c1a4f102 | ||
|
|
ba4ba1b9fc | ||
|
|
59f10f06ca | ||
|
|
f7953cbc37 | ||
|
|
9a74e81754 | ||
|
|
420e4b6766 | ||
|
|
aed48ffc93 | ||
|
|
0cebe69ff8 | ||
|
|
e5990dba81 | ||
|
|
c9b0d01250 | ||
|
|
4918ed3f3c | ||
|
|
b176ce4251 | ||
|
|
518ff5409e | ||
|
|
803bd3c5b2 | ||
|
|
03d4e73304 | ||
|
|
55a820b09f | ||
|
|
f2a65dc360 | ||
|
|
7b4a889ea7 | ||
|
|
f627d251c3 | ||
|
|
d5b7125415 | ||
|
|
67dd3cf0e3 | ||
|
|
b8b62bb5af | ||
|
|
b4a9d1ac18 | ||
|
|
8aae651c2c | ||
|
|
fc9465b324 | ||
|
|
579a50be2c | ||
|
|
9c5b967e4c | ||
|
|
d41deb729b | ||
|
|
a93a89f3f0 | ||
|
|
11d642a25f | ||
|
|
67448498ea | ||
|
|
489b8da713 |
88
.github/workflows/push-docker.yml
vendored
88
.github/workflows/push-docker.yml
vendored
@@ -9,13 +9,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
config: [
|
|
||||||
{ dockerfile: "./Dockerfile", tagSuffix: "" },
|
|
||||||
{ dockerfile: "./Dockerfile-ultra-lite", tagSuffix: "-ultra-lite" },
|
|
||||||
{ dockerfile: "./Dockerfile-lite", tagSuffix: "-lite" }
|
|
||||||
]
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v3.5.2
|
- uses: actions/checkout@v3.5.2
|
||||||
@@ -56,14 +49,6 @@ jobs:
|
|||||||
id: repoowner
|
id: repoowner
|
||||||
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')"
|
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')"
|
||||||
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2.1.0
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
|
||||||
|
|
||||||
|
|
||||||
- name: Generate tags
|
- name: Generate tags
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4.4.0
|
uses: docker/metadata-action@v4.4.0
|
||||||
@@ -72,19 +57,80 @@ jobs:
|
|||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}${{ matrix.config.tagSuffix }},enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=latest${{ matrix.config.tagSuffix }},enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build and push Dockerfile
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.1.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.5.0
|
||||||
|
|
||||||
|
- name: Build and push main Dockerfile
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ${{ matrix.config.dockerfile }}
|
dockerfile: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- name: Generate tags ultra-lite
|
||||||
|
id: meta2
|
||||||
|
uses: docker/metadata-action@v4.4.0
|
||||||
|
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 }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build and push Dockerfile-ultra-lite
|
||||||
|
uses: docker/build-push-action@v4.0.0
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile-ultra-lite
|
||||||
|
push: true
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
tags: ${{ steps.meta2.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta2.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- name: Generate tags lite
|
||||||
|
id: meta3
|
||||||
|
uses: docker/metadata-action@v4.4.0
|
||||||
|
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 }}-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
type=raw,value=latest-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build and push Dockerfile-lite
|
||||||
|
uses: docker/build-push-action@v4.0.0
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile-lite
|
||||||
|
push: true
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
tags: ${{ steps.meta3.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta3.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
|||||||
45
.github/workflows/releaseArtifacts.yml
vendored
Normal file
45
.github/workflows/releaseArtifacts.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Release Artifacts
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
jobs:
|
||||||
|
push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v3.11.0
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Generate jar
|
||||||
|
run: ./gradlew clean createExe
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: ./build/launch4j/Stirling-PDF.exe
|
||||||
|
asset_name: Stirling-PDF.exe
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar
|
||||||
|
asset_name: Stirling-PDF.jar
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
|
||||||
235
.gitignore
vendored
235
.gitignore
vendored
@@ -1,115 +1,122 @@
|
|||||||
|
|
||||||
|
|
||||||
### Eclipse ###
|
### Eclipse ###
|
||||||
.metadata
|
.metadata
|
||||||
bin/
|
bin/
|
||||||
tmp/
|
tmp/
|
||||||
*.tmp
|
*.tmp
|
||||||
*.bak
|
*.bak
|
||||||
*.swp
|
*.swp
|
||||||
*~.nib
|
*~.nib
|
||||||
local.properties
|
local.properties
|
||||||
.settings/
|
.settings/
|
||||||
.loadpath
|
.loadpath
|
||||||
.recommenders
|
.recommenders
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
version.properties
|
version.properties
|
||||||
|
pipeline/
|
||||||
# Gradle
|
|
||||||
.gradle
|
#### Stirling-PDF Files ###
|
||||||
.lock
|
customFiles/
|
||||||
|
config/
|
||||||
# External tool builders
|
watchedFolders/
|
||||||
.externalToolBuilders/
|
|
||||||
|
|
||||||
# Locally stored "Eclipse launch configurations"
|
# Gradle
|
||||||
*.launch
|
.gradle
|
||||||
|
.lock
|
||||||
# PyDev specific (Python IDE for Eclipse)
|
|
||||||
*.pydevproject
|
# External tool builders
|
||||||
|
.externalToolBuilders/
|
||||||
# CDT-specific (C/C++ Development Tooling)
|
|
||||||
.cproject
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
# CDT- autotools
|
|
||||||
.autotools
|
# PyDev specific (Python IDE for Eclipse)
|
||||||
|
*.pydevproject
|
||||||
# Java annotation processor (APT)
|
|
||||||
.factorypath
|
# CDT-specific (C/C++ Development Tooling)
|
||||||
|
.cproject
|
||||||
# PDT-specific (PHP Development Tools)
|
|
||||||
.buildpath
|
# CDT- autotools
|
||||||
|
.autotools
|
||||||
# sbteclipse plugin
|
|
||||||
.target
|
# Java annotation processor (APT)
|
||||||
|
.factorypath
|
||||||
# Tern plugin
|
|
||||||
.tern-project
|
# PDT-specific (PHP Development Tools)
|
||||||
|
.buildpath
|
||||||
# TeXlipse plugin
|
|
||||||
.texlipse
|
# sbteclipse plugin
|
||||||
|
.target
|
||||||
# STS (Spring Tool Suite)
|
|
||||||
.springBeans
|
# Tern plugin
|
||||||
|
.tern-project
|
||||||
# Code Recommenders
|
|
||||||
.recommenders/
|
# TeXlipse plugin
|
||||||
|
.texlipse
|
||||||
# Annotation Processing
|
|
||||||
.apt_generated/
|
# STS (Spring Tool Suite)
|
||||||
.apt_generated_test/
|
.springBeans
|
||||||
|
|
||||||
# Scala IDE specific (Scala & Java development for Eclipse)
|
# Code Recommenders
|
||||||
.cache-main
|
.recommenders/
|
||||||
.scala_dependencies
|
|
||||||
.worksheet
|
# Annotation Processing
|
||||||
|
.apt_generated/
|
||||||
# Uncomment this line if you wish to ignore the project description file.
|
.apt_generated_test/
|
||||||
# Typically, this file would be tracked if it contains build/dependency configurations:
|
|
||||||
#.project
|
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||||
|
.cache-main
|
||||||
### Eclipse Patch ###
|
.scala_dependencies
|
||||||
# Spring Boot Tooling
|
.worksheet
|
||||||
.sts4-cache/
|
|
||||||
|
# Uncomment this line if you wish to ignore the project description file.
|
||||||
### Git ###
|
# Typically, this file would be tracked if it contains build/dependency configurations:
|
||||||
# Created by git for backups. To disable backups in Git:
|
#.project
|
||||||
# $ git config --global mergetool.keepBackup false
|
|
||||||
*.orig
|
### Eclipse Patch ###
|
||||||
|
# Spring Boot Tooling
|
||||||
# Created by git when using merge tools for conflicts
|
.sts4-cache/
|
||||||
*.BACKUP.*
|
|
||||||
*.BASE.*
|
### Git ###
|
||||||
*.LOCAL.*
|
# Created by git for backups. To disable backups in Git:
|
||||||
*.REMOTE.*
|
# $ git config --global mergetool.keepBackup false
|
||||||
*_BACKUP_*.txt
|
*.orig
|
||||||
*_BASE_*.txt
|
|
||||||
*_LOCAL_*.txt
|
# Created by git when using merge tools for conflicts
|
||||||
*_REMOTE_*.txt
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
### Java ###
|
*.LOCAL.*
|
||||||
# Compiled class file
|
*.REMOTE.*
|
||||||
*.class
|
*_BACKUP_*.txt
|
||||||
|
*_BASE_*.txt
|
||||||
# Log file
|
*_LOCAL_*.txt
|
||||||
*.log
|
*_REMOTE_*.txt
|
||||||
|
|
||||||
# BlueJ files
|
### Java ###
|
||||||
*.ctxt
|
# Compiled class file
|
||||||
|
*.class
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
# Log file
|
||||||
|
*.log
|
||||||
# Package Files #
|
|
||||||
*.jar
|
# BlueJ files
|
||||||
*.war
|
*.ctxt
|
||||||
*.nar
|
|
||||||
*.ear
|
# Mobile Tools for Java (J2ME)
|
||||||
*.zip
|
.mtj.tmp/
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
# Package Files #
|
||||||
|
*.jar
|
||||||
/build
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
/build
|
||||||
|
|
||||||
/.vscode
|
/.vscode
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
# Build jbig2enc in a separate stage
|
# Build jbig2enc in a separate stage
|
||||||
FROM frooodle/stirling-pdf-base:latest
|
FROM frooodle/stirling-pdf-base:beta4
|
||||||
|
|
||||||
# Create scripts folder and copy local scripts
|
# Create scripts folder and copy local scripts
|
||||||
RUN mkdir /scripts
|
RUN mkdir /scripts
|
||||||
COPY ./scripts/* /scripts/
|
COPY ./scripts/* /scripts/
|
||||||
|
|
||||||
|
#Install fonts
|
||||||
|
RUN mkdir /usr/share/fonts/opentype/noto/
|
||||||
|
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
|
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||||
|
RUN fc-cache -f -v
|
||||||
|
|
||||||
# Copy the application JAR file
|
# Copy the application JAR file
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ COPY build/libs/*.jar app.jar
|
|||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV GROUPS_TO_REMOVE=LibreOffice,CLI
|
ENV GROUPS_TO_REMOVE=CLI
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-jar", "/app.jar"]
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ RUN apt-get update && \
|
|||||||
libjpeg-dev && \
|
libjpeg-dev && \
|
||||||
pip install --upgrade pip && \
|
pip install --upgrade pip && \
|
||||||
pip install --no-cache-dir \
|
pip install --no-cache-dir \
|
||||||
opencv-python-headless && \
|
opencv-python-headless WeasyPrint && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Final stage: Copy necessary files from the previous stage
|
# Final stage: Copy necessary files from the previous stage
|
||||||
|
|||||||
46
Endpoint-groups.md
Normal file
46
Endpoint-groups.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
| 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 | | | | ✔️ | | | | | | | ✔️ |
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
|
||||||
|
|
||||||
## How does the OCR Work
|
## How does the OCR Work
|
||||||
Stirling-PDF uses OCRmyPDF which in turn uses tesseract for its text recognition.
|
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition.
|
||||||
All credit goes to them for this awesome work!
|
All credit goes to them for this awesome work!
|
||||||
|
|
||||||
## Language Packs
|
## Language Packs
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ This folder is required for the python scripts using OpenCV
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir /opt/Stirling-PDF &&\
|
sudo mkdir /opt/Stirling-PDF &&\
|
||||||
sudo mv ./build/libs/S-PDF-*.jar /opt/Stirling-PDF/ &&\
|
sudo mv ./build/libs/Stirling-PDF-*.jar /opt/Stirling-PDF/ &&\
|
||||||
sudo mv scripts /opt/Stirling-PDF/ &&\
|
sudo mv scripts /opt/Stirling-PDF/ &&\
|
||||||
echo "Scripts installed."
|
echo "Scripts installed."
|
||||||
```
|
```
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -8,6 +8,8 @@
|
|||||||
[](https://www.paypal.com/paypalme/froodleplex)
|
[](https://www.paypal.com/paypalme/froodleplex)
|
||||||
[](https://github.com/sponsors/Frooodle)
|
[](https://github.com/sponsors/Frooodle)
|
||||||
|
|
||||||
|
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Frooodle/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
||||||
|
|
||||||
This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs.
|
This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs.
|
||||||
|
|
||||||
Stirling PDF makes no outbound calls for any record keeping or tracking.
|
Stirling PDF makes no outbound calls for any record keeping or tracking.
|
||||||
@@ -27,6 +29,11 @@ Feel free to request any features or bug fixes either in github issues or our [D
|
|||||||
- Convert PDFs to and from images
|
- Convert PDFs to and from images
|
||||||
- Reorganize PDF pages into different orders.
|
- Reorganize PDF pages into different orders.
|
||||||
- Add/Generate signatures
|
- Add/Generate signatures
|
||||||
|
- Format PDFs into a multi-paged page
|
||||||
|
- Scale page contents size by set %
|
||||||
|
- Adjust Contrast
|
||||||
|
- Crop PDF
|
||||||
|
- Auto Split PDF (With physically scanned page dividers)
|
||||||
- Flatten PDFs
|
- Flatten PDFs
|
||||||
- Repair PDFs
|
- Repair PDFs
|
||||||
- Detect and remove blank pages
|
- Detect and remove blank pages
|
||||||
@@ -39,8 +46,14 @@ Feel free to request any features or bug fixes either in github issues or our [D
|
|||||||
- Add watermark(s)
|
- Add watermark(s)
|
||||||
- Convert Any common file to PDF (using LibreOffice)
|
- Convert Any common file to PDF (using LibreOffice)
|
||||||
- Convert PDF to Word/Powerpoint/Others (using LibreOffice)
|
- Convert PDF to Word/Powerpoint/Others (using LibreOffice)
|
||||||
|
- Convert HTML to PDF
|
||||||
|
- URL to PDF
|
||||||
- Extract images from PDF
|
- Extract images from PDF
|
||||||
|
- Extract images from Scans
|
||||||
|
- Add page numbers
|
||||||
|
- Auto rename file by detecting PDF header text
|
||||||
- OCR on PDF (Using OCRMyPDF)
|
- OCR on PDF (Using OCRMyPDF)
|
||||||
|
- PDF/A conversion (Using OCRMyPDF)
|
||||||
- Edit metadata
|
- Edit metadata
|
||||||
- Dark mode support.
|
- Dark mode support.
|
||||||
- Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example)
|
- Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example)
|
||||||
@@ -53,6 +66,7 @@ Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) h
|
|||||||
## Technologies used
|
## Technologies used
|
||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- PDFBox
|
- PDFBox
|
||||||
|
- IText7
|
||||||
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
||||||
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
|
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
|
||||||
- HTML, CSS, JavaScript
|
- HTML, CSS, JavaScript
|
||||||
@@ -68,16 +82,25 @@ Please view https://github.com/Frooodle/Stirling-PDF/blob/main/LocalRunGuide.md
|
|||||||
### Docker
|
### Docker
|
||||||
https://hub.docker.com/r/frooodle/s-pdf
|
https://hub.docker.com/r/frooodle/s-pdf
|
||||||
|
|
||||||
|
Stirling PDF has 3 different versions, a Full version, Lite and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space.
|
||||||
|
To see what the different versions offer please look at our [version mapping](https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md)
|
||||||
|
For people that dont mind about space optimisation just use latest tag.
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
Docker Run
|
Docker Run
|
||||||
```
|
```
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata \
|
-v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata \
|
||||||
--name stirling-pdf \
|
--name stirling-pdf \
|
||||||
frooodle/s-pdf
|
frooodle/s-pdf:latest
|
||||||
|
|
||||||
|
|
||||||
Can also add these for customisation but are not required
|
Can also add these for customisation but are not required
|
||||||
|
-v /location/of/extraConfigs:/configs \
|
||||||
|
-v /location/of/customFiles:/customFiles \
|
||||||
-e APP_HOME_NAME="Stirling PDF" \
|
-e APP_HOME_NAME="Stirling PDF" \
|
||||||
-e APP_HOME_DESCRIPTION="Your locally hosted one-stop-shop for all your PDF needs." \
|
-e APP_HOME_DESCRIPTION="Your locally hosted one-stop-shop for all your PDF needs." \
|
||||||
-e APP_NAVBAR_NAME="Stirling PDF" \
|
-e APP_NAVBAR_NAME="Stirling PDF" \
|
||||||
@@ -90,12 +113,13 @@ Docker Compose
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
image: frooodle/s-pdf
|
image: frooodle/s-pdf:latest
|
||||||
ports:
|
ports:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
volumes:
|
volumes:
|
||||||
- /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata #Required for extra OCR languages
|
- /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata #Required for extra OCR languages
|
||||||
# - /location/of/extraConfigs:/configs
|
# - /location/of/extraConfigs:/configs
|
||||||
|
# - /location/of/customFiles:/customFiles/
|
||||||
# environment:
|
# environment:
|
||||||
# APP_LOCALE: en_GB
|
# APP_LOCALE: en_GB
|
||||||
# APP_HOME_NAME: Stirling PDF
|
# APP_HOME_NAME: Stirling PDF
|
||||||
@@ -111,7 +135,7 @@ services:
|
|||||||
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
||||||
|
|
||||||
## Want to add your own language?
|
## Want to add your own language?
|
||||||
Stirling PDF currently supports
|
Stirling PDF currently supports 16!
|
||||||
- English (English) (en_GB)
|
- English (English) (en_GB)
|
||||||
- Arabic (العربية) (ar_AR)
|
- Arabic (العربية) (ar_AR)
|
||||||
- German (Deutsch) (de_DE)
|
- German (Deutsch) (de_DE)
|
||||||
@@ -124,6 +148,10 @@ Stirling PDF currently supports
|
|||||||
- Polish (Polski) (pl_PL)
|
- Polish (Polski) (pl_PL)
|
||||||
- Romanian (Română) (ro_RO)
|
- Romanian (Română) (ro_RO)
|
||||||
- Korean (한국어) (ko_KR)
|
- Korean (한국어) (ko_KR)
|
||||||
|
- Portuguese Brazilian (Português) (pt_BR)
|
||||||
|
- Russian (Русский) (ru_RU)
|
||||||
|
- Basque (Euskara) (eu_ES)
|
||||||
|
- Japanese (日本語) (ja_JP)
|
||||||
|
|
||||||
If you want to add your own language to Stirling-PDF please refer
|
If you want to add your own language to Stirling-PDF please refer
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
||||||
@@ -148,11 +176,13 @@ Using the same method you can also change
|
|||||||
- Enable/Disable search engine visiblility with ALLOW_GOOGLE_VISIBILITY with true / false values. Default disable visiblility.
|
- Enable/Disable search engine visiblility with ALLOW_GOOGLE_VISIBILITY with true / false values. Default disable visiblility.
|
||||||
- Change root URI for Stirling-PDF ie change server.com/ to server.com/pdf-app by running APP_ROOT_PATH as pdf-app
|
- Change root URI for Stirling-PDF ie change server.com/ to server.com/pdf-app by running APP_ROOT_PATH as pdf-app
|
||||||
- Disable and remove endpoints and functionality from Stirling-PDF. Currently the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma seperated lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image to pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/groups.md)
|
- Disable and remove endpoints and functionality from Stirling-PDF. Currently the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma seperated lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image to pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/groups.md)
|
||||||
|
- Change the max file size allowed through the server with the environment variable MAX_FILE_SIZE. default 2000MB
|
||||||
|
- Customise static files such as app logo by placing files in the /customFiles/static/ directory. Example to customise app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
|
||||||
|
- Enable/Disable metric api endpoints with ENABLE_API_METRICS. Default enabled
|
||||||
|
|
||||||
## API
|
## API
|
||||||
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
||||||
[here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation
|
[here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|||||||
59
Version-groups.md
Normal file
59
Version-groups.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
|Technology | Ultra-Lite | Lite | Full |
|
||||||
|
|----------------|:----------:|:----:|:----:|
|
||||||
|
| Java | ✔️ | ✔️ | ✔️ |
|
||||||
|
| JavaScript | ✔️ | ✔️ | ✔️ |
|
||||||
|
| Libre | | ✔️ | ✔️ |
|
||||||
|
| Python | | | ✔️ |
|
||||||
|
| OpenCV | | | ✔️ |
|
||||||
|
| OCRmyPDF | | | ✔️ |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Operation | Ultra-Lite | Lite | Full
|
||||||
|
--------------------|------------|------|-----
|
||||||
|
add-page-numbers | ✔️ | ✔️ | ✔️
|
||||||
|
add-password | ✔️ | ✔️ | ✔️
|
||||||
|
add-image | ✔️ | ✔️ | ✔️
|
||||||
|
add-watermark | ✔️ | ✔️ | ✔️
|
||||||
|
adjust-contrast | ✔️ | ✔️ | ✔️
|
||||||
|
auto-split-pdf | ✔️ | ✔️ | ✔️
|
||||||
|
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 | ✔️ | ✔️ | ✔️
|
||||||
|
pdf-organizer | ✔️ | ✔️ | ✔️
|
||||||
|
pdf-to-img | ✔️ | ✔️ | ✔️
|
||||||
|
pdf-to-single-page | ✔️ | ✔️ | ✔️
|
||||||
|
remove-pages | ✔️ | ✔️ | ✔️
|
||||||
|
remove-password | ✔️ | ✔️ | ✔️
|
||||||
|
rotate-pdf | ✔️ | ✔️ | ✔️
|
||||||
|
sanitize-pdf | ✔️ | ✔️ | ✔️
|
||||||
|
scale-pages | ✔️ | ✔️ | ✔️
|
||||||
|
sign | ✔️ | ✔️ | ✔️
|
||||||
|
show-javascript | ✔️ | ✔️ | ✔️
|
||||||
|
split-pdfs | ✔️ | ✔️ | ✔️
|
||||||
|
file-to-pdf | | ✔️ | ✔️
|
||||||
|
pdf-to-html | | ✔️ | ✔️
|
||||||
|
pdf-to-presentation | | ✔️ | ✔️
|
||||||
|
pdf-to-text | | ✔️ | ✔️
|
||||||
|
pdf-to-word | | ✔️ | ✔️
|
||||||
|
pdf-to-xml | | ✔️ | ✔️
|
||||||
|
repair | | ✔️ | ✔️
|
||||||
|
xlsx-to-pdf | | ✔️ | ✔️
|
||||||
|
compress-pdf | | | ✔️
|
||||||
|
extract-image-scans | | | ✔️
|
||||||
|
ocr-pdf | | | ✔️
|
||||||
|
pdf-to-pdfa | | | ✔️
|
||||||
|
remove-blanks | | | ✔️
|
||||||
45
build.gradle
45
build.gradle
@@ -1,13 +1,14 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.1.0'
|
id 'org.springframework.boot' version '3.1.2'
|
||||||
id 'io.spring.dependency-management' version '1.1.0'
|
id 'io.spring.dependency-management' version '1.1.2'
|
||||||
id 'org.springdoc.openapi-gradle-plugin' version '1.6.0'
|
id 'org.springdoc.openapi-gradle-plugin' version '1.6.0'
|
||||||
id "io.swagger.swaggerhub" version "1.1.0"
|
id "io.swagger.swaggerhub" version "1.2.0"
|
||||||
|
id 'edu.sc.seis.launch4j' version '3.0.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.10.1'
|
version = '0.12.2'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -20,23 +21,49 @@ openApi {
|
|||||||
outputFileName = "SwaggerDoc.json"
|
outputFileName = "SwaggerDoc.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
launch4j {
|
||||||
|
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
|
outfile="Stirling-PDF.exe"
|
||||||
|
headerType="console"
|
||||||
|
jarTask = tasks.bootJar
|
||||||
|
|
||||||
|
errTitle="Encountered error, Do you have Java 17?"
|
||||||
|
downloadUrl="https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe"
|
||||||
|
variables=["BROWSER_OPEN=true"]
|
||||||
|
jreMinVersion="17"
|
||||||
|
|
||||||
|
mutexName="Stirling-PDF"
|
||||||
|
windowTitle="Stirling-PDF"
|
||||||
|
|
||||||
|
messagesStartupError="An error occurred while starting Stirling-PDF"
|
||||||
|
//messagesJreNotFoundError="This application requires a Java Runtime Environment, Please download Java 17."
|
||||||
|
messagesJreVersionError="You are running the wrong version of Java, Please download Java 17."
|
||||||
|
messagesLauncherError="Java is corrupted. Please uninstall and then install Java 17."
|
||||||
|
messagesInstanceAlreadyExists="Stirling-PDF is already running."
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
|
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.0'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.1'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.0'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
|
||||||
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
||||||
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
||||||
implementation 'commons-io:commons-io:2.11.0'
|
implementation 'commons-io:commons-io:2.13.0'
|
||||||
|
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
|
||||||
|
|
||||||
//general PDF
|
//general PDF
|
||||||
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
|
implementation 'org.apache.pdfbox:pdfbox:2.0.29'
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
||||||
implementation 'com.itextpdf:itext7-core:7.2.5'
|
implementation 'com.itextpdf:itext7-core:7.2.5'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
implementation 'io.micrometer:micrometer-core'
|
implementation 'io.micrometer:micrometer-core'
|
||||||
|
implementation group: 'com.google.zxing', name: 'core', version: '3.5.1'
|
||||||
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
|
implementation 'org.commonmark:commonmark:0.21.0'
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
|
|
||||||
|
|||||||
38
groups.md
38
groups.md
@@ -1,38 +0,0 @@
|
|||||||
Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript
|
|
||||||
--------------------|---------|---------|----------|-------|------|--------|--------|-------------|--------- |-------- |-----------
|
|
||||||
remove-pages | X | | | | | | | | | X |
|
|
||||||
merge-pdfs | X | | | | | | | | | X |
|
|
||||||
split-pdfs | X | | | | | | | | | X |
|
|
||||||
pdf-organizer | X | | | | | | | | | X | X
|
|
||||||
rotate-pdf | X | | | | | | | | | X |
|
|
||||||
multi-page-layout | X | | | | | | | | | X |
|
|
||||||
scale-pages | X | | | | | | | | | X |
|
|
||||||
pdf-to-img | | X | | | | | | | | X |
|
|
||||||
img-to-pdf | | X | | | | | | | | X |
|
|
||||||
pdf-to-pdfa | | X | | | X | | | | X | |
|
|
||||||
file-to-pdf | | X | | | X | | | X | | |
|
|
||||||
xlsx-to-pdf | | X | | | X | | | X | | |
|
|
||||||
pdf-to-word | | X | | | X | | | X | | |
|
|
||||||
pdf-to-presentation | | X | | | X | | | X | | |
|
|
||||||
pdf-to-text | | X | | | X | | | X | | |
|
|
||||||
pdf-to-html | | X | | | X | | | X | | |
|
|
||||||
pdf-to-xml | | X | | | X | | | X | | |
|
|
||||||
add-password | | | X | | | | | | | X |
|
|
||||||
remove-password | | | X | | | | | | | X |
|
|
||||||
change-permissions | | | X | | | | | | | X |
|
|
||||||
add-watermark | | | X | | | | | | | X |
|
|
||||||
cert-sign | | | X | | | | | | | X |
|
|
||||||
ocr-pdf | | | | X | X | | | | X | |
|
|
||||||
add-image | | | | X | | | | | | X |
|
|
||||||
compress-pdf | | | | X | X | | | | X
|
|
||||||
extract-images | | | | X | | | | | | X |
|
|
||||||
change-metadata | | | | X | | | | | | X |
|
|
||||||
extract-image-scans | | | | X | X | X | X | | | |
|
|
||||||
sign | | | | X | | | | | | | X
|
|
||||||
flatten | | | | X | | | | | | |
|
|
||||||
repair | | | | X | X | | | X | | |
|
|
||||||
remove-blanks | | | | X | X | X | X | | | |
|
|
||||||
compare | | | | X | | | | | | | X
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 118 KiB |
36
lauch4jConfig.xml
Normal file
36
lauch4jConfig.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<launch4jConfig>
|
||||||
|
<dontWrapJar>false</dontWrapJar>
|
||||||
|
<headerType>console</headerType>
|
||||||
|
<jar>.\build\libs\S-PDF-0.10.1.jar</jar>
|
||||||
|
<outfile>.\Stirling-PDF.exe</outfile>
|
||||||
|
<errTitle>Please download Java17</errTitle>
|
||||||
|
<cmdLine></cmdLine>
|
||||||
|
<chdir>.</chdir>
|
||||||
|
<priority>normal</priority>
|
||||||
|
<downloadUrl>https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe</downloadUrl>
|
||||||
|
<supportUrl></supportUrl>
|
||||||
|
<stayAlive>false</stayAlive>
|
||||||
|
<restartOnCrash>false</restartOnCrash>
|
||||||
|
<manifest></manifest>
|
||||||
|
<icon>./src/main/resources/static/favicon.ico</icon>
|
||||||
|
<var>BROWSER_OPEN=true</var>
|
||||||
|
<singleInstance>
|
||||||
|
<mutexName>Stirling-PDF</mutexName>
|
||||||
|
<windowTitle>Stirling-PDF</windowTitle>
|
||||||
|
</singleInstance>
|
||||||
|
<jre>
|
||||||
|
<path>%JAVA_HOME%;%PATH%</path>
|
||||||
|
<requiresJdk>false</requiresJdk>
|
||||||
|
<requires64Bit>false</requires64Bit>
|
||||||
|
<minVersion>17</minVersion>
|
||||||
|
<maxVersion></maxVersion>
|
||||||
|
</jre>
|
||||||
|
<messages>
|
||||||
|
<startupErr>An error occurred while starting Stirling-PDF</startupErr>
|
||||||
|
<jreNotFoundErr>This application requires a Java Runtime Environment, Please download Java 17.</jreNotFoundErr>
|
||||||
|
<jreVersionErr>You are running the wrong version of Java, Please download Java 17.</jreVersionErr>
|
||||||
|
<launcherErr>Java is corrupted. Please uninstall and then install Java 17.</launcherErr>
|
||||||
|
<instanceAlreadyExistsMsg>Stirling-PDF is already running.</instanceAlreadyExistsMsg>
|
||||||
|
</messages>
|
||||||
|
</launch4jConfig>
|
||||||
80
scripts/PropSync.java
Normal file
80
scripts/PropSync.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package stirling.software.Stirling.Stats;
|
||||||
|
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class PropSync {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
File folder = new File("C:\\Users\\systo\\git\\Stirling-PDF\\src\\main\\resources");
|
||||||
|
File[] files = folder.listFiles((dir, name) -> name.matches("messages_.*\\.properties"));
|
||||||
|
|
||||||
|
List<String> enLines = Files.readAllLines(Paths.get(folder + "\\messages_en_GB.properties"), StandardCharsets.UTF_8);
|
||||||
|
Map<String, String> enProps = linesToProps(enLines);
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
if (!file.getName().equals("messages_en_GB.properties")) {
|
||||||
|
System.out.println("Processing file: " + file.getName());
|
||||||
|
List<String> lines;
|
||||||
|
try {
|
||||||
|
lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
|
||||||
|
} catch (MalformedInputException e) {
|
||||||
|
System.out.println("Skipping due to not UTF8 format for file: " + file.getName());
|
||||||
|
continue;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> currentProps = linesToProps(lines);
|
||||||
|
List<String> newLines = syncPropsWithLines(enProps, currentProps, enLines);
|
||||||
|
|
||||||
|
Files.write(file.toPath(), newLines, StandardCharsets.UTF_8);
|
||||||
|
System.out.println("Finished processing file: " + file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> linesToProps(List<String> lines) {
|
||||||
|
Map<String, String> props = new LinkedHashMap<>();
|
||||||
|
for (String line : lines) {
|
||||||
|
if (!line.trim().isEmpty() && line.contains("=")) {
|
||||||
|
String[] parts = line.split("=", 2);
|
||||||
|
props.put(parts[0].trim(), parts[1].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> syncPropsWithLines(Map<String, String> enProps, Map<String, String> currentProps, List<String> enLines) {
|
||||||
|
List<String> newLines = new ArrayList<>();
|
||||||
|
boolean needsTranslateComment = false; // flag to check if we need to add "TODO: Translate"
|
||||||
|
|
||||||
|
for (String line : enLines) {
|
||||||
|
if (line.contains("=")) {
|
||||||
|
String key = line.split("=", 2)[0].trim();
|
||||||
|
|
||||||
|
if (currentProps.containsKey(key)) {
|
||||||
|
newLines.add(key + "=" + currentProps.get(key));
|
||||||
|
needsTranslateComment = false;
|
||||||
|
} else {
|
||||||
|
if (!needsTranslateComment) {
|
||||||
|
newLines.add("##########################");
|
||||||
|
newLines.add("### TODO: Translate ###");
|
||||||
|
newLines.add("##########################");
|
||||||
|
needsTranslateComment = true;
|
||||||
|
}
|
||||||
|
newLines.add(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// handle comments and other non-property lines
|
||||||
|
newLines.add(line);
|
||||||
|
needsTranslateComment = false; // reset the flag when we encounter comments or empty lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,17 @@ echo "Copying original files without overwriting existing files"
|
|||||||
mkdir -p /usr/share/tesseract-ocr
|
mkdir -p /usr/share/tesseract-ocr
|
||||||
cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr
|
cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr
|
||||||
|
|
||||||
|
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
||||||
|
if [[ -n "$TESSERACT_LANGS" ]]; then
|
||||||
|
# Convert comma-separated values to a space-separated list
|
||||||
|
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
||||||
|
|
||||||
|
# Install each language pack
|
||||||
|
for LANG in $LANGS; do
|
||||||
|
apt-get install -y "tesseract-ocr-$LANG"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Run the main command
|
# Run the main command
|
||||||
exec "$@"
|
exec "$@"
|
||||||
@@ -1 +1 @@
|
|||||||
rootProject.name = 'S-PDF'
|
rootProject.name = 'Stirling-PDF'
|
||||||
|
|||||||
@@ -1,11 +1,76 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import java.io.IOException;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
@SpringBootApplication
|
import java.nio.file.Paths;
|
||||||
public class SPdfApplication {
|
|
||||||
public static void main(String[] args) {
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
SpringApplication.run(SPdfApplication.class, args);
|
import org.springframework.boot.SpringApplication;
|
||||||
}
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
//@EnableScheduling
|
||||||
|
public class SPdfApplication {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment env;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// Check if the BROWSER_OPEN environment variable is set to true
|
||||||
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
|
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true");
|
||||||
|
|
||||||
|
if (browserOpen) {
|
||||||
|
try {
|
||||||
|
String port = env.getProperty("local.server.port");
|
||||||
|
if(port == null || port.length() == 0) {
|
||||||
|
port="8080";
|
||||||
|
}
|
||||||
|
String url = "http://localhost:" + port;
|
||||||
|
|
||||||
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
if (os.contains("win")) {
|
||||||
|
// For Windows
|
||||||
|
rt.exec("rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(SPdfApplication.class, args);
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneralUtils.createDir("customFiles/static/");
|
||||||
|
GeneralUtils.createDir("customFiles/templates/");
|
||||||
|
GeneralUtils.createDir("config");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println("Stirling-PDF Started.");
|
||||||
|
|
||||||
|
String port = System.getProperty("local.server.port");
|
||||||
|
if(port == null || port.length() == 0) {
|
||||||
|
port="8080";
|
||||||
|
}
|
||||||
|
String url = "http://localhost:" + port;
|
||||||
|
System.out.println("Navigate to " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,53 +1,60 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.LocaleResolver;
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Beans implements WebMvcConfigurer {
|
public class Beans implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(localeChangeInterceptor());
|
registry.addInterceptor(localeChangeInterceptor());
|
||||||
registry.addInterceptor(new CleanUrlInterceptor());
|
registry.addInterceptor(new CleanUrlInterceptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LocaleChangeInterceptor localeChangeInterceptor() {
|
public LocaleChangeInterceptor localeChangeInterceptor() {
|
||||||
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
|
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
|
||||||
lci.setParamName("lang");
|
lci.setParamName("lang");
|
||||||
return lci;
|
return lci;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LocaleResolver localeResolver() {
|
public LocaleResolver localeResolver() {
|
||||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||||
|
|
||||||
String appLocaleEnv = System.getProperty("APP_LOCALE");
|
String appLocaleEnv = System.getProperty("APP_LOCALE");
|
||||||
if (appLocaleEnv == null)
|
if (appLocaleEnv == null)
|
||||||
appLocaleEnv = System.getenv("APP_LOCALE");
|
appLocaleEnv = System.getenv("APP_LOCALE");
|
||||||
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
|
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||||
|
|
||||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
||||||
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
||||||
String tempLanguageTag = tempLocale.toLanguageTag();
|
String tempLanguageTag = tempLocale.toLanguageTag();
|
||||||
|
|
||||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||||
defaultLocale = tempLocale;
|
defaultLocale = tempLocale;
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
|
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_","-"));
|
||||||
}
|
tempLanguageTag = tempLocale.toLanguageTag();
|
||||||
}
|
|
||||||
|
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||||
slr.setDefaultLocale(defaultLocale);
|
defaultLocale = tempLocale;
|
||||||
return slr;
|
} else {
|
||||||
}
|
System.err.println("Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slr.setDefaultLocale(defaultLocale);
|
||||||
|
return slr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -7,39 +9,71 @@ import org.springframework.web.servlet.ModelAndView;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
public class CleanUrlInterceptor implements HandlerInterceptor {
|
public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
private static final Pattern LANG_PATTERN = Pattern.compile("&?lang=([^&]+)");
|
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
String queryString = request.getQueryString();
|
throws Exception {
|
||||||
if (queryString != null && !queryString.isEmpty()) {
|
String queryString = request.getQueryString();
|
||||||
String requestURI = request.getRequestURI();
|
if (queryString != null && !queryString.isEmpty()) {
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
|
||||||
// Keep the lang parameter if it exists
|
Map<String, String> parameters = new HashMap<>();
|
||||||
Matcher langMatcher = LANG_PATTERN.matcher(queryString);
|
|
||||||
String langQueryString = langMatcher.find() ? "lang=" + langMatcher.group(1) : "";
|
|
||||||
|
|
||||||
// Check if there are any other query parameters besides the lang parameter
|
// Keep only the allowed parameters
|
||||||
String remainingQueryString = queryString.replaceAll(LANG_PATTERN.pattern(), "").replaceAll("&+", "&").replaceAll("^&|&$", "");
|
String[] queryParameters = queryString.split("&");
|
||||||
|
for (String param : queryParameters) {
|
||||||
|
String[] keyValue = param.split("=");
|
||||||
|
if (keyValue.length != 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ALLOWED_PARAMS.contains(keyValue[0])) {
|
||||||
|
parameters.put(keyValue[0], keyValue[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!remainingQueryString.isEmpty()) {
|
// If there are any parameters that are not allowed
|
||||||
// Redirect to the URL without other query parameters
|
if (parameters.size() != queryParameters.length) {
|
||||||
String redirectUrl = requestURI + (langQueryString.isEmpty() ? "" : "?" + langQueryString);
|
// Construct new query string
|
||||||
response.sendRedirect(redirectUrl);
|
StringBuilder newQueryString = new StringBuilder();
|
||||||
return false;
|
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||||
}
|
if (newQueryString.length() > 0) {
|
||||||
}
|
newQueryString.append("&");
|
||||||
return true;
|
}
|
||||||
}
|
newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
// Redirect to the URL with only allowed query parameters
|
||||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
String redirectUrl = requestURI + "?" + newQueryString;
|
||||||
}
|
response.sendRedirect(redirectUrl);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||||
}
|
ModelAndView modelAndView) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||||
|
Exception ex) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,200 +1,222 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@Service
|
@Service
|
||||||
public class EndpointConfiguration {
|
public class EndpointConfiguration {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
||||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public EndpointConfiguration() {
|
public EndpointConfiguration() {
|
||||||
init();
|
init();
|
||||||
processEnvironmentConfigs();
|
processEnvironmentConfigs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableEndpoint(String endpoint) {
|
public void enableEndpoint(String endpoint) {
|
||||||
endpointStatuses.put(endpoint, true);
|
endpointStatuses.put(endpoint, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableEndpoint(String endpoint) {
|
public void disableEndpoint(String endpoint) {
|
||||||
if(!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
if(!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
||||||
logger.info("Disabling {}", endpoint);
|
logger.info("Disabling {}", endpoint);
|
||||||
endpointStatuses.put(endpoint, false);
|
endpointStatuses.put(endpoint, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEndpointEnabled(String endpoint) {
|
public boolean isEndpointEnabled(String endpoint) {
|
||||||
if (endpoint.startsWith("/")) {
|
if (endpoint.startsWith("/")) {
|
||||||
endpoint = endpoint.substring(1);
|
endpoint = endpoint.substring(1);
|
||||||
}
|
}
|
||||||
return endpointStatuses.getOrDefault(endpoint, true);
|
return endpointStatuses.getOrDefault(endpoint, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addEndpointToGroup(String group, String endpoint) {
|
public void addEndpointToGroup(String group, String endpoint) {
|
||||||
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint);
|
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableGroup(String group) {
|
public void enableGroup(String group) {
|
||||||
Set<String> endpoints = endpointGroups.get(group);
|
Set<String> endpoints = endpointGroups.get(group);
|
||||||
if (endpoints != null) {
|
if (endpoints != null) {
|
||||||
for (String endpoint : endpoints) {
|
for (String endpoint : endpoints) {
|
||||||
enableEndpoint(endpoint);
|
enableEndpoint(endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableGroup(String group) {
|
public void disableGroup(String group) {
|
||||||
Set<String> endpoints = endpointGroups.get(group);
|
Set<String> endpoints = endpointGroups.get(group);
|
||||||
if (endpoints != null) {
|
if (endpoints != null) {
|
||||||
for (String endpoint : endpoints) {
|
for (String endpoint : endpoints) {
|
||||||
disableEndpoint(endpoint);
|
disableEndpoint(endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
// Adding endpoints to "PageOps" group
|
// Adding endpoints to "PageOps" group
|
||||||
addEndpointToGroup("PageOps", "remove-pages");
|
addEndpointToGroup("PageOps", "remove-pages");
|
||||||
addEndpointToGroup("PageOps", "merge-pdfs");
|
addEndpointToGroup("PageOps", "merge-pdfs");
|
||||||
addEndpointToGroup("PageOps", "split-pdfs");
|
addEndpointToGroup("PageOps", "split-pdfs");
|
||||||
addEndpointToGroup("PageOps", "pdf-organizer");
|
addEndpointToGroup("PageOps", "pdf-organizer");
|
||||||
addEndpointToGroup("PageOps", "rotate-pdf");
|
addEndpointToGroup("PageOps", "rotate-pdf");
|
||||||
addEndpointToGroup("PageOps", "multi-page-layout");
|
addEndpointToGroup("PageOps", "multi-page-layout");
|
||||||
addEndpointToGroup("PageOps", "scale-pages");
|
addEndpointToGroup("PageOps", "scale-pages");
|
||||||
|
addEndpointToGroup("PageOps", "adjust-contrast");
|
||||||
// Adding endpoints to "Convert" group
|
addEndpointToGroup("PageOps", "crop");
|
||||||
addEndpointToGroup("Convert", "pdf-to-img");
|
addEndpointToGroup("PageOps", "auto-split-pdf");
|
||||||
addEndpointToGroup("Convert", "img-to-pdf");
|
addEndpointToGroup("PageOps", "extract-page");
|
||||||
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
||||||
addEndpointToGroup("Convert", "file-to-pdf");
|
|
||||||
addEndpointToGroup("Convert", "xlsx-to-pdf");
|
// Adding endpoints to "Convert" group
|
||||||
addEndpointToGroup("Convert", "pdf-to-word");
|
addEndpointToGroup("Convert", "pdf-to-img");
|
||||||
addEndpointToGroup("Convert", "pdf-to-presentation");
|
addEndpointToGroup("Convert", "img-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-text");
|
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("Convert", "pdf-to-html");
|
addEndpointToGroup("Convert", "file-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-xml");
|
addEndpointToGroup("Convert", "xlsx-to-pdf");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-word");
|
||||||
// Adding endpoints to "Security" group
|
addEndpointToGroup("Convert", "pdf-to-presentation");
|
||||||
addEndpointToGroup("Security", "add-password");
|
addEndpointToGroup("Convert", "pdf-to-text");
|
||||||
addEndpointToGroup("Security", "remove-password");
|
addEndpointToGroup("Convert", "pdf-to-html");
|
||||||
addEndpointToGroup("Security", "change-permissions");
|
addEndpointToGroup("Convert", "pdf-to-xml");
|
||||||
addEndpointToGroup("Security", "add-watermark");
|
addEndpointToGroup("Convert", "html-to-pdf");
|
||||||
addEndpointToGroup("Security", "cert-sign");
|
addEndpointToGroup("Convert", "url-to-pdf");
|
||||||
|
addEndpointToGroup("Convert", "markdown-to-pdf");
|
||||||
|
|
||||||
|
// Adding endpoints to "Security" group
|
||||||
// Adding endpoints to "Other" group
|
addEndpointToGroup("Security", "add-password");
|
||||||
addEndpointToGroup("Other", "ocr-pdf");
|
addEndpointToGroup("Security", "remove-password");
|
||||||
addEndpointToGroup("Other", "add-image");
|
addEndpointToGroup("Security", "change-permissions");
|
||||||
addEndpointToGroup("Other", "compress-pdf");
|
addEndpointToGroup("Security", "add-watermark");
|
||||||
addEndpointToGroup("Other", "extract-images");
|
addEndpointToGroup("Security", "cert-sign");
|
||||||
addEndpointToGroup("Other", "change-metadata");
|
addEndpointToGroup("Security", "sanitize-pdf");
|
||||||
addEndpointToGroup("Other", "extract-image-scans");
|
|
||||||
addEndpointToGroup("Other", "sign");
|
|
||||||
addEndpointToGroup("Other", "flatten");
|
// Adding endpoints to "Other" group
|
||||||
addEndpointToGroup("Other", "repair");
|
addEndpointToGroup("Other", "ocr-pdf");
|
||||||
addEndpointToGroup("Other", "remove-blanks");
|
addEndpointToGroup("Other", "add-image");
|
||||||
addEndpointToGroup("Other", "compare");
|
addEndpointToGroup("Other", "compress-pdf");
|
||||||
|
addEndpointToGroup("Other", "extract-images");
|
||||||
|
addEndpointToGroup("Other", "change-metadata");
|
||||||
|
addEndpointToGroup("Other", "extract-image-scans");
|
||||||
|
addEndpointToGroup("Other", "sign");
|
||||||
|
addEndpointToGroup("Other", "flatten");
|
||||||
|
addEndpointToGroup("Other", "repair");
|
||||||
|
addEndpointToGroup("Other", "remove-blanks");
|
||||||
//CLI
|
addEndpointToGroup("Other", "compare");
|
||||||
addEndpointToGroup("CLI", "compress-pdf");
|
addEndpointToGroup("Other", "add-page-numbers");
|
||||||
addEndpointToGroup("CLI", "extract-image-scans");
|
addEndpointToGroup("Other", "auto-rename");
|
||||||
addEndpointToGroup("CLI", "remove-blanks");
|
addEndpointToGroup("Other", "get-info-on-pdf");
|
||||||
addEndpointToGroup("CLI", "repair");
|
addEndpointToGroup("Other", "show-javascript");
|
||||||
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
|
||||||
addEndpointToGroup("CLI", "file-to-pdf");
|
|
||||||
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
|
||||||
addEndpointToGroup("CLI", "pdf-to-word");
|
//CLI
|
||||||
addEndpointToGroup("CLI", "pdf-to-presentation");
|
addEndpointToGroup("CLI", "compress-pdf");
|
||||||
addEndpointToGroup("CLI", "pdf-to-text");
|
addEndpointToGroup("CLI", "extract-image-scans");
|
||||||
addEndpointToGroup("CLI", "pdf-to-html");
|
addEndpointToGroup("CLI", "remove-blanks");
|
||||||
addEndpointToGroup("CLI", "pdf-to-xml");
|
addEndpointToGroup("CLI", "repair");
|
||||||
addEndpointToGroup("CLI", "ocr-pdf");
|
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
||||||
|
addEndpointToGroup("CLI", "file-to-pdf");
|
||||||
//python
|
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
||||||
addEndpointToGroup("Python", "extract-image-scans");
|
addEndpointToGroup("CLI", "pdf-to-word");
|
||||||
addEndpointToGroup("Python", "remove-blanks");
|
addEndpointToGroup("CLI", "pdf-to-presentation");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-text");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-html");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-xml");
|
||||||
//openCV
|
addEndpointToGroup("CLI", "ocr-pdf");
|
||||||
addEndpointToGroup("OpenCV", "extract-image-scans");
|
addEndpointToGroup("CLI", "html-to-pdf");
|
||||||
addEndpointToGroup("OpenCV", "remove-blanks");
|
addEndpointToGroup("CLI", "url-to-pdf");
|
||||||
|
|
||||||
//LibreOffice
|
|
||||||
addEndpointToGroup("LibreOffice", "repair");
|
//python
|
||||||
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
addEndpointToGroup("Python", "extract-image-scans");
|
||||||
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
addEndpointToGroup("Python", "remove-blanks");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
addEndpointToGroup("Python", "html-to-pdf");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
addEndpointToGroup("Python", "url-to-pdf");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-text");
|
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
//openCV
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
addEndpointToGroup("OpenCV", "extract-image-scans");
|
||||||
|
addEndpointToGroup("OpenCV", "remove-blanks");
|
||||||
|
|
||||||
//OCRmyPDF
|
//LibreOffice
|
||||||
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
addEndpointToGroup("LibreOffice", "repair");
|
||||||
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
||||||
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
|
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
||||||
|
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||||
//Java
|
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||||
addEndpointToGroup("Java", "merge-pdfs");
|
addEndpointToGroup("LibreOffice", "pdf-to-text");
|
||||||
addEndpointToGroup("Java", "remove-pages");
|
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
||||||
addEndpointToGroup("Java", "split-pdfs");
|
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
||||||
addEndpointToGroup("Java", "pdf-organizer");
|
|
||||||
addEndpointToGroup("Java", "rotate-pdf");
|
|
||||||
addEndpointToGroup("Java", "pdf-to-img");
|
//OCRmyPDF
|
||||||
addEndpointToGroup("Java", "img-to-pdf");
|
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
||||||
addEndpointToGroup("Java", "add-password");
|
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("Java", "remove-password");
|
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
|
||||||
addEndpointToGroup("Java", "change-permissions");
|
|
||||||
addEndpointToGroup("Java", "add-watermark");
|
//Java
|
||||||
addEndpointToGroup("Java", "add-image");
|
addEndpointToGroup("Java", "merge-pdfs");
|
||||||
addEndpointToGroup("Java", "extract-images");
|
addEndpointToGroup("Java", "remove-pages");
|
||||||
addEndpointToGroup("Java", "change-metadata");
|
addEndpointToGroup("Java", "split-pdfs");
|
||||||
addEndpointToGroup("Java", "cert-sign");
|
addEndpointToGroup("Java", "pdf-organizer");
|
||||||
addEndpointToGroup("Java", "multi-page-layout");
|
addEndpointToGroup("Java", "rotate-pdf");
|
||||||
addEndpointToGroup("Java", "scale-pages");
|
addEndpointToGroup("Java", "pdf-to-img");
|
||||||
|
addEndpointToGroup("Java", "img-to-pdf");
|
||||||
|
addEndpointToGroup("Java", "add-password");
|
||||||
//Javascript
|
addEndpointToGroup("Java", "remove-password");
|
||||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
addEndpointToGroup("Java", "change-permissions");
|
||||||
addEndpointToGroup("Javascript", "sign");
|
addEndpointToGroup("Java", "add-watermark");
|
||||||
addEndpointToGroup("Javascript", "compare");
|
addEndpointToGroup("Java", "add-image");
|
||||||
|
addEndpointToGroup("Java", "extract-images");
|
||||||
}
|
addEndpointToGroup("Java", "change-metadata");
|
||||||
|
addEndpointToGroup("Java", "cert-sign");
|
||||||
private void processEnvironmentConfigs() {
|
addEndpointToGroup("Java", "multi-page-layout");
|
||||||
String endpointsToRemove = System.getenv("ENDPOINTS_TO_REMOVE");
|
addEndpointToGroup("Java", "scale-pages");
|
||||||
String groupsToRemove = System.getenv("GROUPS_TO_REMOVE");
|
addEndpointToGroup("Java", "add-page-numbers");
|
||||||
|
addEndpointToGroup("Java", "auto-rename");
|
||||||
if (endpointsToRemove != null) {
|
addEndpointToGroup("Java", "auto-split-pdf");
|
||||||
String[] endpoints = endpointsToRemove.split(",");
|
addEndpointToGroup("Java", "sanitize-pdf");
|
||||||
for (String endpoint : endpoints) {
|
addEndpointToGroup("Java", "crop");
|
||||||
disableEndpoint(endpoint.trim());
|
addEndpointToGroup("Java", "get-info-on-pdf");
|
||||||
}
|
addEndpointToGroup("Java", "extract-page");
|
||||||
}
|
addEndpointToGroup("Java", "pdf-to-single-page");
|
||||||
|
addEndpointToGroup("Java", "markdown-to-pdf");
|
||||||
if (groupsToRemove != null) {
|
addEndpointToGroup("Java", "show-javascript");
|
||||||
String[] groups = groupsToRemove.split(",");
|
|
||||||
for (String group : groups) {
|
//Javascript
|
||||||
disableGroup(group.trim());
|
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||||
}
|
addEndpointToGroup("Javascript", "sign");
|
||||||
}
|
addEndpointToGroup("Javascript", "compare");
|
||||||
}
|
addEndpointToGroup("Javascript", "adjust-contrast");
|
||||||
|
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processEnvironmentConfigs() {
|
||||||
|
String endpointsToRemove = System.getenv("ENDPOINTS_TO_REMOVE");
|
||||||
|
String groupsToRemove = System.getenv("GROUPS_TO_REMOVE");
|
||||||
|
|
||||||
|
if (endpointsToRemove != null) {
|
||||||
|
String[] endpoints = endpointsToRemove.split(",");
|
||||||
|
for (String endpoint : endpoints) {
|
||||||
|
disableEndpoint(endpoint.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToRemove != null) {
|
||||||
|
String[] groups = groupsToRemove.split(",");
|
||||||
|
for (String group : groups) {
|
||||||
|
disableGroup(group.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ public class MetricsFilter extends OncePerRequestFilter {
|
|||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
|
//System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||||
// Ignore static resources
|
// Ignore static resources
|
||||||
if (!(uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
if (!(uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
||||||
Counter counter = Counter.builder("http.requests")
|
Counter counter = Counter.builder("http.requests")
|
||||||
@@ -36,6 +37,7 @@ public class MetricsFilter extends OncePerRequestFilter {
|
|||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
counter.increment();
|
counter.increment();
|
||||||
|
//System.out.println("Counted");
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
|
public static LocalDateTime startTime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||||
|
startTime = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ package stirling.software.SPDF.config;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -15,4 +16,12 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(endpointInterceptor);
|
registry.addInterceptor(endpointInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
// Handler for external static resources
|
||||||
|
registry.addResourceHandler("/**")
|
||||||
|
.addResourceLocations("file:customFiles/static/", "classpath:/static/")
|
||||||
|
.setCachePeriod(0); // Optional: disable caching
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.itextpdf.kernel.geom.PageSize;
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfReader;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener;
|
||||||
|
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
|
public class CropController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||||
|
@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> cropPdf(
|
||||||
|
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||||
|
@Parameter(description = "The x-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("x") float x,
|
||||||
|
@Parameter(description = "The y-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("y") float y,
|
||||||
|
@Parameter(description = "The width of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("width") float width,
|
||||||
|
@Parameter(description = "The height of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("height") float height) throws IOException {
|
||||||
|
byte[] bytes = file.getBytes();
|
||||||
|
System.out.println("x=" + x + ", " + "y=" + y + ", " + "width=" + width + ", " +"height=" + height );
|
||||||
|
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
|
||||||
|
PdfDocument pdfDoc = new PdfDocument(reader);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PdfWriter writer = new PdfWriter(baos);
|
||||||
|
PdfDocument outputPdf = new PdfDocument(writer);
|
||||||
|
|
||||||
|
int totalPages = pdfDoc.getNumberOfPages();
|
||||||
|
|
||||||
|
for (int i = 1; i <= totalPages; i++) {
|
||||||
|
PdfPage page = outputPdf.addNewPage(new PageSize(width, height));
|
||||||
|
PdfCanvas pdfCanvas = new PdfCanvas(page);
|
||||||
|
|
||||||
|
PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf);
|
||||||
|
|
||||||
|
// Save the graphics state, apply the transformations, add the object, and then
|
||||||
|
// restore the graphics state
|
||||||
|
pdfCanvas.saveState();
|
||||||
|
pdfCanvas.rectangle(x, y, width, height);
|
||||||
|
pdfCanvas.clip();
|
||||||
|
pdfCanvas.addXObject(formXObject, -x, -y);
|
||||||
|
pdfCanvas.restoreState();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
outputPdf.close();
|
||||||
|
byte[] pdfContent = baos.toByteArray();
|
||||||
|
pdfDoc.close();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfContent,
|
||||||
|
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,9 +17,11 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class MergeController {
|
public class MergeController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||||
@@ -47,7 +49,7 @@ public class MergeController {
|
|||||||
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Merge multiple PDF files into one",
|
summary = "Merge multiple PDF files into one",
|
||||||
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided."
|
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> mergePdfs(
|
public ResponseEntity<byte[]> mergePdfs(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|||||||
@@ -24,15 +24,17 @@ import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class MultiPageLayoutController {
|
public class MultiPageLayoutController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file.")
|
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||||
@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {
|
@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {
|
||||||
|
|||||||
@@ -18,16 +18,18 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class RearrangePagesPDFController {
|
public class RearrangePagesPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||||
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.")
|
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> deletePages(
|
public ResponseEntity<byte[]> deletePages(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
|
||||||
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)
|
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)
|
||||||
@@ -151,7 +153,7 @@ public class RearrangePagesPDFController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
||||||
@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.")
|
@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF")
|
||||||
public ResponseEntity<byte[]> rearrangePages(
|
public ResponseEntity<byte[]> rearrangePages(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile,
|
||||||
@RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder,
|
@RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder,
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class RotationController {
|
public class RotationController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
||||||
@@ -26,7 +28,7 @@ public class RotationController {
|
|||||||
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Rotate a PDF file",
|
summary = "Rotate a PDF file",
|
||||||
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90."
|
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> rotatePDF(
|
public ResponseEntity<byte[]> rotatePDF(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|||||||
@@ -39,22 +39,24 @@ import io.swagger.v3.oas.annotations.Hidden;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class ScalePagesController {
|
public class ScalePagesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file.")
|
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> scalePages(
|
public ResponseEntity<byte[]> scalePages(
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||||
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = {
|
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "string", allowableValues = {
|
||||||
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4",
|
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4",
|
||||||
"B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL",
|
"B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL",
|
||||||
"EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize,
|
"EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize,
|
||||||
@Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor)
|
@Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "integer")) @RequestParam("scaleFactor") float scaleFactor)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
Map<String, PageSize> sizeMap = new HashMap<>();
|
Map<String, PageSize> sizeMap = new HashMap<>();
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -28,17 +25,19 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPDFController {
|
public class SplitPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||||
@Operation(summary = "Split a PDF file into separate documents",
|
@Operation(summary = "Split a PDF file into separate documents",
|
||||||
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page.")
|
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
||||||
public ResponseEntity<byte[]> splitPdf(
|
public ResponseEntity<byte[]> splitPdf(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be split")
|
@Parameter(description = "The input PDF file to be split")
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import com.itextpdf.kernel.pdf.*;
|
||||||
|
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
||||||
|
import com.itextpdf.kernel.geom.PageSize;
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
|
import com.itextpdf.layout.Document;
|
||||||
|
import com.itextpdf.layout.element.Image;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
|
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
|
public class ToSinglePageController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class);
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
|
||||||
|
@Operation(
|
||||||
|
summary = "Convert a multi-page PDF into a single long page PDF",
|
||||||
|
description = "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO"
|
||||||
|
)
|
||||||
|
public ResponseEntity<byte[]> pdfToSinglePage(
|
||||||
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
@Parameter(description = "The input multi-page PDF file to be converted into a single page", required = true)
|
||||||
|
MultipartFile file) throws IOException {
|
||||||
|
|
||||||
|
PdfReader reader = new PdfReader(file.getInputStream());
|
||||||
|
PdfDocument sourceDocument = new PdfDocument(reader);
|
||||||
|
|
||||||
|
float totalHeight = 0;
|
||||||
|
float width = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) {
|
||||||
|
Rectangle pageSize = sourceDocument.getPage(i).getPageSize();
|
||||||
|
totalHeight += pageSize.getHeight();
|
||||||
|
if(width < pageSize.getWidth())
|
||||||
|
width = pageSize.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PdfWriter writer = new PdfWriter(baos);
|
||||||
|
PdfDocument newDocument = new PdfDocument(writer);
|
||||||
|
PageSize newPageSize = new PageSize(width, totalHeight);
|
||||||
|
newDocument.addNewPage(newPageSize);
|
||||||
|
|
||||||
|
Document layoutDoc = new Document(newDocument);
|
||||||
|
float yOffset = totalHeight;
|
||||||
|
|
||||||
|
for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) {
|
||||||
|
PdfFormXObject pageCopy = sourceDocument.getPage(i).copyAsFormXObject(newDocument);
|
||||||
|
Image copiedPage = new Image(pageCopy);
|
||||||
|
copiedPage.setFixedPosition(0, yOffset - sourceDocument.getPage(i).getPageSize().getHeight());
|
||||||
|
yOffset -= sourceDocument.getPage(i).getPageSize().getHeight();
|
||||||
|
layoutDoc.add(copiedPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutDoc.close();
|
||||||
|
sourceDocument.close();
|
||||||
|
|
||||||
|
byte[] result = baos.toByteArray();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
public class ConvertEpubToPdf {
|
||||||
|
//TODO
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/epub-to-single-pdf")
|
||||||
|
@Hidden
|
||||||
|
@Operation(
|
||||||
|
summary = "Convert an EPUB file to a single PDF",
|
||||||
|
description = "This endpoint takes an EPUB file input and converts it to a single PDF."
|
||||||
|
)
|
||||||
|
public ResponseEntity<byte[]> epubToSinglePdf(
|
||||||
|
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
if (fileInput == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide an EPUB file for conversion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = fileInput.getOriginalFilename();
|
||||||
|
if (originalFilename == null || !originalFilename.endsWith(".epub")) {
|
||||||
|
throw new IllegalArgumentException("File must be in .epub format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, byte[]> epubContents = extractEpubContent(fileInput);
|
||||||
|
List<String> htmlFilesOrder = getHtmlFilesOrderFromOpf(epubContents);
|
||||||
|
|
||||||
|
List<byte[]> individualPdfs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String htmlFile : htmlFilesOrder) {
|
||||||
|
byte[] htmlContent = epubContents.get(htmlFile);
|
||||||
|
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent, htmlFile.replace(".html", ".pdf"));
|
||||||
|
individualPdfs.add(pdfBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pseudo-code to merge individual PDFs into one.
|
||||||
|
byte[] mergedPdfBytes = mergeMultiplePdfsIntoOne(individualPdfs);
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(mergedPdfBytes, originalFilename.replace(".epub", ".pdf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming a pseudo-code function that merges multiple PDFs into one.
|
||||||
|
private byte[] mergeMultiplePdfsIntoOne(List<byte[]> individualPdfs) {
|
||||||
|
// You can use a library such as iText or PDFBox to perform the merging here.
|
||||||
|
// Return the byte[] of the merged PDF.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, byte[]> extractEpubContent(MultipartFile fileInput) throws IOException {
|
||||||
|
Map<String, byte[]> contentMap = new HashMap<>();
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(fileInput.getInputStream())) {
|
||||||
|
ZipEntry zipEntry = zis.getNextEntry();
|
||||||
|
while (zipEntry != null) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read = 0;
|
||||||
|
while ((read = zis.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
contentMap.put(zipEntry.getName(), baos.toByteArray());
|
||||||
|
zipEntry = zis.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getHtmlFilesOrderFromOpf(Map<String, byte[]> epubContents) throws Exception {
|
||||||
|
String opfContent = new String(epubContents.get("OEBPS/content.opf")); // Adjusting for given path
|
||||||
|
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||||
|
InputSource is = new InputSource(new StringReader(opfContent));
|
||||||
|
Document doc = dBuilder.parse(is);
|
||||||
|
|
||||||
|
NodeList itemRefs = doc.getElementsByTagName("itemref");
|
||||||
|
List<String> htmlFilesOrder = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < itemRefs.getLength(); i++) {
|
||||||
|
Element itemRef = (Element) itemRefs.item(i);
|
||||||
|
String idref = itemRef.getAttribute("idref");
|
||||||
|
|
||||||
|
NodeList items = doc.getElementsByTagName("item");
|
||||||
|
for (int j = 0; j < items.getLength(); j++) {
|
||||||
|
Element item = (Element) items.item(j);
|
||||||
|
if (idref.equals(item.getAttribute("id"))) {
|
||||||
|
htmlFilesOrder.add(item.getAttribute("href")); // Fetching the actual href
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlFilesOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
public class ConvertHtmlToPDF {
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/html-to-pdf")
|
||||||
|
@Operation(
|
||||||
|
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
|
||||||
|
description = "This endpoint takes an HTML or ZIP file input and converts it to a PDF format."
|
||||||
|
)
|
||||||
|
public ResponseEntity<byte[]> HtmlToPdf(
|
||||||
|
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput) throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
if (fileInput == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide an HTML or ZIP file for conversion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = fileInput.getOriginalFilename();
|
||||||
|
if (originalFilename == null || (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) {
|
||||||
|
throw new IllegalArgumentException("File must be either .html or .zip format.");
|
||||||
|
}byte[] pdfBytes = FileToPdf.convertHtmlToPdf( fileInput.getBytes(), originalFilename);
|
||||||
|
|
||||||
|
String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,16 +20,18 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertImgPDFController {
|
public class ConvertImgPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
|
||||||
@Operation(summary = "Convert PDF to image(s)",
|
@Operation(summary = "Convert PDF to image(s)",
|
||||||
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images.")
|
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional")
|
||||||
public ResponseEntity<Resource> convertToImage(
|
public ResponseEntity<Resource> convertToImage(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be converted")
|
@Parameter(description = "The input PDF file to be converted")
|
||||||
@@ -41,7 +43,7 @@ public class ConvertImgPDFController {
|
|||||||
@Parameter(description = "Choose between a single image containing all pages or separate images for each page", schema = @Schema(allowableValues = {"single", "multiple"}))
|
@Parameter(description = "Choose between a single image containing all pages or separate images for each page", schema = @Schema(allowableValues = {"single", "multiple"}))
|
||||||
String singleOrMultiple,
|
String singleOrMultiple,
|
||||||
@RequestParam("colorType")
|
@RequestParam("colorType")
|
||||||
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"rgb", "greyscale", "blackwhite"}))
|
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"color", "greyscale", "blackwhite"}))
|
||||||
String colorType,
|
String colorType,
|
||||||
@RequestParam("dpi")
|
@RequestParam("dpi")
|
||||||
@Parameter(description = "The DPI (dots per inch) for the output image(s)")
|
@Parameter(description = "The DPI (dots per inch) for the output image(s)")
|
||||||
@@ -83,7 +85,7 @@ public class ConvertImgPDFController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
|
||||||
@Operation(summary = "Convert images to a PDF file",
|
@Operation(summary = "Convert images to a PDF file",
|
||||||
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images.")
|
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?")
|
||||||
public ResponseEntity<byte[]> convertToPdf(
|
public ResponseEntity<byte[]> convertToPdf(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input images to be converted to a PDF file")
|
@Parameter(description = "The input images to be converted to a PDF file")
|
||||||
@@ -92,7 +94,7 @@ public class ConvertImgPDFController {
|
|||||||
@Parameter(description = "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", example = "false")
|
@Parameter(description = "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", example = "false")
|
||||||
boolean stretchToFit,
|
boolean stretchToFit,
|
||||||
@RequestParam("colorType")
|
@RequestParam("colorType")
|
||||||
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"rgb", "greyscale", "blackwhite"}))
|
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"color", "greyscale", "blackwhite"}))
|
||||||
String colorType,
|
String colorType,
|
||||||
@RequestParam(defaultValue = "false", name = "autoRotate")
|
@RequestParam(defaultValue = "false", name = "autoRotate")
|
||||||
@Parameter(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true")
|
@Parameter(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true")
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
public class ConvertMarkdownToPdf {
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/markdown-to-pdf")
|
||||||
|
@Operation(
|
||||||
|
summary = "Convert a Markdown file to PDF",
|
||||||
|
description = "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format."
|
||||||
|
)
|
||||||
|
public ResponseEntity<byte[]> markdownToPdf(
|
||||||
|
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
if (fileInput == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide a Markdown file for conversion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = fileInput.getOriginalFilename();
|
||||||
|
if (originalFilename == null || !originalFilename.endsWith(".md")) {
|
||||||
|
throw new IllegalArgumentException("File must be in .md format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Markdown to HTML using CommonMark
|
||||||
|
Parser parser = Parser.builder().build();
|
||||||
|
Node document = parser.parse(new String(fileInput.getBytes()));
|
||||||
|
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||||
|
String htmlContent = renderer.render(document);
|
||||||
|
|
||||||
|
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html");
|
||||||
|
|
||||||
|
String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,10 +17,13 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertOfficeController {
|
public class ConvertOfficeController {
|
||||||
|
|
||||||
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||||
@@ -39,7 +42,7 @@ public class ConvertOfficeController {
|
|||||||
|
|
||||||
// Run the LibreOffice command
|
// Run the LibreOffice command
|
||||||
List<String> command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString()));
|
List<String> command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString()));
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the converted PDF file
|
// Read the converted PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
@@ -57,13 +60,13 @@ public class ConvertOfficeController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using OCR",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
description = "This endpoint converts a given file to a PDF using Optical Character Recognition (OCR). The filename of the resulting PDF will be the original filename with '_convertedToPDF.pdf' appended."
|
description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
public ResponseEntity<byte[]> processFileToPDF(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(
|
@Parameter(
|
||||||
description = "The input file to be converted to a PDF file using OCR",
|
description = "The input file to be converted to a PDF file using LibreOffice",
|
||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
MultipartFile inputFile
|
MultipartFile inputFile
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.PDFToFile;
|
import stirling.software.SPDF.utils.PDFToFile;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertPDFToOffice {
|
public class ConvertPDFToOffice {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
|
||||||
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format.")
|
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToHTML(
|
public ResponseEntity<byte[]> processPdfToHTML(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
@@ -27,7 +29,7 @@ public class ConvertPDFToOffice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
|
||||||
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format.")
|
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToPresentation(
|
public ResponseEntity<byte[]> processPdfToPresentation(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
|
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
|
||||||
@@ -38,7 +40,7 @@ public class ConvertPDFToOffice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
|
||||||
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format.")
|
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToRTForTXT(
|
public ResponseEntity<byte[]> processPdfToRTForTXT(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
|
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
|
||||||
@@ -49,7 +51,7 @@ public class ConvertPDFToOffice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
|
||||||
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format.")
|
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToWord(
|
public ResponseEntity<byte[]> processPdfToWord(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
|
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
|
||||||
@@ -60,7 +62,7 @@ public class ConvertPDFToOffice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
|
||||||
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file.")
|
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToXML(
|
public ResponseEntity<byte[]> processPdfToXML(
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|||||||
@@ -14,16 +14,19 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertPDFToPDFA {
|
public class ConvertPDFToPDFA {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents."
|
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> pdfToPdfA(
|
public ResponseEntity<byte[]> pdfToPdfA(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@@ -47,7 +50,7 @@ public class ConvertPDFToPDFA {
|
|||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
public class ConvertWebsiteToPDF {
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/url-to-pdf")
|
||||||
|
@Operation(
|
||||||
|
summary = "Convert a URL to a PDF",
|
||||||
|
description = "This endpoint fetches content from a URL and converts it to a PDF format."
|
||||||
|
)
|
||||||
|
public ResponseEntity<byte[]> urlToPdf(
|
||||||
|
@RequestParam(required = true, value = "urlInput")
|
||||||
|
@Parameter(description = "The input URL to be converted to a PDF file", required = true)
|
||||||
|
String URL) throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
// Validate the URL format
|
||||||
|
if(!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
||||||
|
throw new IllegalArgumentException("Invalid URL format provided.");
|
||||||
|
}
|
||||||
|
Path tempOutputFile = null;
|
||||||
|
byte[] pdfBytes;
|
||||||
|
try {
|
||||||
|
// Prepare the output file path
|
||||||
|
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
|
// Prepare the OCRmyPDF command
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add("weasyprint");
|
||||||
|
command.add(URL);
|
||||||
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
// Read the optimized PDF file
|
||||||
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// Clean up the temporary files
|
||||||
|
Files.delete(tempOutputFile);
|
||||||
|
}
|
||||||
|
// Convert URL to a safe filename
|
||||||
|
String outputFilename = convertURLToFileName(URL);
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertURLToFileName(String url) {
|
||||||
|
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_");
|
||||||
|
if(safeName.length() > 50) {
|
||||||
|
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
||||||
|
}
|
||||||
|
return safeName + ".pdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.filters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Filter", description = "Filter APIs")
|
||||||
|
public class FilterController {
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
||||||
|
@Operation(summary = "Checks if a PDF contains set text, returns true if does", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> containsText(
|
||||||
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile,
|
||||||
|
@Parameter(description = "The text to check for", required = true) String text,
|
||||||
|
@Parameter(description = "The page number to check for text on accepts 'All', ranges like '1-4'", required = false) String pageNumber)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
||||||
|
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
||||||
|
@Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> containsImage(
|
||||||
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile,
|
||||||
|
@Parameter(description = "The page number to check for image on accepts 'All', ranges like '1-4'", required = false) String pageNumber)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
||||||
|
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
||||||
|
@Operation(summary = "Checks if a PDF is greater, less or equal to a setPageCount", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> pageCount(
|
||||||
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
||||||
|
@Parameter(description = "Page Count", required = true) String pageCount,
|
||||||
|
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
||||||
|
"Greater", "Equal", "Less" })) String comparator)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
// Load the PDF
|
||||||
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
int actualPageCount = document.getNumberOfPages();
|
||||||
|
|
||||||
|
boolean valid = false;
|
||||||
|
// Perform the comparison
|
||||||
|
switch (comparator) {
|
||||||
|
case "Greater":
|
||||||
|
valid = actualPageCount > Integer.parseInt(pageCount);
|
||||||
|
break;
|
||||||
|
case "Equal":
|
||||||
|
valid = actualPageCount == Integer.parseInt(pageCount);
|
||||||
|
break;
|
||||||
|
case "Less":
|
||||||
|
valid = actualPageCount < Integer.parseInt(pageCount);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
||||||
|
@Operation(summary = "Checks if a PDF is of a certain size", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> pageSize(
|
||||||
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
||||||
|
@Parameter(description = "Standard Page Size", required = true) String standardPageSize,
|
||||||
|
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
||||||
|
"Greater", "Equal", "Less" })) String comparator)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
// Load the PDF
|
||||||
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
|
||||||
|
PDPage firstPage = document.getPage(0);
|
||||||
|
PDRectangle actualPageSize = firstPage.getMediaBox();
|
||||||
|
|
||||||
|
// Calculate the area of the actual page size
|
||||||
|
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
|
||||||
|
|
||||||
|
// Get the standard size and calculate its area
|
||||||
|
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
|
||||||
|
float standardArea = standardSize.getWidth() * standardSize.getHeight();
|
||||||
|
|
||||||
|
boolean valid = false;
|
||||||
|
// Perform the comparison
|
||||||
|
switch (comparator) {
|
||||||
|
case "Greater":
|
||||||
|
valid = actualArea > standardArea;
|
||||||
|
break;
|
||||||
|
case "Equal":
|
||||||
|
valid = actualArea == standardArea;
|
||||||
|
break;
|
||||||
|
case "Less":
|
||||||
|
valid = actualArea < standardArea;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
||||||
|
@Operation(summary = "Checks if a PDF is a set file size", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> fileSize(
|
||||||
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
||||||
|
@Parameter(description = "File Size", required = true) String fileSize,
|
||||||
|
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
||||||
|
"Greater", "Equal", "Less" })) String comparator)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
// Get the file size
|
||||||
|
long actualFileSize = inputFile.getSize();
|
||||||
|
|
||||||
|
boolean valid = false;
|
||||||
|
// Perform the comparison
|
||||||
|
switch (comparator) {
|
||||||
|
case "Greater":
|
||||||
|
valid = actualFileSize > Long.parseLong(fileSize);
|
||||||
|
break;
|
||||||
|
case "Equal":
|
||||||
|
valid = actualFileSize == Long.parseLong(fileSize);
|
||||||
|
break;
|
||||||
|
case "Less":
|
||||||
|
valid = actualFileSize < Long.parseLong(fileSize);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
||||||
|
@Operation(summary = "Checks if a PDF is of a certain rotation", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> pageRotation(
|
||||||
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
||||||
|
@Parameter(description = "Rotation in degrees", required = true) int rotation,
|
||||||
|
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
||||||
|
"Greater", "Equal", "Less" })) String comparator)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
// Load the PDF
|
||||||
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
|
||||||
|
// Get the rotation of the first page
|
||||||
|
PDPage firstPage = document.getPage(0);
|
||||||
|
int actualRotation = firstPage.getRotation();
|
||||||
|
boolean valid = false;
|
||||||
|
// Perform the comparison
|
||||||
|
switch (comparator) {
|
||||||
|
case "Greater":
|
||||||
|
valid = actualRotation > rotation;
|
||||||
|
break;
|
||||||
|
case "Equal":
|
||||||
|
valid = actualRotation == rotation;
|
||||||
|
break;
|
||||||
|
case "Less":
|
||||||
|
valid = actualRotation < rotation;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.*;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
|
import org.apache.tomcat.util.http.ResponseUtil;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.itextpdf.io.font.constants.StandardFonts;
|
||||||
|
import com.itextpdf.kernel.font.PdfFont;
|
||||||
|
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfReader;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
||||||
|
import com.itextpdf.layout.Canvas;
|
||||||
|
import com.itextpdf.layout.element.Paragraph;
|
||||||
|
import com.itextpdf.layout.properties.TextAlignment;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
|
import org.apache.pdfbox.text.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
|
public class AutoRenameController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class);
|
||||||
|
|
||||||
|
private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
|
||||||
|
private static final int LINE_LIMIT = 11;
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/auto-rename")
|
||||||
|
@Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> extractHeader(
|
||||||
|
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the header is to be extracted.", required = true) MultipartFile file,
|
||||||
|
@RequestParam(required = false, defaultValue = "false") @Parameter(description = "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", required = false) Boolean useFirstTextAsFallback)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
PDDocument document = PDDocument.load(file.getInputStream());
|
||||||
|
PDFTextStripper reader = new PDFTextStripper() {
|
||||||
|
class LineInfo {
|
||||||
|
String text;
|
||||||
|
float fontSize;
|
||||||
|
|
||||||
|
LineInfo(String text, float fontSize) {
|
||||||
|
this.text = text;
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LineInfo> lineInfos = new ArrayList<>();
|
||||||
|
StringBuilder lineBuilder = new StringBuilder();
|
||||||
|
float lastY = -1;
|
||||||
|
float maxFontSizeInLine = 0.0f;
|
||||||
|
int lineCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processTextPosition(TextPosition text) {
|
||||||
|
if (lastY != text.getY() && lineCount < LINE_LIMIT) {
|
||||||
|
processLine();
|
||||||
|
lineBuilder = new StringBuilder(text.getUnicode());
|
||||||
|
maxFontSizeInLine = text.getFontSizeInPt();
|
||||||
|
lastY = text.getY();
|
||||||
|
lineCount++;
|
||||||
|
} else if (lineCount < LINE_LIMIT) {
|
||||||
|
lineBuilder.append(text.getUnicode());
|
||||||
|
if (text.getFontSizeInPt() > maxFontSizeInLine) {
|
||||||
|
maxFontSizeInLine = text.getFontSizeInPt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processLine() {
|
||||||
|
if (lineBuilder.length() > 0 && lineCount < LINE_LIMIT) {
|
||||||
|
lineInfos.add(new LineInfo(lineBuilder.toString(), maxFontSizeInLine));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText(PDDocument doc) throws IOException {
|
||||||
|
this.lineInfos.clear();
|
||||||
|
this.lineBuilder = new StringBuilder();
|
||||||
|
this.lastY = -1;
|
||||||
|
this.maxFontSizeInLine = 0.0f;
|
||||||
|
this.lineCount = 0;
|
||||||
|
super.getText(doc);
|
||||||
|
processLine(); // Process the last line
|
||||||
|
|
||||||
|
// Merge lines with same font size
|
||||||
|
List<LineInfo> mergedLineInfos = new ArrayList<>();
|
||||||
|
for (int i = 0; i < lineInfos.size(); i++) {
|
||||||
|
String mergedText = lineInfos.get(i).text;
|
||||||
|
float fontSize = lineInfos.get(i).fontSize;
|
||||||
|
while (i + 1 < lineInfos.size() && lineInfos.get(i + 1).fontSize == fontSize) {
|
||||||
|
mergedText += " " + lineInfos.get(i + 1).text;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
mergedLineInfos.add(new LineInfo(mergedText, fontSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort lines by font size in descending order and get the first one
|
||||||
|
mergedLineInfos.sort(Comparator.comparing((LineInfo li) -> li.fontSize).reversed());
|
||||||
|
String title = mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(0).text;
|
||||||
|
|
||||||
|
return title != null ? title : (useFirstTextAsFallback ? (mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(mergedLineInfos.size() - 1).text) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
String header = reader.getText(document);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sanitize the header string by removing characters not allowed in a filename.
|
||||||
|
if (header != null && header.length() < 255) {
|
||||||
|
header = header.replaceAll("[/\\\\?%*:|\"<>]", "");
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
|
||||||
|
} else {
|
||||||
|
logger.info("File has no good title to be found");
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(document, file.getOriginalFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.awt.image.DataBufferInt;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.LuminanceSource;
|
||||||
|
import com.google.zxing.MultiFormatReader;
|
||||||
|
import com.google.zxing.NotFoundException;
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class AutoSplitPdfController {
|
||||||
|
|
||||||
|
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
|
||||||
|
|
||||||
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
|
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> autoSplitPdf(
|
||||||
|
@RequestParam("fileInput") @Parameter(description = "The input PDF file which needs to be split into separate documents based on QR code boundaries.", required = true) MultipartFile file,
|
||||||
|
@RequestParam(value ="duplexMode",defaultValue = "false") @Parameter(description = "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", required = false) boolean duplexMode)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
InputStream inputStream = file.getInputStream();
|
||||||
|
PDDocument document = PDDocument.load(inputStream);
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
|
||||||
|
List<PDDocument> splitDocuments = new ArrayList<>();
|
||||||
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||||
|
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
||||||
|
String result = decodeQRCode(bim);
|
||||||
|
|
||||||
|
if (QR_CONTENT.equals(result) && page != 0) {
|
||||||
|
splitDocuments.add(new PDDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!splitDocuments.isEmpty() && !QR_CONTENT.equals(result)) {
|
||||||
|
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
||||||
|
} else if (page == 0) {
|
||||||
|
PDDocument firstDocument = new PDDocument();
|
||||||
|
firstDocument.addPage(document.getPage(page));
|
||||||
|
splitDocuments.add(firstDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If duplexMode is true and current page is a divider, then skip next page
|
||||||
|
if (duplexMode && QR_CONTENT.equals(result)) {
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove split documents that have no pages
|
||||||
|
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
||||||
|
|
||||||
|
for (PDDocument splitDocument : splitDocuments) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
splitDocument.save(baos);
|
||||||
|
splitDocumentsBoas.add(baos);
|
||||||
|
splitDocument.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||||
|
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
|
||||||
|
byte[] data;
|
||||||
|
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||||
|
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||||
|
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||||
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
|
byte[] pdf = baos.toByteArray();
|
||||||
|
|
||||||
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
|
zipOut.putNextEntry(pdfEntry);
|
||||||
|
zipOut.write(pdf);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
data = Files.readAllBytes(zipFile);
|
||||||
|
Files.delete(zipFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String decodeQRCode(BufferedImage bufferedImage) {
|
||||||
|
LuminanceSource source;
|
||||||
|
|
||||||
|
if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte) {
|
||||||
|
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
|
||||||
|
source = new PlanarYUVLuminanceSource(pixels, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), false);
|
||||||
|
} else if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferInt) {
|
||||||
|
int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
|
||||||
|
byte[] newPixels = new byte[pixels.length];
|
||||||
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
|
newPixels[i] = (byte) (pixels[i] & 0xff);
|
||||||
|
}
|
||||||
|
source = new PlanarYUVLuminanceSource(newPixels, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), false);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data");
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Result result = new MultiFormatReader().decode(bitmap);
|
||||||
|
return result.getText();
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
return null; // there is no QR code in the image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,17 +28,20 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import stirling.software.SPDF.pdf.ImageFinder;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class BlankPageController {
|
public class BlankPageController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages."
|
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> removeBlankPages(
|
public ResponseEntity<byte[]> removeBlankPages(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@@ -71,7 +74,7 @@ public class BlankPageController {
|
|||||||
pagesToKeepIndex.add(pageIndex);
|
pagesToKeepIndex.add(pageIndex);
|
||||||
System.out.println("page " + pageIndex + " has text");
|
System.out.println("page " + pageIndex + " has text");
|
||||||
} else {
|
} else {
|
||||||
boolean hasImages = hasImagesOnPage(page);
|
boolean hasImages = PdfUtils.hasImagesOnPage(page);
|
||||||
if (hasImages) {
|
if (hasImages) {
|
||||||
System.out.println("page " + pageIndex + " has image");
|
System.out.println("page " + pageIndex + " has image");
|
||||||
|
|
||||||
@@ -84,10 +87,10 @@ public class BlankPageController {
|
|||||||
List<String> command = new ArrayList<>(Arrays.asList("python3", System.getProperty("user.dir") + "/scripts/detect-blank-pages.py", tempFile.toString() ,"--threshold", String.valueOf(threshold), "--white_percent", String.valueOf(whitePercent)));
|
List<String> command = new ArrayList<>(Arrays.asList("python3", System.getProperty("user.dir") + "/scripts/detect-blank-pages.py", tempFile.toString() ,"--threshold", String.valueOf(threshold), "--white_percent", String.valueOf(whitePercent)));
|
||||||
|
|
||||||
// Run CLI command
|
// Run CLI command
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// does contain data
|
// does contain data
|
||||||
if (returnCode == 0) {
|
if (returnCode.getRc() == 0) {
|
||||||
System.out.println("page " + pageIndex + " has image which is not blank");
|
System.out.println("page " + pageIndex + " has image which is not blank");
|
||||||
pagesToKeepIndex.add(pageIndex);
|
pagesToKeepIndex.add(pageIndex);
|
||||||
} else {
|
} else {
|
||||||
@@ -120,9 +123,5 @@ public class BlankPageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static boolean hasImagesOnPage(PDPage page) throws IOException {
|
|
||||||
ImageFinder imageFinder = new ImageFinder(page);
|
|
||||||
imageFinder.processPage(page);
|
|
||||||
return imageFinder.hasImages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,17 +31,20 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class CompressController {
|
public class CompressController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters.")
|
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> optimizePdf(
|
public ResponseEntity<byte[]> optimizePdf(
|
||||||
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
|
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
|
||||||
@RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {
|
@RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {
|
||||||
@@ -114,7 +117,7 @@ public class CompressController {
|
|||||||
command.add("-sOutputFile=" + tempOutputFile.toString());
|
command.add("-sOutputFile=" + tempOutputFile.toString());
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
|
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Check if file size is within expected size or not auto mode so instantly finish
|
// Check if file size is within expected size or not auto mode so instantly finish
|
||||||
long outputFileSize = Files.size(tempOutputFile);
|
long outputFileSize = Files.size(tempOutputFile);
|
||||||
@@ -219,6 +222,15 @@ public class CompressController {
|
|||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
|
// Check if optimized file is larger than the original
|
||||||
|
if(pdfBytes.length > inputFileSize) {
|
||||||
|
// Log the occurrence
|
||||||
|
logger.warn("Optimized file is larger than the original. Returning the original file instead.");
|
||||||
|
|
||||||
|
// Read the original file again
|
||||||
|
pdfBytes = Files.readAllBytes(tempInputFile);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.delete(tempInputFile);
|
Files.delete(tempInputFile);
|
||||||
Files.delete(tempOutputFile);
|
Files.delete(tempOutputFile);
|
||||||
|
|||||||
@@ -31,17 +31,20 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class ExtractImageScansController {
|
public class ExtractImageScansController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
||||||
@Operation(summary = "Extract image scans from an input file",
|
@Operation(summary = "Extract image scans from an input file",
|
||||||
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size.")
|
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||||
public ResponseEntity<byte[]> extractImageScans(
|
public ResponseEntity<byte[]> extractImageScans(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input file containing image scans")
|
@Parameter(description = "The input file containing image scans")
|
||||||
@@ -115,7 +118,7 @@ public class ExtractImageScansController {
|
|||||||
|
|
||||||
|
|
||||||
// Run CLI command
|
// Run CLI command
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the output photos in temp directory
|
// Read the output photos in temp directory
|
||||||
List<Path> tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList());
|
List<Path> tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList());
|
||||||
|
|||||||
@@ -29,15 +29,17 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class ExtractImagesController {
|
public class ExtractImagesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||||
@Operation(summary = "Extract images from a PDF file",
|
@Operation(summary = "Extract images from a PDF file",
|
||||||
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format.")
|
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||||
public ResponseEntity<byte[]> extractImages(
|
public ResponseEntity<byte[]> extractImages(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file containing images")
|
@Parameter(description = "The input PDF file containing images")
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.AffineTransformOp;
|
||||||
|
//Required for image manipulation
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.BufferedImageOp;
|
||||||
|
import java.awt.image.ConvolveOp;
|
||||||
|
import java.awt.image.Kernel;
|
||||||
|
import java.awt.image.RescaleOp;
|
||||||
|
//Required for file input/output
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
//Other required classes
|
||||||
import java.nio.file.Path;
|
import java.util.Random;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
//Required for image input/output
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -17,46 +36,17 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import com.itextpdf.io.source.ByteArrayOutputStream;
|
import com.itextpdf.io.source.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
//Required for PDF manipulation
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
|
||||||
|
|
||||||
|
|
||||||
//Required for image manipulation
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.BufferedImageOp;
|
|
||||||
import java.awt.image.RescaleOp;
|
|
||||||
import java.awt.image.AffineTransformOp;
|
|
||||||
import java.awt.image.ConvolveOp;
|
|
||||||
import java.awt.image.Kernel;
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
|
|
||||||
//Required for image input/output
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
//Required for file input/output
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
//Other required classes
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class FakeScanController {
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
|
public class FakeScanControllerWIP {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class);
|
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -19,9 +19,11 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class MetadataController {
|
public class MetadataController {
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ public class MetadataController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
||||||
@Operation(summary = "Update metadata of a PDF file",
|
@Operation(summary = "Update metadata of a PDF file",
|
||||||
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields.")
|
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> metadata(
|
public ResponseEntity<byte[]> metadata(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to update metadata")
|
@Parameter(description = "The input PDF file to update metadata")
|
||||||
@@ -73,6 +75,7 @@ public class MetadataController {
|
|||||||
@RequestParam(value = "trapped", required = false)
|
@RequestParam(value = "trapped", required = false)
|
||||||
@Parameter(description = "The trapped status of the document")
|
@Parameter(description = "The trapped status of the document")
|
||||||
String trapped,
|
String trapped,
|
||||||
|
@Parameter(description = "Map list of key and value of custom parameters, note these must start with customKey and customValue if they are non standard")
|
||||||
@RequestParam Map<String, String> allRequestParams)
|
@RequestParam Map<String, String> allRequestParams)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,13 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class OCRController {
|
public class OCRController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
||||||
@@ -47,7 +50,7 @@ public class OCRController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
@Operation(summary = "Process a PDF file with OCR",
|
@Operation(summary = "Process a PDF file with OCR",
|
||||||
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options.")
|
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
public ResponseEntity<byte[]> processPdfWithOCR(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be processed with OCR")
|
@Parameter(description = "The input PDF file to be processed with OCR")
|
||||||
@@ -139,8 +142,12 @@ public class OCRController {
|
|||||||
command.addAll(Arrays.asList("--language", languageOption, tempInputFile.toString(), tempOutputFile.toString()));
|
command.addAll(Arrays.asList("--language", languageOption, tempInputFile.toString(), tempOutputFile.toString()));
|
||||||
|
|
||||||
// Run CLI command
|
// Run CLI command
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(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 = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -151,7 +158,7 @@ public class OCRController {
|
|||||||
|
|
||||||
List<String> gsCommand = Arrays.asList("gs", "-sDEVICE=pdfwrite", "-dFILTERIMAGE", "-o", tempPdfWithoutImages.toString(), tempOutputFile.toString());
|
List<String> gsCommand = Arrays.asList("gs", "-sDEVICE=pdfwrite", "-dFILTERIMAGE", "-o", tempPdfWithoutImages.toString(), tempOutputFile.toString());
|
||||||
|
|
||||||
int gsReturnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand);
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand);
|
||||||
tempOutputFile = tempPdfWithoutImages;
|
tempOutputFile = tempPdfWithoutImages;
|
||||||
}
|
}
|
||||||
// Read the OCR processed PDF file
|
// Read the OCR processed PDF file
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class OverlayImageController {
|
public class OverlayImageController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
||||||
@@ -25,7 +27,7 @@ public class OverlayImageController {
|
|||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay image onto a PDF file",
|
summary = "Overlay image onto a PDF file",
|
||||||
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified."
|
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> overlayImage(
|
public ResponseEntity<byte[]> overlayImage(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.*;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.tomcat.util.http.ResponseUtil;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.itextpdf.io.font.constants.StandardFonts;
|
||||||
|
import com.itextpdf.kernel.font.PdfFont;
|
||||||
|
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfReader;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
||||||
|
import com.itextpdf.layout.Canvas;
|
||||||
|
import com.itextpdf.layout.element.Paragraph;
|
||||||
|
import com.itextpdf.layout.properties.TextAlignment;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
|
public class PageNumbersController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
||||||
|
|
||||||
|
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||||
|
@Operation(summary = "Add page numbers to a PDF document", description = "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> addPageNumbers(
|
||||||
|
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||||
|
@Parameter(description = "Custom margin: small/medium/large", required = true, schema = @Schema(type = "string", allowableValues = {"small", "medium", "large"})) @RequestParam("customMargin") String customMargin,
|
||||||
|
@Parameter(description = "Position: 1 of 9 positions", required = true, schema = @Schema(type = "integer", minimum = "1", maximum = "9")) @RequestParam("position") int position,
|
||||||
|
@Parameter(description = "Starting number", required = true, schema = @Schema(type = "integer", minimum = "1")) @RequestParam("startingNumber") int startingNumber,
|
||||||
|
@Parameter(description = "Which pages to number, default all", required = false, schema = @Schema(type = "string")) @RequestParam(value = "pagesToNumber", required = false) String pagesToNumber,
|
||||||
|
@Parameter(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"", required = false, schema = @Schema(type = "string")) @RequestParam(value = "customText", required = false) String customText)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
|
||||||
|
|
||||||
|
int pageNumber = startingNumber;
|
||||||
|
float marginFactor;
|
||||||
|
switch (customMargin.toLowerCase()) {
|
||||||
|
case "small":
|
||||||
|
marginFactor = 0.02f;
|
||||||
|
break;
|
||||||
|
case "medium":
|
||||||
|
marginFactor = 0.035f;
|
||||||
|
break;
|
||||||
|
case "large":
|
||||||
|
marginFactor = 0.05f;
|
||||||
|
break;
|
||||||
|
case "x-large":
|
||||||
|
marginFactor = 0.1f;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
marginFactor = 0.035f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fontSize = 12.0f;
|
||||||
|
|
||||||
|
PdfReader reader = new PdfReader(bais);
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PdfWriter writer = new PdfWriter(baos);
|
||||||
|
|
||||||
|
PdfDocument pdfDoc = new PdfDocument(reader, writer);
|
||||||
|
|
||||||
|
List<Integer> pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), pdfDoc.getNumberOfPages());
|
||||||
|
|
||||||
|
for (int i : pagesToNumberList) {
|
||||||
|
PdfPage page = pdfDoc.getPage(i+1);
|
||||||
|
Rectangle pageSize = page.getPageSize();
|
||||||
|
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdfDoc);
|
||||||
|
|
||||||
|
String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(pdfDoc.getNumberOfPages())) : String.valueOf(pageNumber);
|
||||||
|
|
||||||
|
PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA);
|
||||||
|
float textWidth = font.getWidth(text, fontSize);
|
||||||
|
float textHeight = font.getAscent(text, fontSize) - font.getDescent(text, fontSize);
|
||||||
|
|
||||||
|
float x, y;
|
||||||
|
TextAlignment alignment;
|
||||||
|
|
||||||
|
int xGroup = (position - 1) % 3;
|
||||||
|
int yGroup = 2 - (position - 1) / 3;
|
||||||
|
|
||||||
|
switch (xGroup) {
|
||||||
|
case 0: // left
|
||||||
|
x = pageSize.getLeft() + marginFactor * pageSize.getWidth();
|
||||||
|
alignment = TextAlignment.LEFT;
|
||||||
|
break;
|
||||||
|
case 1: // center
|
||||||
|
x = pageSize.getLeft() + (pageSize.getWidth()) / 2;
|
||||||
|
alignment = TextAlignment.CENTER;
|
||||||
|
break;
|
||||||
|
default: // right
|
||||||
|
x = pageSize.getRight() - marginFactor * pageSize.getWidth();
|
||||||
|
alignment = TextAlignment.RIGHT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (yGroup) {
|
||||||
|
case 0: // bottom
|
||||||
|
y = pageSize.getBottom() + marginFactor * pageSize.getHeight();
|
||||||
|
break;
|
||||||
|
case 1: // middle
|
||||||
|
y = pageSize.getBottom() + (pageSize.getHeight() ) / 2;
|
||||||
|
break;
|
||||||
|
default: // top
|
||||||
|
y = pageSize.getTop() - marginFactor * pageSize.getHeight();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Canvas(pdfCanvas, page.getPageSize())
|
||||||
|
.showTextAligned(new Paragraph(text).setFont(font).setFontSize(fontSize), x, y, alignment);
|
||||||
|
|
||||||
|
pageNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pdfDoc.close();
|
||||||
|
byte[] resultBytes = baos.toByteArray();
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(resultBytes, URLEncoder.encode(file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", "UTF-8"), MediaType.APPLICATION_PDF);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,10 +16,13 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class RepairController {
|
public class RepairController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
||||||
@@ -27,7 +30,7 @@ public class RepairController {
|
|||||||
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response."
|
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> repairPdf(
|
public ResponseEntity<byte[]> repairPdf(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@@ -49,7 +52,7 @@ public class RepairController {
|
|||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
|
|
||||||
|
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.other;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.*;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
|
import org.apache.tomcat.util.http.ResponseUtil;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.itextpdf.io.font.constants.StandardFonts;
|
||||||
|
import com.itextpdf.kernel.font.PdfFont;
|
||||||
|
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfReader;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfStream;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfArray;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDictionary;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfName;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfObject;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
||||||
|
import com.itextpdf.layout.Canvas;
|
||||||
|
import com.itextpdf.layout.element.Paragraph;
|
||||||
|
import com.itextpdf.layout.properties.TextAlignment;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.*;
|
||||||
|
import org.apache.pdfbox.text.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.*;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
|
public class ShowJavascript {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
||||||
|
@Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> extractHeader(
|
||||||
|
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the javascript is to be extracted.", required = true) MultipartFile inputFile)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
try (
|
||||||
|
PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream()))
|
||||||
|
) {
|
||||||
|
|
||||||
|
String name = "";
|
||||||
|
String script = "";
|
||||||
|
String entryName = "File: "+inputFile.getOriginalFilename() + ", Script: ";
|
||||||
|
//Javascript
|
||||||
|
PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
|
||||||
|
if (namesDict != null) {
|
||||||
|
PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript);
|
||||||
|
if (javascriptDict != null) {
|
||||||
|
|
||||||
|
PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names);
|
||||||
|
for (int i = 0; i < namesArray.size(); i += 2) {
|
||||||
|
if(namesArray.getAsString(i) != null)
|
||||||
|
name = namesArray.getAsString(i).toString();
|
||||||
|
|
||||||
|
PdfObject jsCode = namesArray.get(i+1);
|
||||||
|
if (jsCode instanceof PdfStream) {
|
||||||
|
byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes();
|
||||||
|
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
||||||
|
script = "//" + entryName + name + "\n" +jsCodeStr;
|
||||||
|
|
||||||
|
} else if (jsCode instanceof PdfDictionary) {
|
||||||
|
// If the JS code is in a dictionary, you'll need to know the key to use.
|
||||||
|
// Assuming the key is PdfName.JS:
|
||||||
|
PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS);
|
||||||
|
if (jsCodeStream != null) {
|
||||||
|
byte[] jsCodeBytes = jsCodeStream.getBytes();
|
||||||
|
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
||||||
|
script = "//" + entryName + name + "\n" +jsCodeStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(script.equals("")) {
|
||||||
|
script = "PDF '" +inputFile.getOriginalFilename() + "' does not contain Javascript";
|
||||||
|
}
|
||||||
|
return WebResponseUtils.bytesToWebResponse(script.getBytes(), name + ".js");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,516 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
||||||
|
public class PipelineController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
final String jsonFileName = "pipelineConfig.json";
|
||||||
|
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
||||||
|
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 25000)
|
||||||
|
public void scanFolders() {
|
||||||
|
logger.info("Scanning folders...");
|
||||||
|
Path watchedFolderPath = Paths.get(watchedFoldersDir);
|
||||||
|
if (!Files.exists(watchedFolderPath)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(watchedFolderPath);
|
||||||
|
logger.info("Created directory: {}", watchedFolderPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error creating directory: {}", watchedFolderPath, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (Stream<Path> paths = Files.walk(watchedFolderPath)) {
|
||||||
|
paths.filter(Files::isDirectory).forEach(t -> {
|
||||||
|
try {
|
||||||
|
if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) {
|
||||||
|
handleDirectory(t);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error handling directory: {}", t, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error walking through directory: {}", watchedFolderPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDirectory(Path dir) throws Exception {
|
||||||
|
logger.info("Handling directory: {}", dir);
|
||||||
|
Path jsonFile = dir.resolve(jsonFileName);
|
||||||
|
Path processingDir = dir.resolve("processing"); // Directory to move files during processing
|
||||||
|
if (!Files.exists(processingDir)) {
|
||||||
|
Files.createDirectory(processingDir);
|
||||||
|
logger.info("Created processing directory: {}", processingDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.exists(jsonFile)) {
|
||||||
|
// Read JSON file
|
||||||
|
String jsonString;
|
||||||
|
try {
|
||||||
|
jsonString = new String(Files.readAllBytes(jsonFile));
|
||||||
|
logger.info("Read JSON file: {}", jsonFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error reading JSON file: {}", jsonFile, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode JSON to PipelineConfig
|
||||||
|
PipelineConfig config;
|
||||||
|
try {
|
||||||
|
config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
|
// Assuming your PipelineConfig class has getters for all necessary fields, you
|
||||||
|
// can perform checks here
|
||||||
|
if (config.getOperations() == null || config.getOutputDir() == null || config.getName() == null) {
|
||||||
|
throw new IOException("Invalid JSON format");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error parsing PipelineConfig: {}", jsonString, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each operation in the pipeline
|
||||||
|
for (PipelineOperation operation : config.getOperations()) {
|
||||||
|
// Collect all files based on fileInput
|
||||||
|
File[] files;
|
||||||
|
String fileInput = (String) operation.getParameters().get("fileInput");
|
||||||
|
if ("automated".equals(fileInput)) {
|
||||||
|
// If fileInput is "automated", process all files in the directory
|
||||||
|
try (Stream<Path> paths = Files.list(dir)) {
|
||||||
|
files = paths
|
||||||
|
.filter(path -> !Files.isDirectory(path)) // exclude directories
|
||||||
|
.filter(path -> !path.equals(jsonFile)) // exclude jsonFile
|
||||||
|
.map(Path::toFile)
|
||||||
|
.toArray(File[]::new);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If fileInput contains a path, process only this file
|
||||||
|
files = new File[] { new File(fileInput) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the files for processing
|
||||||
|
List<File> filesToProcess = new ArrayList<>();
|
||||||
|
for (File file : files) {
|
||||||
|
logger.info(file.getName());
|
||||||
|
logger.info("{} to {}",file.toPath(), processingDir.resolve(file.getName()));
|
||||||
|
Files.move(file.toPath(), processingDir.resolve(file.getName()));
|
||||||
|
filesToProcess.add(processingDir.resolve(file.getName()).toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the files
|
||||||
|
try {
|
||||||
|
List<Resource> resources = handleFiles(filesToProcess.toArray(new File[0]), jsonString);
|
||||||
|
|
||||||
|
if(resources == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Move resultant files and rename them as per config in JSON file
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
String resourceName = resource.getFilename();
|
||||||
|
String baseName = resourceName.substring(0, resourceName.lastIndexOf("."));
|
||||||
|
String extension = resourceName.substring(resourceName.lastIndexOf(".")+1);
|
||||||
|
|
||||||
|
String outputFileName = config.getOutputPattern().replace("{filename}", baseName);
|
||||||
|
|
||||||
|
outputFileName = outputFileName.replace("{pipelineName}", config.getName());
|
||||||
|
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||||
|
outputFileName = outputFileName.replace("{date}", LocalDate.now().format(dateFormatter));
|
||||||
|
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmmss");
|
||||||
|
outputFileName = outputFileName.replace("{time}", LocalTime.now().format(timeFormatter));
|
||||||
|
|
||||||
|
outputFileName += "." + extension;
|
||||||
|
// {filename} {folder} {date} {tmime} {pipeline}
|
||||||
|
String outputDir = config.getOutputDir();
|
||||||
|
|
||||||
|
// Check if the environment variable 'automatedOutputFolder' is set
|
||||||
|
String outputFolder = System.getenv("automatedOutputFolder");
|
||||||
|
|
||||||
|
if (outputFolder == null || outputFolder.isEmpty()) {
|
||||||
|
// If the environment variable is not set, use the default value
|
||||||
|
outputFolder = finishedFoldersDir;
|
||||||
|
}
|
||||||
|
logger.info("outputDir 0={}", outputDir);
|
||||||
|
// Replace the placeholders in the outputDir string
|
||||||
|
outputDir = outputDir.replace("{outputFolder}", outputFolder);
|
||||||
|
outputDir = outputDir.replace("{folderName}", dir.toString());
|
||||||
|
logger.info("outputDir 1={}", outputDir);
|
||||||
|
outputDir = outputDir.replace("\\watchedFolders", "");
|
||||||
|
outputDir = outputDir.replace("//watchedFolders", "");
|
||||||
|
outputDir = outputDir.replace("\\\\watchedFolders", "");
|
||||||
|
outputDir = outputDir.replace("/watchedFolders", "");
|
||||||
|
|
||||||
|
Path outputPath;
|
||||||
|
logger.info("outputDir 2={}", outputDir);
|
||||||
|
if (Paths.get(outputDir).isAbsolute()) {
|
||||||
|
// If it's an absolute path, use it directly
|
||||||
|
outputPath = Paths.get(outputDir);
|
||||||
|
} else {
|
||||||
|
// If it's a relative path, make it relative to the current working directory
|
||||||
|
outputPath = Paths.get(".", outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("outputPath={}", outputPath);
|
||||||
|
|
||||||
|
if (!Files.exists(outputPath)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(outputPath);
|
||||||
|
logger.info("Created directory: {}", outputPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error creating directory: {}", outputPath, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("outputPath {}", outputPath);
|
||||||
|
logger.info("outputPath.resolve(outputFileName).toString() {}", outputPath.resolve(outputFileName).toString());
|
||||||
|
File newFile = new File(outputPath.resolve(outputFileName).toString());
|
||||||
|
OutputStream os = new FileOutputStream(newFile);
|
||||||
|
os.write(((ByteArrayResource)resource).getByteArray());
|
||||||
|
os.close();
|
||||||
|
logger.info("made {}", outputPath.resolve(outputFileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If successful, delete the original files
|
||||||
|
for (File file : filesToProcess) {
|
||||||
|
Files.deleteIfExists(processingDir.resolve(file.getName()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// If an error occurs, move the original files back
|
||||||
|
for (File file : filesToProcess) {
|
||||||
|
Files.move(processingDir.resolve(file.getName()), file.toPath());
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception {
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = mapper.readTree(jsonString);
|
||||||
|
|
||||||
|
JsonNode pipelineNode = jsonNode.get("pipeline");
|
||||||
|
logger.info("Running pipelineNode: {}", pipelineNode);
|
||||||
|
ByteArrayOutputStream logStream = new ByteArrayOutputStream();
|
||||||
|
PrintStream logPrintStream = new PrintStream(logStream);
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
|
||||||
|
for (JsonNode operationNode : pipelineNode) {
|
||||||
|
String operation = operationNode.get("operation").asText();
|
||||||
|
logger.info("Running operation: {}", operation);
|
||||||
|
JsonNode parametersNode = operationNode.get("parameters");
|
||||||
|
String inputFileExtension = "";
|
||||||
|
if (operationNode.has("inputFileType")) {
|
||||||
|
inputFileExtension = operationNode.get("inputFileType").asText();
|
||||||
|
} else {
|
||||||
|
inputFileExtension = ".pdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> newOutputFiles = new ArrayList<>();
|
||||||
|
boolean hasInputFileType = false;
|
||||||
|
|
||||||
|
for (Resource file : outputFiles) {
|
||||||
|
if (file.getFilename().endsWith(inputFileExtension)) {
|
||||||
|
hasInputFileType = true;
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("fileInput", file);
|
||||||
|
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> parameters = parametersNode.fields();
|
||||||
|
while (parameters.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> parameter = parameters.next();
|
||||||
|
body.add(parameter.getKey(), parameter.getValue().asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
|
||||||
|
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
String url = "http://localhost:8080/" + operation;
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
||||||
|
|
||||||
|
// If the operation is filter and the response body is null or empty, skip this file
|
||||||
|
if (operation.startsWith("filter-") && (response.getBody() == null || response.getBody().length == 0)) {
|
||||||
|
logger.info("Skipping file due to failing {}", operation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
|
logPrintStream.println("Error: " + response.getBody());
|
||||||
|
hasErrors = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Define filename
|
||||||
|
String filename;
|
||||||
|
if ("auto-rename".equals(operation)) {
|
||||||
|
// If the operation is "auto-rename", generate a new filename.
|
||||||
|
// This is a simple example of generating a filename using current timestamp.
|
||||||
|
// Modify as per your needs.
|
||||||
|
filename = "file_" + System.currentTimeMillis();
|
||||||
|
} else {
|
||||||
|
// Otherwise, keep the original filename.
|
||||||
|
filename = file.getFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the response body is a zip file
|
||||||
|
if (isZip(response.getBody())) {
|
||||||
|
// Unzip the file and add all the files to the new output files
|
||||||
|
newOutputFiles.addAll(unzip(response.getBody()));
|
||||||
|
} else {
|
||||||
|
Resource outputResource = new ByteArrayResource(response.getBody()) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
newOutputFiles.add(outputResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasInputFileType) {
|
||||||
|
logPrintStream.println(
|
||||||
|
"No files with extension " + inputFileExtension + " found for operation " + operation);
|
||||||
|
hasErrors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles = newOutputFiles;
|
||||||
|
}
|
||||||
|
logPrintStream.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (hasErrors) {
|
||||||
|
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
|
||||||
|
}
|
||||||
|
return outputFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> handleFiles(File[] files, String jsonString) throws Exception {
|
||||||
|
if(files == null || files.length == 0) {
|
||||||
|
logger.info("No files");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Handling files: {} files, with JSON string of length: {}", files.length, jsonString.length());
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = mapper.readTree(jsonString);
|
||||||
|
|
||||||
|
JsonNode pipelineNode = jsonNode.get("pipeline");
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
Path path = Paths.get(file.getAbsolutePath());
|
||||||
|
System.out.println("Reading file: " + path); // debug statement
|
||||||
|
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
Resource fileResource = new ByteArrayResource(Files.readAllBytes(path)) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return file.getName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
outputFiles.add(fileResource);
|
||||||
|
} else {
|
||||||
|
System.out.println("File not found: " + path); // debug statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("Files successfully loaded. Starting processing...");
|
||||||
|
return processFiles(outputFiles, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Resource> handleFiles(MultipartFile[] files, String jsonString) throws Exception {
|
||||||
|
if(files == null || files.length == 0) {
|
||||||
|
logger.info("No files");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
logger.info("Handling files: {} files, with JSON string of length: {}", files.length, jsonString.length());
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = mapper.readTree(jsonString);
|
||||||
|
|
||||||
|
JsonNode pipelineNode = jsonNode.get("pipeline");
|
||||||
|
|
||||||
|
boolean hasErrors = false;
|
||||||
|
List<Resource> outputFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
for (MultipartFile file : files) {
|
||||||
|
Resource fileResource = new ByteArrayResource(file.getBytes()) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return file.getOriginalFilename();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
outputFiles.add(fileResource);
|
||||||
|
}
|
||||||
|
logger.info("Files successfully loaded. Starting processing...");
|
||||||
|
return processFiles(outputFiles, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/handleData")
|
||||||
|
public ResponseEntity<byte[]> handleData(@RequestPart("fileInput") MultipartFile[] files,
|
||||||
|
@RequestParam("json") String jsonString) {
|
||||||
|
logger.info("Received POST request to /handleData with {} files", files.length);
|
||||||
|
try {
|
||||||
|
List<Resource> outputFiles = handleFiles(files, jsonString);
|
||||||
|
|
||||||
|
if (outputFiles != null && outputFiles.size() == 1) {
|
||||||
|
// If there is only one file, return it directly
|
||||||
|
Resource singleFile = outputFiles.get(0);
|
||||||
|
InputStream is = singleFile.getInputStream();
|
||||||
|
byte[] bytes = new byte[(int) singleFile.contentLength()];
|
||||||
|
is.read(bytes);
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
logger.info("Returning single file response...");
|
||||||
|
return WebResponseUtils.bytesToWebResponse(bytes, singleFile.getFilename(),
|
||||||
|
MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} else if (outputFiles == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a ByteArrayOutputStream to hold the zip
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ZipOutputStream zipOut = new ZipOutputStream(baos);
|
||||||
|
|
||||||
|
// Loop through each file and add it to the zip
|
||||||
|
for (Resource file : outputFiles) {
|
||||||
|
ZipEntry zipEntry = new ZipEntry(file.getFilename());
|
||||||
|
zipOut.putNextEntry(zipEntry);
|
||||||
|
|
||||||
|
// Read the file into a byte array
|
||||||
|
InputStream is = file.getInputStream();
|
||||||
|
byte[] bytes = new byte[(int) file.contentLength()];
|
||||||
|
is.read(bytes);
|
||||||
|
|
||||||
|
// Write the bytes of the file to the zip
|
||||||
|
zipOut.write(bytes, 0, bytes.length);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
zipOut.close();
|
||||||
|
|
||||||
|
logger.info("Returning zipped file response...");
|
||||||
|
return WebResponseUtils.boasToWebResponse(baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error handling data: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isZip(byte[] data) {
|
||||||
|
if (data == null || data.length < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the first four bytes of the data against the standard zip magic number
|
||||||
|
return data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Resource> unzip(byte[] data) throws IOException {
|
||||||
|
logger.info("Unzipping data of length: {}", data.length);
|
||||||
|
List<Resource> unzippedFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||||
|
ZipInputStream zis = new ZipInputStream(bais)) {
|
||||||
|
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int count;
|
||||||
|
|
||||||
|
while ((count = zis.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String filename = entry.getName();
|
||||||
|
Resource fileResource = new ByteArrayResource(baos.toByteArray()) {
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the unzipped file is a zip file, unzip it
|
||||||
|
if (isZip(baos.toByteArray())) {
|
||||||
|
logger.info("File {} is a zip file. Unzipping...", filename);
|
||||||
|
unzippedFiles.addAll(unzip(baos.toByteArray()));
|
||||||
|
} else {
|
||||||
|
unzippedFiles.add(fileResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
|
||||||
|
return unzippedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -51,8 +51,10 @@ import com.itextpdf.signatures.SignatureUtil;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class CertSignController {
|
public class CertSignController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
|
||||||
@@ -63,7 +65,7 @@ public class CertSignController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
@Operation(summary = "Sign PDF with a Digital Certificate",
|
@Operation(summary = "Sign PDF with a Digital Certificate",
|
||||||
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file.")
|
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
|
||||||
public ResponseEntity<byte[]> signPDF(
|
public ResponseEntity<byte[]> signPDF(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to be signed")
|
@Parameter(description = "The input PDF file to be signed")
|
||||||
|
|||||||
@@ -0,0 +1,779 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.cos.COSArray;
|
||||||
|
import org.apache.pdfbox.cos.COSBase;
|
||||||
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.cos.COSString;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
|
||||||
|
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
|
||||||
|
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
|
|
||||||
|
import com.itextpdf.kernel.pdf.PdfObject;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfOutline;
|
||||||
|
import com.itextpdf.forms.PdfAcroForm;
|
||||||
|
import com.itextpdf.forms.fields.PdfFormField;
|
||||||
|
import com.itextpdf.kernel.geom.Rectangle;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfArray;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfCatalog;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDictionary;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocumentInfo;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfEncryption;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfReader;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfResources;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfStream;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfString;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfName;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfViewerPreferences;
|
||||||
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
|
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
|
||||||
|
import com.itextpdf.kernel.pdf.annot.PdfFileAttachmentAnnotation;
|
||||||
|
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
|
||||||
|
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
|
||||||
|
import com.itextpdf.kernel.pdf.layer.PdfLayer;
|
||||||
|
import com.itextpdf.kernel.pdf.layer.PdfOCProperties;
|
||||||
|
import com.itextpdf.kernel.xmp.XMPException;
|
||||||
|
import com.itextpdf.kernel.xmp.XMPMeta;
|
||||||
|
import com.itextpdf.kernel.xmp.XMPMetaFactory;
|
||||||
|
import com.itextpdf.kernel.xmp.options.SerializeOptions;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
|
public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
static ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
||||||
|
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> getPdfInfo(
|
||||||
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
@Parameter(description = "The input PDF file to get info on", required = true) MultipartFile inputFile)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
try (
|
||||||
|
PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream());
|
||||||
|
PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream()))
|
||||||
|
) {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
ObjectNode jsonOutput = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
// Metadata using PDFBox
|
||||||
|
PDDocumentInformation info = pdfBoxDoc.getDocumentInformation();
|
||||||
|
ObjectNode metadata = objectMapper.createObjectNode();
|
||||||
|
ObjectNode basicInfo = objectMapper.createObjectNode();
|
||||||
|
ObjectNode docInfoNode = objectMapper.createObjectNode();
|
||||||
|
ObjectNode compliancy = objectMapper.createObjectNode();
|
||||||
|
ObjectNode encryption = objectMapper.createObjectNode();
|
||||||
|
ObjectNode other = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
|
||||||
|
metadata.put("Title", info.getTitle());
|
||||||
|
metadata.put("Author", info.getAuthor());
|
||||||
|
metadata.put("Subject", info.getSubject());
|
||||||
|
metadata.put("Keywords", info.getKeywords());
|
||||||
|
metadata.put("Producer", info.getProducer());
|
||||||
|
metadata.put("Creator", info.getCreator());
|
||||||
|
metadata.put("CreationDate", formatDate(info.getCreationDate()));
|
||||||
|
metadata.put("ModificationDate", formatDate(info.getModificationDate()));
|
||||||
|
jsonOutput.set("Metadata", metadata);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Total file size of the PDF
|
||||||
|
long fileSizeInBytes = inputFile.getSize();
|
||||||
|
basicInfo.put("FileSizeInBytes", fileSizeInBytes);
|
||||||
|
|
||||||
|
// Number of words, paragraphs, and images in the entire document
|
||||||
|
String fullText = new PDFTextStripper().getText(pdfBoxDoc);
|
||||||
|
String[] words = fullText.split("\\s+");
|
||||||
|
int wordCount = words.length;
|
||||||
|
int paragraphCount = fullText.split("\r\n|\r|\n").length;
|
||||||
|
basicInfo.put("WordCount", wordCount);
|
||||||
|
basicInfo.put("ParagraphCount", paragraphCount);
|
||||||
|
// Number of characters in the entire document (including spaces and special characters)
|
||||||
|
int charCount = fullText.length();
|
||||||
|
basicInfo.put("CharacterCount", charCount);
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize the flags and types
|
||||||
|
boolean hasCompression = false;
|
||||||
|
String compressionType = "None";
|
||||||
|
|
||||||
|
// Check for object streams
|
||||||
|
for (int i = 1; i <= itextDoc.getNumberOfPdfObjects(); i++) {
|
||||||
|
PdfObject obj = itextDoc.getPdfObject(i);
|
||||||
|
if (obj != null && obj.isStream() && ((PdfStream) obj).get(PdfName.Type) == PdfName.ObjStm) {
|
||||||
|
hasCompression = true;
|
||||||
|
compressionType = "Object Streams";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not compressed using object streams, check for compressed Xref tables
|
||||||
|
if (!hasCompression && itextDoc.getReader().hasRebuiltXref()) {
|
||||||
|
hasCompression = true;
|
||||||
|
compressionType = "Compressed Xref or Rebuilt Xref";
|
||||||
|
}
|
||||||
|
basicInfo.put("Compression", hasCompression);
|
||||||
|
if(hasCompression)
|
||||||
|
basicInfo.put("CompressionType", compressionType);
|
||||||
|
|
||||||
|
String language = pdfBoxDoc.getDocumentCatalog().getLanguage();
|
||||||
|
basicInfo.put("Language", language);
|
||||||
|
basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages());
|
||||||
|
|
||||||
|
|
||||||
|
// Page Mode using iText7
|
||||||
|
PdfCatalog catalog = itextDoc.getCatalog();
|
||||||
|
PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode);
|
||||||
|
|
||||||
|
// Document Information using PDFBox
|
||||||
|
docInfoNode.put("PDF version", pdfBoxDoc.getVersion());
|
||||||
|
docInfoNode.put("Trapped", info.getTrapped());
|
||||||
|
docInfoNode.put("Page Mode", getPageModeDescription(pageMode));;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false);
|
||||||
|
ObjectNode formFieldsNode = objectMapper.createObjectNode();
|
||||||
|
if (acroForm != null) {
|
||||||
|
for (Map.Entry<String, PdfFormField> entry : acroForm.getFormFields().entrySet()) {
|
||||||
|
formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonOutput.set("FormFields", formFieldsNode);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//embeed files TODO size
|
||||||
|
ArrayNode embeddedFilesArray = objectMapper.createArrayNode();
|
||||||
|
if(itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) != null)
|
||||||
|
{
|
||||||
|
PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names)
|
||||||
|
.getAsDictionary(PdfName.EmbeddedFiles);
|
||||||
|
if (embeddedFiles != null) {
|
||||||
|
|
||||||
|
PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names);
|
||||||
|
if(namesArray != null) {
|
||||||
|
for (int i = 0; i < namesArray.size(); i += 2) {
|
||||||
|
ObjectNode embeddedFileNode = objectMapper.createObjectNode();
|
||||||
|
embeddedFileNode.put("Name", namesArray.getAsString(i).toString());
|
||||||
|
// Add other details if required
|
||||||
|
embeddedFilesArray.add(embeddedFileNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other.set("EmbeddedFiles", embeddedFilesArray);
|
||||||
|
|
||||||
|
//attachments TODO size
|
||||||
|
ArrayNode attachmentsArray = objectMapper.createArrayNode();
|
||||||
|
for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) {
|
||||||
|
for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) {
|
||||||
|
if (annotation instanceof PdfFileAttachmentAnnotation) {
|
||||||
|
ObjectNode attachmentNode = objectMapper.createObjectNode();
|
||||||
|
attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString());
|
||||||
|
attachmentNode.put("Description", annotation.getContents().getValue());
|
||||||
|
attachmentsArray.add(attachmentNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other.set("Attachments", attachmentsArray);
|
||||||
|
|
||||||
|
//Javascript
|
||||||
|
PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
|
||||||
|
ArrayNode javascriptArray = objectMapper.createArrayNode();
|
||||||
|
if (namesDict != null) {
|
||||||
|
PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript);
|
||||||
|
if (javascriptDict != null) {
|
||||||
|
|
||||||
|
PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names);
|
||||||
|
for (int i = 0; i < namesArray.size(); i += 2) {
|
||||||
|
ObjectNode jsNode = objectMapper.createObjectNode();
|
||||||
|
if(namesArray.getAsString(i) != null)
|
||||||
|
jsNode.put("JS Name", namesArray.getAsString(i).toString());
|
||||||
|
|
||||||
|
// Here we check for a PdfStream object and retrieve the JS code from it
|
||||||
|
PdfObject jsCode = namesArray.get(i+1);
|
||||||
|
if (jsCode instanceof PdfStream) {
|
||||||
|
byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes();
|
||||||
|
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
||||||
|
jsNode.put("JS Script Length", jsCodeStr.length());
|
||||||
|
} else if (jsCode instanceof PdfDictionary) {
|
||||||
|
// If the JS code is in a dictionary, you'll need to know the key to use.
|
||||||
|
// Assuming the key is PdfName.JS:
|
||||||
|
PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS);
|
||||||
|
if (jsCodeStream != null) {
|
||||||
|
byte[] jsCodeBytes = jsCodeStream.getBytes();
|
||||||
|
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
||||||
|
jsNode.put("JS Script Character Length", jsCodeStr.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
javascriptArray.add(jsNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other.set("JavaScript", javascriptArray);
|
||||||
|
|
||||||
|
//TODO size
|
||||||
|
PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false);
|
||||||
|
ArrayNode layersArray = objectMapper.createArrayNode();
|
||||||
|
if (ocProperties != null) {
|
||||||
|
|
||||||
|
for (PdfLayer layer : ocProperties.getLayers()) {
|
||||||
|
ObjectNode layerNode = objectMapper.createObjectNode();
|
||||||
|
layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString());
|
||||||
|
layersArray.add(layerNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
other.set("Layers", layersArray);
|
||||||
|
|
||||||
|
//TODO Security
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Digital Signatures using iText7 TODO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot();
|
||||||
|
ArrayNode structureTreeArray;
|
||||||
|
try {
|
||||||
|
if(structureTreeRoot != null) {
|
||||||
|
structureTreeArray = exploreStructureTree(structureTreeRoot.getKids());
|
||||||
|
other.set("StructureTree", structureTreeArray);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean isPdfACompliant = checkOutputIntent(itextDoc, "PDF/A");
|
||||||
|
boolean isPdfXCompliant = checkOutputIntent(itextDoc, "PDF/X");
|
||||||
|
boolean isPdfECompliant = checkForStandard(itextDoc, "PDF/E");
|
||||||
|
boolean isPdfVTCompliant = checkForStandard(itextDoc, "PDF/VT");
|
||||||
|
boolean isPdfUACompliant = checkForStandard(itextDoc, "PDF/UA");
|
||||||
|
boolean isPdfBCompliant = checkForStandard(itextDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard.
|
||||||
|
boolean isPdfSECCompliant = checkForStandard(itextDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021.
|
||||||
|
|
||||||
|
compliancy.put("IsPDF/ACompliant", isPdfACompliant);
|
||||||
|
compliancy.put("IsPDF/XCompliant", isPdfXCompliant);
|
||||||
|
compliancy.put("IsPDF/ECompliant", isPdfECompliant);
|
||||||
|
compliancy.put("IsPDF/VTCompliant", isPdfVTCompliant);
|
||||||
|
compliancy.put("IsPDF/UACompliant", isPdfUACompliant);
|
||||||
|
compliancy.put("IsPDF/BCompliant", isPdfBCompliant);
|
||||||
|
compliancy.put("IsPDF/SECCompliant", isPdfSECCompliant);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ArrayNode bookmarksArray = objectMapper.createArrayNode();
|
||||||
|
PdfOutline root = itextDoc.getOutlines(false);
|
||||||
|
if (root != null) {
|
||||||
|
for (PdfOutline child : root.getAllChildren()) {
|
||||||
|
addOutlinesToArray(child, bookmarksArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other.set("Bookmarks/Outline/TOC", bookmarksArray);
|
||||||
|
|
||||||
|
byte[] xmpBytes = itextDoc.getXmpMetadata();
|
||||||
|
String xmpString = null;
|
||||||
|
if (xmpBytes != null) {
|
||||||
|
try {
|
||||||
|
XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes);
|
||||||
|
xmpString = new String(XMPMetaFactory.serializeToBuffer(xmpMeta, new SerializeOptions()));
|
||||||
|
} catch (XMPException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other.put("XMPMetadata", xmpString);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (pdfBoxDoc.isEncrypted()) {
|
||||||
|
encryption.put("IsEncrypted", true);
|
||||||
|
|
||||||
|
// Retrieve encryption details using getEncryption()
|
||||||
|
PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();
|
||||||
|
encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter());
|
||||||
|
encryption.put("KeyLength", pdfEncryption.getLength());
|
||||||
|
encryption.put("Permissions", pdfBoxDoc.getCurrentAccessPermission().toString());
|
||||||
|
|
||||||
|
// Add other encryption-related properties as needed
|
||||||
|
} else {
|
||||||
|
encryption.put("IsEncrypted", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ObjectNode pageInfoParent = objectMapper.createObjectNode();
|
||||||
|
for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) {
|
||||||
|
ObjectNode pageInfo = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
// Page-level Information
|
||||||
|
Rectangle pageSize = itextDoc.getPage(pageNum).getPageSize();
|
||||||
|
pageInfo.put("Width", pageSize.getWidth());
|
||||||
|
pageInfo.put("Height", pageSize.getHeight());
|
||||||
|
pageInfo.put("Rotation", itextDoc.getPage(pageNum).getRotation());
|
||||||
|
pageInfo.put("Page Orientation", getPageOrientation(pageSize.getWidth(),pageSize.getHeight()));
|
||||||
|
pageInfo.put("Standard Size", getPageSize(pageSize.getWidth(),pageSize.getHeight()));
|
||||||
|
|
||||||
|
// Boxes
|
||||||
|
pageInfo.put("MediaBox", itextDoc.getPage(pageNum).getMediaBox().toString());
|
||||||
|
pageInfo.put("CropBox", itextDoc.getPage(pageNum).getCropBox().toString());
|
||||||
|
pageInfo.put("BleedBox", itextDoc.getPage(pageNum).getBleedBox().toString());
|
||||||
|
pageInfo.put("TrimBox", itextDoc.getPage(pageNum).getTrimBox().toString());
|
||||||
|
pageInfo.put("ArtBox", itextDoc.getPage(pageNum).getArtBox().toString());
|
||||||
|
|
||||||
|
// Content Extraction
|
||||||
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
|
textStripper.setStartPage(pageNum -1);
|
||||||
|
textStripper.setEndPage(pageNum - 1);
|
||||||
|
String pageText = textStripper.getText(pdfBoxDoc);
|
||||||
|
|
||||||
|
pageInfo.put("Text Characters Count", pageText.length()); //
|
||||||
|
|
||||||
|
// Annotations
|
||||||
|
List<PdfAnnotation> annotations = itextDoc.getPage(pageNum).getAnnotations();
|
||||||
|
|
||||||
|
int subtypeCount = 0;
|
||||||
|
int contentsCount = 0;
|
||||||
|
|
||||||
|
for (PdfAnnotation annotation : annotations) {
|
||||||
|
if(annotation.getSubtype() != null) {
|
||||||
|
subtypeCount++; // Increase subtype count
|
||||||
|
}
|
||||||
|
if(annotation.getContents() != null) {
|
||||||
|
contentsCount++; // Increase contents count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode annotationsObject = objectMapper.createObjectNode();
|
||||||
|
annotationsObject.put("AnnotationsCount", annotations.size());
|
||||||
|
annotationsObject.put("SubtypeCount", subtypeCount);
|
||||||
|
annotationsObject.put("ContentsCount", contentsCount);
|
||||||
|
pageInfo.set("Annotations", annotationsObject);
|
||||||
|
|
||||||
|
// Images (simplified)
|
||||||
|
// This part is non-trivial as images can be embedded in multiple ways in a PDF.
|
||||||
|
// Here is a basic structure to recognize image XObjects on a page.
|
||||||
|
ArrayNode imagesArray = objectMapper.createArrayNode();
|
||||||
|
PdfResources resources = itextDoc.getPage(pageNum).getResources();
|
||||||
|
for (PdfName name : resources.getResourceNames()) {
|
||||||
|
PdfObject obj = resources.getResource(name);
|
||||||
|
if (obj instanceof PdfStream) {
|
||||||
|
PdfStream stream = (PdfStream) obj;
|
||||||
|
if (PdfName.Image.equals(stream.getAsName(PdfName.Subtype))) {
|
||||||
|
ObjectNode imageNode = objectMapper.createObjectNode();
|
||||||
|
imageNode.put("Width", stream.getAsNumber(PdfName.Width).intValue());
|
||||||
|
imageNode.put("Height", stream.getAsNumber(PdfName.Height).intValue());
|
||||||
|
PdfObject colorSpace = stream.get(PdfName.ColorSpace);
|
||||||
|
if (colorSpace != null) {
|
||||||
|
imageNode.put("ColorSpace", colorSpace.toString());
|
||||||
|
}
|
||||||
|
imagesArray.add(imageNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo.set("Images", imagesArray);
|
||||||
|
|
||||||
|
|
||||||
|
// Links
|
||||||
|
ArrayNode linksArray = objectMapper.createArrayNode();
|
||||||
|
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
||||||
|
|
||||||
|
for (PdfAnnotation annotation : annotations) {
|
||||||
|
if (annotation instanceof PdfLinkAnnotation) {
|
||||||
|
PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation;
|
||||||
|
if(linkAnnotation != null && linkAnnotation.getAction() != null) {
|
||||||
|
String uri = linkAnnotation.getAction().toString();
|
||||||
|
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add unique URIs to linksArray
|
||||||
|
for (String uri : uniqueURIs) {
|
||||||
|
ObjectNode linkNode = objectMapper.createObjectNode();
|
||||||
|
linkNode.put("URI", uri);
|
||||||
|
linksArray.add(linkNode);
|
||||||
|
}
|
||||||
|
pageInfo.set("Links", linksArray);
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
ArrayNode fontsArray = objectMapper.createArrayNode();
|
||||||
|
PdfDictionary fontDicts = resources.getResource(PdfName.Font);
|
||||||
|
Set<String> uniqueSubtypes = new HashSet<>(); // To store unique subtypes
|
||||||
|
|
||||||
|
// Map to store unique fonts and their counts
|
||||||
|
Map<String, ObjectNode> uniqueFontsMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (fontDicts != null) {
|
||||||
|
for (PdfName key : fontDicts.keySet()) {
|
||||||
|
ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font
|
||||||
|
PdfDictionary font = fontDicts.getAsDictionary(key);
|
||||||
|
|
||||||
|
boolean isEmbedded = font.containsKey(PdfName.FontFile) ||
|
||||||
|
font.containsKey(PdfName.FontFile2) ||
|
||||||
|
font.containsKey(PdfName.FontFile3);
|
||||||
|
fontNode.put("IsEmbedded", isEmbedded);
|
||||||
|
|
||||||
|
if (font.containsKey(PdfName.Encoding)) {
|
||||||
|
String encoding = font.getAsName(PdfName.Encoding).toString();
|
||||||
|
fontNode.put("Encoding", encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font.getAsString(PdfName.BaseFont) != null) {
|
||||||
|
fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
String subtype = null;
|
||||||
|
if (font.containsKey(PdfName.Subtype)) {
|
||||||
|
subtype = font.getAsName(PdfName.Subtype).toString();
|
||||||
|
uniqueSubtypes.add(subtype); // Add to set to ensure uniqueness
|
||||||
|
}
|
||||||
|
fontNode.put("Subtype", subtype);
|
||||||
|
|
||||||
|
PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor);
|
||||||
|
if (fontDescriptor != null) {
|
||||||
|
if (fontDescriptor.containsKey(PdfName.ItalicAngle)) {
|
||||||
|
fontNode.put("ItalicAngle", fontDescriptor.getAsNumber(PdfName.ItalicAngle).floatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontDescriptor.containsKey(PdfName.Flags)) {
|
||||||
|
int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue();
|
||||||
|
fontNode.put("IsItalic", (flags & 64) != 0);
|
||||||
|
fontNode.put("IsBold", (flags & 1 << 16) != 0);
|
||||||
|
fontNode.put("IsFixedPitch", (flags & 1) != 0);
|
||||||
|
fontNode.put("IsSerif", (flags & 2) != 0);
|
||||||
|
fontNode.put("IsSymbolic", (flags & 4) != 0);
|
||||||
|
fontNode.put("IsScript", (flags & 8) != 0);
|
||||||
|
fontNode.put("IsNonsymbolic", (flags & 16) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontDescriptor.containsKey(PdfName.FontFamily)) {
|
||||||
|
String fontFamily = fontDescriptor.getAsString(PdfName.FontFamily).toString();
|
||||||
|
fontNode.put("FontFamily", fontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontDescriptor.containsKey(PdfName.FontStretch)) {
|
||||||
|
String fontStretch = fontDescriptor.getAsName(PdfName.FontStretch).toString();
|
||||||
|
fontNode.put("FontStretch", fontStretch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontDescriptor.containsKey(PdfName.FontBBox)) {
|
||||||
|
PdfArray bbox = fontDescriptor.getAsArray(PdfName.FontBBox);
|
||||||
|
fontNode.put("FontBoundingBox", bbox.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontDescriptor.containsKey(PdfName.FontWeight)) {
|
||||||
|
float fontWeight = fontDescriptor.getAsNumber(PdfName.FontWeight).floatValue();
|
||||||
|
fontNode.put("FontWeight", fontWeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font.containsKey(PdfName.ToUnicode)) {
|
||||||
|
fontNode.put("HasToUnicodeMap", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontNode.size() > 0) {
|
||||||
|
// Create a unique key for this font node based on its attributes
|
||||||
|
String uniqueKey = fontNode.toString();
|
||||||
|
|
||||||
|
// Increment count if this font exists, or initialize it if new
|
||||||
|
if (uniqueFontsMap.containsKey(uniqueKey)) {
|
||||||
|
ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey);
|
||||||
|
int count = existingFontNode.get("Count").asInt() + 1;
|
||||||
|
existingFontNode.put("Count", count);
|
||||||
|
} else {
|
||||||
|
fontNode.put("Count", 1);
|
||||||
|
uniqueFontsMap.put(uniqueKey, fontNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add unique font entries to fontsArray
|
||||||
|
for (ObjectNode uniqueFontNode : uniqueFontsMap.values()) {
|
||||||
|
fontsArray.add(uniqueFontNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageInfo.set("Fonts", fontsArray);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Access resources dictionary
|
||||||
|
PdfDictionary resourcesDict = itextDoc.getPage(pageNum).getResources().getPdfObject();
|
||||||
|
|
||||||
|
// Color Spaces & ICC Profiles
|
||||||
|
ArrayNode colorSpacesArray = objectMapper.createArrayNode();
|
||||||
|
PdfDictionary colorSpaces = resourcesDict.getAsDictionary(PdfName.ColorSpace);
|
||||||
|
if (colorSpaces != null) {
|
||||||
|
for (PdfName name : colorSpaces.keySet()) {
|
||||||
|
PdfObject colorSpaceObject = colorSpaces.get(name);
|
||||||
|
if (colorSpaceObject instanceof PdfArray) {
|
||||||
|
PdfArray colorSpaceArray = (PdfArray) colorSpaceObject;
|
||||||
|
if (colorSpaceArray.size() > 1 && colorSpaceArray.get(0) instanceof PdfName && PdfName.ICCBased.equals(colorSpaceArray.get(0))) {
|
||||||
|
ObjectNode iccProfileNode = objectMapper.createObjectNode();
|
||||||
|
PdfStream iccStream = (PdfStream) colorSpaceArray.get(1);
|
||||||
|
byte[] iccData = iccStream.getBytes();
|
||||||
|
// TODO: Further decode and analyze the ICC data if needed
|
||||||
|
iccProfileNode.put("ICC Profile Length", iccData.length);
|
||||||
|
colorSpacesArray.add(iccProfileNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray);
|
||||||
|
|
||||||
|
// Other XObjects
|
||||||
|
Map<String, Integer> xObjectCountMap = new HashMap<>(); // To store the count for each type
|
||||||
|
PdfDictionary xObjects = resourcesDict.getAsDictionary(PdfName.XObject);
|
||||||
|
if (xObjects != null) {
|
||||||
|
for (PdfName name : xObjects.keySet()) {
|
||||||
|
PdfStream xObjectStream = xObjects.getAsStream(name);
|
||||||
|
String xObjectType = xObjectStream.getAsName(PdfName.Subtype).toString();
|
||||||
|
|
||||||
|
// Increment the count for this type in the map
|
||||||
|
xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the count map to pageInfo (or wherever you want to store it)
|
||||||
|
ObjectNode xObjectCountNode = objectMapper.createObjectNode();
|
||||||
|
for (Map.Entry<String, Integer> entry : xObjectCountMap.entrySet()) {
|
||||||
|
xObjectCountNode.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
pageInfo.set("XObjectCounts", xObjectCountNode);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ArrayNode multimediaArray = objectMapper.createArrayNode();
|
||||||
|
for (PdfAnnotation annotation : annotations) {
|
||||||
|
if (PdfName.RichMedia.equals(annotation.getSubtype())) {
|
||||||
|
ObjectNode multimediaNode = objectMapper.createObjectNode();
|
||||||
|
// Extract details from the dictionary as needed
|
||||||
|
multimediaArray.add(multimediaNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo.set("Multimedia", multimediaArray);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pageInfoParent.set("Page " + pageNum, pageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
jsonOutput.set("BasicInfo", basicInfo);
|
||||||
|
jsonOutput.set("DocumentInfo", docInfoNode);
|
||||||
|
jsonOutput.set("Compliancy", compliancy);
|
||||||
|
jsonOutput.set("Encryption", encryption);
|
||||||
|
jsonOutput.set("Other", other);
|
||||||
|
jsonOutput.set("PerPageInfo", pageInfoParent);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Save JSON to file
|
||||||
|
String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(jsonString.getBytes(StandardCharsets.UTF_8), "response.json", MediaType.APPLICATION_JSON);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addOutlinesToArray(PdfOutline outline, ArrayNode arrayNode) {
|
||||||
|
if (outline == null) return;
|
||||||
|
ObjectNode outlineNode = objectMapper.createObjectNode();
|
||||||
|
outlineNode.put("Title", outline.getTitle());
|
||||||
|
// You can add other properties if needed
|
||||||
|
arrayNode.add(outlineNode);
|
||||||
|
|
||||||
|
for (PdfOutline child : outline.getAllChildren()) {
|
||||||
|
addOutlinesToArray(child, arrayNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String getPageOrientation(double width, double height) {
|
||||||
|
if (width > height) {
|
||||||
|
return "Landscape";
|
||||||
|
} else if (height > width) {
|
||||||
|
return "Portrait";
|
||||||
|
} else {
|
||||||
|
return "Square";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String getPageSize(double width, double height) {
|
||||||
|
// Common aspect ratios used for standard paper sizes
|
||||||
|
double[] aspectRatios = {4.0 / 3.0, 3.0 / 2.0, Math.sqrt(2.0), 16.0 / 9.0};
|
||||||
|
|
||||||
|
// Check if the page matches any common aspect ratio
|
||||||
|
for (double aspectRatio : aspectRatios) {
|
||||||
|
if (isCloseToAspectRatio(width, height, aspectRatio)) {
|
||||||
|
return "Standard";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a standard aspect ratio, consider it as a custom size
|
||||||
|
return "Custom";
|
||||||
|
}
|
||||||
|
private boolean isCloseToAspectRatio(double width, double height, double aspectRatio) {
|
||||||
|
// Calculate the aspect ratio of the page
|
||||||
|
double pageAspectRatio = width / height;
|
||||||
|
|
||||||
|
// Compare the page aspect ratio with the common aspect ratio within a threshold
|
||||||
|
return Math.abs(pageAspectRatio - aspectRatio) <= 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkForStandard(PdfDocument document, String standardKeyword) {
|
||||||
|
// Check Output Intents
|
||||||
|
boolean foundInOutputIntents = checkOutputIntent(document, standardKeyword);
|
||||||
|
if (foundInOutputIntents) return true;
|
||||||
|
|
||||||
|
// Check XMP Metadata (rudimentary)
|
||||||
|
try {
|
||||||
|
byte[] metadataBytes = document.getXmpMetadata();
|
||||||
|
if (metadataBytes != null) {
|
||||||
|
XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(metadataBytes);
|
||||||
|
String xmpString = xmpMeta.dumpObject();
|
||||||
|
if (xmpString.contains(standardKeyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (XMPException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean checkOutputIntent(PdfDocument document, String standard) {
|
||||||
|
PdfArray outputIntents = document.getCatalog().getPdfObject().getAsArray(PdfName.OutputIntents);
|
||||||
|
if (outputIntents != null && !outputIntents.isEmpty()) {
|
||||||
|
for (int i = 0; i < outputIntents.size(); i++) {
|
||||||
|
PdfDictionary outputIntentDict = outputIntents.getAsDictionary(i);
|
||||||
|
if (outputIntentDict != null) {
|
||||||
|
PdfString s = outputIntentDict.getAsString(PdfName.S);
|
||||||
|
if (s != null && s.toString().contains(standard)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayNode exploreStructureTree(List<Object> nodes) {
|
||||||
|
ArrayNode elementsArray = objectMapper.createArrayNode();
|
||||||
|
if (nodes != null) {
|
||||||
|
for (Object obj : nodes) {
|
||||||
|
if (obj instanceof PDStructureNode) {
|
||||||
|
PDStructureNode node = (PDStructureNode) obj;
|
||||||
|
ObjectNode elementNode = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
if (node instanceof PDStructureElement) {
|
||||||
|
PDStructureElement structureElement = (PDStructureElement) node;
|
||||||
|
elementNode.put("Type", structureElement.getStructureType());
|
||||||
|
elementNode.put("Content", getContent(structureElement));
|
||||||
|
|
||||||
|
// Recursively explore child elements
|
||||||
|
ArrayNode childElements = exploreStructureTree(structureElement.getKids());
|
||||||
|
if (childElements.size() > 0) {
|
||||||
|
elementNode.set("Children", childElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elementsArray.add(elementNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elementsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getContent(PDStructureElement structureElement) {
|
||||||
|
StringBuilder contentBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
for (Object item : structureElement.getKids()) {
|
||||||
|
if (item instanceof COSString) {
|
||||||
|
COSString cosString = (COSString) item;
|
||||||
|
contentBuilder.append(cosString.getString());
|
||||||
|
} else if (item instanceof PDStructureElement) {
|
||||||
|
// For simplicity, we're handling only COSString and PDStructureElement here
|
||||||
|
// but a more comprehensive method would handle other types too
|
||||||
|
contentBuilder.append(getContent((PDStructureElement) item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String formatDate(Calendar calendar) {
|
||||||
|
if (calendar != null) {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
return sdf.format(calendar.getTime());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPageModeDescription(PdfName pageMode) {
|
||||||
|
return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,8 +17,10 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class PasswordController {
|
public class PasswordController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
||||||
@@ -27,7 +29,7 @@ public class PasswordController {
|
|||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove password from a PDF file",
|
summary = "Remove password from a PDF file",
|
||||||
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password."
|
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> removePassword(
|
public ResponseEntity<byte[]> removePassword(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@@ -44,43 +46,43 @@ public class PasswordController {
|
|||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add password to a PDF file",
|
summary = "Add password to a PDF file",
|
||||||
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file."
|
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> addPassword(
|
public ResponseEntity<byte[]> addPassword(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestPart(required = true, value = "fileInput")
|
||||||
@Parameter(description = "The input PDF file to which the password should be added", required = true)
|
@Parameter(description = "The input PDF file to which the password should be added", required = true)
|
||||||
MultipartFile fileInput,
|
MultipartFile fileInput,
|
||||||
@RequestParam(defaultValue = "", name = "ownerPassword")
|
@RequestParam(value = "", name = "ownerPassword")
|
||||||
@Parameter(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)")
|
@Parameter(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)")
|
||||||
String ownerPassword,
|
String ownerPassword,
|
||||||
@RequestParam(defaultValue = "", name = "password")
|
@RequestParam( name = "password", required = false)
|
||||||
@Parameter(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)")
|
@Parameter(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)")
|
||||||
String password,
|
String password,
|
||||||
@RequestParam(defaultValue = "128", name = "keyLength")
|
@RequestParam( name = "keyLength", required = false)
|
||||||
@Parameter(description = "The length of the encryption key", schema = @Schema(allowableValues = {"40", "128", "256"}))
|
@Parameter(description = "The length of the encryption key", schema = @Schema(allowableValues = {"40", "128", "256"}))
|
||||||
int keyLength,
|
int keyLength,
|
||||||
@RequestParam(defaultValue = "false", name = "canAssembleDocument")
|
@RequestParam( name = "canAssembleDocument", required = false)
|
||||||
@Parameter(description = "Whether the document assembly is allowed", example = "false")
|
@Parameter(description = "Whether the document assembly is allowed", example = "false")
|
||||||
boolean canAssembleDocument,
|
boolean canAssembleDocument,
|
||||||
@RequestParam(defaultValue = "false", name = "canExtractContent")
|
@RequestParam( name = "canExtractContent", required = false)
|
||||||
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
|
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
|
||||||
boolean canExtractContent,
|
boolean canExtractContent,
|
||||||
@RequestParam(defaultValue = "false", name = "canExtractForAccessibility")
|
@RequestParam( name = "canExtractForAccessibility", required = false)
|
||||||
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
|
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
|
||||||
boolean canExtractForAccessibility,
|
boolean canExtractForAccessibility,
|
||||||
@RequestParam(defaultValue = "false", name = "canFillInForm")
|
@RequestParam( name = "canFillInForm", required = false)
|
||||||
@Parameter(description = "Whether form filling is allowed", example = "false")
|
@Parameter(description = "Whether form filling is allowed", example = "false")
|
||||||
boolean canFillInForm,
|
boolean canFillInForm,
|
||||||
@RequestParam(defaultValue = "false", name = "canModify")
|
@RequestParam( name = "canModify", required = false)
|
||||||
@Parameter(description = "Whether the document modification is allowed", example = "false")
|
@Parameter(description = "Whether the document modification is allowed", example = "false")
|
||||||
boolean canModify,
|
boolean canModify,
|
||||||
@RequestParam(defaultValue = "false", name = "canModifyAnnotations")
|
@RequestParam( name = "canModifyAnnotations", required = false)
|
||||||
@Parameter(description = "Whether modification of annotations is allowed", example = "false")
|
@Parameter(description = "Whether modification of annotations is allowed", example = "false")
|
||||||
boolean canModifyAnnotations,
|
boolean canModifyAnnotations,
|
||||||
@RequestParam(defaultValue = "false", name = "canPrint")
|
@RequestParam(name = "canPrint", required = false)
|
||||||
@Parameter(description = "Whether printing of the document is allowed", example = "false")
|
@Parameter(description = "Whether printing of the document is allowed", example = "false")
|
||||||
boolean canPrint,
|
boolean canPrint,
|
||||||
@RequestParam(defaultValue = "false", name = "canPrintFaithful")
|
@RequestParam( name = "canPrintFaithful", required = false)
|
||||||
@Parameter(description = "Whether faithful printing is allowed", example = "false")
|
@Parameter(description = "Whether faithful printing is allowed", example = "false")
|
||||||
boolean canPrintFaithful
|
boolean canPrintFaithful
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.*;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDTerminalField;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.cos.COSString;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class SanitizeController {
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||||
|
@Operation(summary = "Sanitize a PDF file",
|
||||||
|
description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> sanitizePDF(
|
||||||
|
@RequestPart(required = true, value = "fileInput")
|
||||||
|
@Parameter(description = "The input PDF file to be sanitized")
|
||||||
|
MultipartFile inputFile,
|
||||||
|
@RequestParam(name = "removeJavaScript", required = false, defaultValue = "true")
|
||||||
|
@Parameter(description = "Remove JavaScript actions from the PDF if set to true")
|
||||||
|
Boolean removeJavaScript,
|
||||||
|
@RequestParam(name = "removeEmbeddedFiles", required = false, defaultValue = "true")
|
||||||
|
@Parameter(description = "Remove embedded files from the PDF if set to true")
|
||||||
|
Boolean removeEmbeddedFiles,
|
||||||
|
@RequestParam(name = "removeMetadata", required = false, defaultValue = "true")
|
||||||
|
@Parameter(description = "Remove metadata from the PDF if set to true")
|
||||||
|
Boolean removeMetadata,
|
||||||
|
@RequestParam(name = "removeLinks", required = false, defaultValue = "true")
|
||||||
|
@Parameter(description = "Remove links from the PDF if set to true")
|
||||||
|
Boolean removeLinks,
|
||||||
|
@RequestParam(name = "removeFonts", required = false, defaultValue = "true")
|
||||||
|
@Parameter(description = "Remove fonts from the PDF if set to true")
|
||||||
|
Boolean removeFonts) throws IOException {
|
||||||
|
|
||||||
|
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
||||||
|
if (removeJavaScript) {
|
||||||
|
sanitizeJavaScript(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeEmbeddedFiles) {
|
||||||
|
sanitizeEmbeddedFiles(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeMetadata) {
|
||||||
|
sanitizeMetadata(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeLinks) {
|
||||||
|
sanitizeLinks(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeFonts) {
|
||||||
|
sanitizeFonts(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_sanitized.pdf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
||||||
|
// Get the root dictionary (catalog) of the PDF
|
||||||
|
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||||
|
|
||||||
|
// Get the Names dictionary
|
||||||
|
COSDictionary namesDict = (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
||||||
|
|
||||||
|
if (namesDict != null) {
|
||||||
|
// Get the JavaScript dictionary
|
||||||
|
COSDictionary javaScriptDict = (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
||||||
|
|
||||||
|
if (javaScriptDict != null) {
|
||||||
|
// Remove the JavaScript dictionary
|
||||||
|
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PDPage page : document.getPages()) {
|
||||||
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
|
if (annotation instanceof PDAnnotationWidget) {
|
||||||
|
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
||||||
|
PDAction action = widget.getAction();
|
||||||
|
if (action instanceof PDActionJavaScript) {
|
||||||
|
widget.setAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||||
|
if (acroForm != null) {
|
||||||
|
for (PDField field : acroForm.getFields()) {
|
||||||
|
PDFormFieldAdditionalActions actions = field.getActions();
|
||||||
|
if(actions != null) {
|
||||||
|
if (actions.getC() instanceof PDActionJavaScript) {
|
||||||
|
actions.setC(null);
|
||||||
|
}
|
||||||
|
if (actions.getF() instanceof PDActionJavaScript) {
|
||||||
|
actions.setF(null);
|
||||||
|
}
|
||||||
|
if (actions.getK() instanceof PDActionJavaScript) {
|
||||||
|
actions.setK(null);
|
||||||
|
}
|
||||||
|
if (actions.getV() instanceof PDActionJavaScript) {
|
||||||
|
actions.setV(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void sanitizeEmbeddedFiles(PDDocument document) {
|
||||||
|
PDPageTree allPages = document.getPages();
|
||||||
|
|
||||||
|
for (PDPage page : allPages) {
|
||||||
|
PDResources res = page.getResources();
|
||||||
|
|
||||||
|
// Remove embedded files from the PDF
|
||||||
|
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void sanitizeMetadata(PDDocument document) {
|
||||||
|
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
||||||
|
if (metadata != null) {
|
||||||
|
document.getDocumentCatalog().setMetadata(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void sanitizeLinks(PDDocument document) throws IOException {
|
||||||
|
for (PDPage page : document.getPages()) {
|
||||||
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
|
if (annotation instanceof PDAnnotationLink) {
|
||||||
|
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
||||||
|
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
|
||||||
|
((PDAnnotationLink) annotation).setAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sanitizeFonts(PDDocument document) {
|
||||||
|
for (PDPage page : document.getPages()) {
|
||||||
|
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +1,28 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -19,79 +32,167 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class WatermarkController {
|
public class WatermarkController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||||
@Operation(summary = "Add watermark to a PDF file",
|
@Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
||||||
description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer.")
|
public ResponseEntity<byte[]> addWatermark(
|
||||||
public ResponseEntity<byte[]> addWatermark(
|
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to add a watermark") MultipartFile pdfFile,
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestParam(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType,
|
||||||
@Parameter(description = "The input PDF file to add a watermark")
|
@RequestParam(required = false) @Parameter(description = "The watermark text") String watermarkText,
|
||||||
MultipartFile pdfFile,
|
@RequestPart(required = false) @Parameter(description = "The watermark image") MultipartFile watermarkImage,
|
||||||
@RequestParam("watermarkText")
|
|
||||||
@Parameter(description = "The watermark text to add to the PDF file")
|
@RequestParam(defaultValue = "roman", name = "alphabet") @Parameter(description = "The selected alphabet",
|
||||||
String watermarkText,
|
schema = @Schema(type = "string",
|
||||||
@RequestParam(defaultValue = "30", name = "fontSize")
|
allowableValues = {"roman","arabic","japanese","korean","chinese"},
|
||||||
@Parameter(description = "The font size of the watermark text", example = "30")
|
defaultValue = "roman")) String alphabet,
|
||||||
float fontSize,
|
@RequestParam(defaultValue = "30", name = "fontSize") @Parameter(description = "The font size of the watermark text", example = "30") float fontSize,
|
||||||
@RequestParam(defaultValue = "0", name = "rotation")
|
@RequestParam(defaultValue = "0", name = "rotation") @Parameter(description = "The rotation of the watermark in degrees", example = "0") float rotation,
|
||||||
@Parameter(description = "The rotation of the watermark text in degrees", example = "0")
|
@RequestParam(defaultValue = "0.5", name = "opacity") @Parameter(description = "The opacity of the watermark (0.0 - 1.0)", example = "0.5") float opacity,
|
||||||
float rotation,
|
@RequestParam(defaultValue = "50", name = "widthSpacer") @Parameter(description = "The width spacer between watermark elements", example = "50") int widthSpacer,
|
||||||
@RequestParam(defaultValue = "0.5", name = "opacity")
|
@RequestParam(defaultValue = "50", name = "heightSpacer") @Parameter(description = "The height spacer between watermark elements", example = "50") int heightSpacer)
|
||||||
@Parameter(description = "The opacity of the watermark text (0.0 - 1.0)", example = "0.5")
|
throws IOException, Exception {
|
||||||
float opacity,
|
|
||||||
@RequestParam(defaultValue = "50", name = "widthSpacer")
|
|
||||||
@Parameter(description = "The width spacer between watermark texts", example = "50")
|
|
||||||
int widthSpacer,
|
|
||||||
@RequestParam(defaultValue = "50", name = "heightSpacer")
|
|
||||||
@Parameter(description = "The height spacer between watermark texts", example = "50")
|
|
||||||
int heightSpacer) throws IOException {
|
|
||||||
|
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
|
|
||||||
// Create a page in the document
|
// Create a page in the document
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
|
|
||||||
// Get the page's content stream
|
// Get the page's content stream
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
|
PDPageContentStream contentStream = new PDPageContentStream(document, page,
|
||||||
|
PDPageContentStream.AppendMode.APPEND, true);
|
||||||
|
|
||||||
// Set transparency
|
// Set transparency
|
||||||
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||||
graphicsState.setNonStrokingAlphaConstant(opacity);
|
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||||
contentStream.setGraphicsStateParameters(graphicsState);
|
contentStream.setGraphicsStateParameters(graphicsState);
|
||||||
|
|
||||||
// Set font of watermark
|
if (watermarkType.equalsIgnoreCase("text")) {
|
||||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
addTextWatermark(contentStream, watermarkText, document, page, rotation, widthSpacer, heightSpacer,
|
||||||
contentStream.beginText();
|
fontSize, alphabet);
|
||||||
contentStream.setFont(font, fontSize);
|
} else if (watermarkType.equalsIgnoreCase("image")) {
|
||||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
addImageWatermark(contentStream, watermarkImage, document, page, rotation, widthSpacer, heightSpacer,
|
||||||
|
fontSize);
|
||||||
|
}
|
||||||
|
|
||||||
// Set size and location of watermark
|
// Close the content stream
|
||||||
float pageWidth = page.getMediaBox().getWidth();
|
contentStream.close();
|
||||||
float pageHeight = page.getMediaBox().getHeight();
|
}
|
||||||
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
|
||||||
float watermarkHeight = heightSpacer + fontSize;
|
|
||||||
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
|
||||||
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
|
||||||
|
|
||||||
// Add the watermark text
|
return WebResponseUtils.pdfDocToWebResponse(document,
|
||||||
for (int i = 0; i < watermarkRows; i++) {
|
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
||||||
for (int j = 0; j < watermarkCols; j++) {
|
}
|
||||||
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), j * watermarkWidth, i * watermarkHeight));
|
|
||||||
contentStream.showTextWithPositioning(new Object[] { watermarkText });
|
private void addTextWatermark(PDPageContentStream contentStream, String watermarkText, PDDocument document,
|
||||||
}
|
PDPage page, float rotation, int widthSpacer, int heightSpacer, float fontSize, String alphabet) throws IOException {
|
||||||
|
String resourceDir = "";
|
||||||
|
PDFont font = PDType1Font.HELVETICA_BOLD;
|
||||||
|
switch (alphabet) {
|
||||||
|
case "arabic":
|
||||||
|
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
||||||
|
break;
|
||||||
|
case "japanese":
|
||||||
|
resourceDir = "static/fonts/Meiryo.ttf";
|
||||||
|
break;
|
||||||
|
case "korean":
|
||||||
|
resourceDir = "static/fonts/malgun.ttf";
|
||||||
|
break;
|
||||||
|
case "chinese":
|
||||||
|
resourceDir = "static/fonts/SimSun.ttf";
|
||||||
|
break;
|
||||||
|
case "roman":
|
||||||
|
default:
|
||||||
|
resourceDir = "static/fonts/NotoSans-Regular.ttf";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(!resourceDir.equals("")) {
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
|
||||||
|
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
|
||||||
|
File tempFile = File.createTempFile("NotoSansFont", fileExtension);
|
||||||
|
try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||||
|
IOUtils.copy(is, os);
|
||||||
}
|
}
|
||||||
|
|
||||||
contentStream.endText();
|
font = PDType0Font.load(document, tempFile);
|
||||||
|
tempFile.deleteOnExit();
|
||||||
// Close the content stream
|
|
||||||
contentStream.close();
|
|
||||||
}
|
}
|
||||||
return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
|
||||||
}
|
contentStream.setFont(font, fontSize);
|
||||||
|
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
||||||
|
|
||||||
|
// Set size and location of text watermark
|
||||||
|
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
||||||
|
float watermarkHeight = heightSpacer + fontSize;
|
||||||
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
|
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
||||||
|
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
||||||
|
|
||||||
|
// Add the text watermark
|
||||||
|
for (int i = 0; i < watermarkRows; i++) {
|
||||||
|
for (int j = 0; j < watermarkCols; j++) {
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation),
|
||||||
|
j * watermarkWidth, i * watermarkHeight));
|
||||||
|
contentStream.showText(watermarkText);
|
||||||
|
contentStream.endText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addImageWatermark(PDPageContentStream contentStream, MultipartFile watermarkImage, PDDocument document, PDPage page, float rotation,
|
||||||
|
int widthSpacer, int heightSpacer, float fontSize) throws IOException {
|
||||||
|
|
||||||
|
// Load the watermark image
|
||||||
|
BufferedImage image = ImageIO.read(watermarkImage.getInputStream());
|
||||||
|
|
||||||
|
// Compute width based on original aspect ratio
|
||||||
|
float aspectRatio = (float) image.getWidth() / (float) image.getHeight();
|
||||||
|
|
||||||
|
// Desired physical height (in PDF points)
|
||||||
|
float desiredPhysicalHeight = fontSize ;
|
||||||
|
|
||||||
|
// Desired physical width based on the aspect ratio
|
||||||
|
float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;
|
||||||
|
|
||||||
|
// Convert the BufferedImage to PDImageXObject
|
||||||
|
PDImageXObject xobject = LosslessFactory.createFromImage(document, image);
|
||||||
|
|
||||||
|
// Calculate the number of rows and columns for watermarks
|
||||||
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
|
int watermarkRows = (int) ((pageHeight + heightSpacer) / (desiredPhysicalHeight + heightSpacer));
|
||||||
|
int watermarkCols = (int) ((pageWidth + widthSpacer) / (desiredPhysicalWidth + widthSpacer));
|
||||||
|
|
||||||
|
for (int i = 0; i < watermarkRows; i++) {
|
||||||
|
for (int j = 0; j < watermarkCols; j++) {
|
||||||
|
float x = j * (desiredPhysicalWidth + widthSpacer);
|
||||||
|
float y = i * (desiredPhysicalHeight + heightSpacer);
|
||||||
|
|
||||||
|
// Save the graphics state
|
||||||
|
contentStream.saveGraphicsState();
|
||||||
|
|
||||||
|
// Create rotation matrix and rotate
|
||||||
|
contentStream.transform(Matrix.getTranslateInstance(x + desiredPhysicalWidth / 2, y + desiredPhysicalHeight / 2));
|
||||||
|
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
|
||||||
|
contentStream.transform(Matrix.getTranslateInstance(-desiredPhysicalWidth / 2, -desiredPhysicalHeight / 2));
|
||||||
|
|
||||||
|
// Draw the image and restore the graphics state
|
||||||
|
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,109 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@Controller
|
|
||||||
public class ConverterWebController {
|
@Controller
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
@GetMapping("/img-to-pdf")
|
public class ConverterWebController {
|
||||||
@Hidden
|
|
||||||
public String convertImgToPdfForm(Model model) {
|
@GetMapping("/img-to-pdf")
|
||||||
model.addAttribute("currentPage", "img-to-pdf");
|
@Hidden
|
||||||
return "convert/img-to-pdf";
|
public String convertImgToPdfForm(Model model) {
|
||||||
}
|
model.addAttribute("currentPage", "img-to-pdf");
|
||||||
|
return "convert/img-to-pdf";
|
||||||
|
}
|
||||||
@GetMapping("/pdf-to-img")
|
|
||||||
@Hidden
|
@GetMapping("/html-to-pdf")
|
||||||
public String pdfToimgForm(Model model) {
|
@Hidden
|
||||||
model.addAttribute("currentPage", "pdf-to-img");
|
public String convertHTMLToPdfForm(Model model) {
|
||||||
return "convert/pdf-to-img";
|
model.addAttribute("currentPage", "html-to-pdf");
|
||||||
}
|
return "convert/html-to-pdf";
|
||||||
|
}
|
||||||
@GetMapping("/file-to-pdf")
|
@GetMapping("/markdown-to-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String convertToPdfForm(Model model) {
|
public String convertMarkdownToPdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "file-to-pdf");
|
model.addAttribute("currentPage", "markdown-to-pdf");
|
||||||
return "convert/file-to-pdf";
|
return "convert/markdown-to-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/url-to-pdf")
|
||||||
//PDF TO......
|
@Hidden
|
||||||
|
public String convertURLToPdfForm(Model model) {
|
||||||
@GetMapping("/pdf-to-html")
|
model.addAttribute("currentPage", "url-to-pdf");
|
||||||
@Hidden
|
return "convert/url-to-pdf";
|
||||||
public ModelAndView pdfToHTML() {
|
}
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
|
|
||||||
modelAndView.addObject("currentPage", "pdf-to-html");
|
|
||||||
return modelAndView;
|
@GetMapping("/pdf-to-img")
|
||||||
}
|
@Hidden
|
||||||
|
public String pdfToimgForm(Model model) {
|
||||||
@GetMapping("/pdf-to-presentation")
|
model.addAttribute("currentPage", "pdf-to-img");
|
||||||
@Hidden
|
return "convert/pdf-to-img";
|
||||||
public ModelAndView pdfToPresentation() {
|
}
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
|
|
||||||
modelAndView.addObject("currentPage", "pdf-to-presentation");
|
@GetMapping("/file-to-pdf")
|
||||||
return modelAndView;
|
@Hidden
|
||||||
}
|
public String convertToPdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "file-to-pdf");
|
||||||
@GetMapping("/pdf-to-text")
|
return "convert/file-to-pdf";
|
||||||
@Hidden
|
}
|
||||||
public ModelAndView pdfToText() {
|
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
|
|
||||||
modelAndView.addObject("currentPage", "pdf-to-text");
|
|
||||||
return modelAndView;
|
//PDF TO......
|
||||||
}
|
|
||||||
|
@GetMapping("/pdf-to-html")
|
||||||
@GetMapping("/pdf-to-word")
|
@Hidden
|
||||||
@Hidden
|
public ModelAndView pdfToHTML() {
|
||||||
public ModelAndView pdfToWord() {
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
|
modelAndView.addObject("currentPage", "pdf-to-html");
|
||||||
modelAndView.addObject("currentPage", "pdf-to-word");
|
return modelAndView;
|
||||||
return modelAndView;
|
}
|
||||||
}
|
|
||||||
|
@GetMapping("/pdf-to-presentation")
|
||||||
@GetMapping("/pdf-to-xml")
|
@Hidden
|
||||||
@Hidden
|
public ModelAndView pdfToPresentation() {
|
||||||
public ModelAndView pdfToXML() {
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
|
modelAndView.addObject("currentPage", "pdf-to-presentation");
|
||||||
modelAndView.addObject("currentPage", "pdf-to-xml");
|
return modelAndView;
|
||||||
return modelAndView;
|
}
|
||||||
}
|
|
||||||
|
@GetMapping("/pdf-to-text")
|
||||||
|
@Hidden
|
||||||
@GetMapping("/pdf-to-pdfa")
|
public ModelAndView pdfToText() {
|
||||||
@Hidden
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
|
||||||
public String pdfToPdfAForm(Model model) {
|
modelAndView.addObject("currentPage", "pdf-to-text");
|
||||||
model.addAttribute("currentPage", "pdf-to-pdfa");
|
return modelAndView;
|
||||||
return "convert/pdf-to-pdfa";
|
}
|
||||||
}
|
|
||||||
}
|
@GetMapping("/pdf-to-word")
|
||||||
|
@Hidden
|
||||||
|
public ModelAndView pdfToWord() {
|
||||||
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
|
||||||
|
modelAndView.addObject("currentPage", "pdf-to-word");
|
||||||
|
return modelAndView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pdf-to-xml")
|
||||||
|
@Hidden
|
||||||
|
public ModelAndView pdfToXML() {
|
||||||
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
|
||||||
|
modelAndView.addObject("currentPage", "pdf-to-xml");
|
||||||
|
return modelAndView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/pdf-to-pdfa")
|
||||||
|
@Hidden
|
||||||
|
public String pdfToPdfAForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "pdf-to-pdfa");
|
||||||
|
return "convert/pdf-to-pdfa";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,79 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@Controller
|
@Controller
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class GeneralWebController {
|
public class GeneralWebController {
|
||||||
|
|
||||||
|
@GetMapping("/pipeline")
|
||||||
|
@Hidden
|
||||||
|
public String pipelineForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "pipeline");
|
||||||
|
|
||||||
|
List<String> pipelineConfigs = new ArrayList<>();
|
||||||
|
try (Stream<Path> paths = Files.walk(Paths.get("./pipeline/defaultWebUIConfigs/"))) {
|
||||||
|
List<Path> jsonFiles = paths
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.filter(p -> p.toString().endsWith(".json"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (Path jsonFile : jsonFiles) {
|
||||||
|
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
||||||
|
pipelineConfigs.add(content);
|
||||||
|
}
|
||||||
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
|
for (String config : pipelineConfigs) {
|
||||||
|
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, Map.class);
|
||||||
|
String name = (String) jsonContent.get("name");
|
||||||
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
|
configWithName.put("json", config);
|
||||||
|
configWithName.put("name", name);
|
||||||
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
|
}
|
||||||
|
model.addAttribute("pipelineConfigsWithNames", pipelineConfigsWithNames);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
model.addAttribute("pipelineConfigs", pipelineConfigs);
|
||||||
|
|
||||||
|
return "pipeline";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/merge-pdfs")
|
@GetMapping("/merge-pdfs")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String mergePdfForm(Model model) {
|
public String mergePdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "merge-pdfs");
|
model.addAttribute("currentPage", "merge-pdfs");
|
||||||
return "merge-pdfs";
|
return "merge-pdfs";
|
||||||
}
|
}
|
||||||
@GetMapping("/about")
|
|
||||||
@Hidden
|
|
||||||
public String gameForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "about");
|
|
||||||
return "about";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/multi-tool")
|
@GetMapping("/multi-tool")
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -29,17 +81,7 @@ public class GeneralWebController {
|
|||||||
model.addAttribute("currentPage", "multi-tool");
|
model.addAttribute("currentPage", "multi-tool");
|
||||||
return "multi-tool";
|
return "multi-tool";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/")
|
|
||||||
public String home(Model model) {
|
|
||||||
model.addAttribute("currentPage", "home");
|
|
||||||
return "home";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/home")
|
|
||||||
public String root(Model model) {
|
|
||||||
return "redirect:/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/remove-pages")
|
@GetMapping("/remove-pages")
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -55,6 +97,20 @@ public class GeneralWebController {
|
|||||||
return "pdf-organizer";
|
return "pdf-organizer";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/extract-page")
|
||||||
|
@Hidden
|
||||||
|
public String extractPages(Model model) {
|
||||||
|
model.addAttribute("currentPage", "extract-page");
|
||||||
|
return "extract-page";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pdf-to-single-page")
|
||||||
|
@Hidden
|
||||||
|
public String pdfToSinglePage(Model model) {
|
||||||
|
model.addAttribute("currentPage", "pdf-to-single-page");
|
||||||
|
return "pdf-to-single-page";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/rotate-pdf")
|
@GetMapping("/rotate-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String rotatePdfForm(Model model) {
|
public String rotatePdfForm(Model model) {
|
||||||
@@ -73,23 +129,47 @@ public class GeneralWebController {
|
|||||||
@Hidden
|
@Hidden
|
||||||
public String signForm(Model model) {
|
public String signForm(Model model) {
|
||||||
model.addAttribute("currentPage", "sign");
|
model.addAttribute("currentPage", "sign");
|
||||||
|
model.addAttribute("fonts", getFontNames());
|
||||||
return "sign";
|
return "sign";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
@Autowired
|
||||||
@ResponseBody
|
private ResourceLoader resourceLoader;
|
||||||
@Hidden
|
|
||||||
public String getRobotsTxt() {
|
private List<String> getFontNames() {
|
||||||
String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY");
|
try {
|
||||||
if (allowGoogleVisibility == null)
|
Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
|
||||||
allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY");
|
.getResources("classpath:static/fonts/*.woff2");
|
||||||
if (allowGoogleVisibility == null)
|
|
||||||
allowGoogleVisibility = "false";
|
return Arrays.stream(resources)
|
||||||
if (Boolean.parseBoolean(allowGoogleVisibility)) {
|
.map(resource -> {
|
||||||
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
|
try {
|
||||||
} else {
|
String filename = resource.getFilename();
|
||||||
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
|
return filename.substring(0, filename.length() - 6); // Remove .woff2 extension
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error processing filename", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to read font directory", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/crop")
|
||||||
|
@Hidden
|
||||||
|
public String cropForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "crop");
|
||||||
|
return "crop";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/auto-split-pdf")
|
||||||
|
@Hidden
|
||||||
|
public String autoSPlitPDFForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "auto-split-pdf");
|
||||||
|
return "auto-split-pdf";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class HomeWebController {
|
||||||
|
|
||||||
|
@GetMapping("/about")
|
||||||
|
@Hidden
|
||||||
|
public String gameForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "about");
|
||||||
|
return "about";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String home(Model model) {
|
||||||
|
model.addAttribute("currentPage", "home");
|
||||||
|
return "home";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/home")
|
||||||
|
public String root(Model model) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||||
|
@ResponseBody
|
||||||
|
@Hidden
|
||||||
|
public String getRobotsTxt() {
|
||||||
|
String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY");
|
||||||
|
if (allowGoogleVisibility == null)
|
||||||
|
allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY");
|
||||||
|
if (allowGoogleVisibility == null)
|
||||||
|
allowGoogleVisibility = "false";
|
||||||
|
if (Boolean.parseBoolean(allowGoogleVisibility)) {
|
||||||
|
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
|
||||||
|
} else {
|
||||||
|
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -12,13 +20,32 @@ import io.micrometer.core.instrument.Counter;
|
|||||||
import io.micrometer.core.instrument.Meter;
|
import io.micrometer.core.instrument.Meter;
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import stirling.software.SPDF.config.StartupApplicationListener;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1")
|
@RequestMapping("/api/v1")
|
||||||
|
@Tag(name = "API", description = "Info APIs")
|
||||||
public class MetricsController {
|
public class MetricsController {
|
||||||
|
|
||||||
private final MeterRegistry meterRegistry;
|
private final MeterRegistry meterRegistry;
|
||||||
|
|
||||||
|
private boolean isEndpointEnabled;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
String isEndpointEnabled = System.getProperty("ENABLE_API_METRICS");
|
||||||
|
if (isEndpointEnabled == null) {
|
||||||
|
isEndpointEnabled = System.getenv("ENABLE_API_METRICS");
|
||||||
|
if (isEndpointEnabled == null) {
|
||||||
|
isEndpointEnabled = "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isEndpointEnabled = "true".equalsIgnoreCase(isEndpointEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
public MetricsController(MeterRegistry meterRegistry) {
|
public MetricsController(MeterRegistry meterRegistry) {
|
||||||
this.meterRegistry = meterRegistry;
|
this.meterRegistry = meterRegistry;
|
||||||
}
|
}
|
||||||
@@ -26,55 +53,211 @@ public class MetricsController {
|
|||||||
@GetMapping("/status")
|
@GetMapping("/status")
|
||||||
@Operation(summary = "Application status and version",
|
@Operation(summary = "Application status and version",
|
||||||
description = "This endpoint returns the status of the application and its version number.")
|
description = "This endpoint returns the status of the application and its version number.")
|
||||||
public Map<String, String> getStatus() {
|
public ResponseEntity<?> getStatus() {
|
||||||
|
if (!isEndpointEnabled) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, String> status = new HashMap<>();
|
Map<String, String> status = new HashMap<>();
|
||||||
status.put("status", "UP");
|
status.put("status", "UP");
|
||||||
status.put("version", getClass().getPackage().getImplementationVersion());
|
status.put("version", getClass().getPackage().getImplementationVersion());
|
||||||
return status;
|
return ResponseEntity.ok(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/loads")
|
@GetMapping("/loads")
|
||||||
@Operation(summary = "GET request count",
|
@Operation(summary = "GET request count",
|
||||||
description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
|
description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
|
||||||
public Double getPageLoads(@RequestParam Optional<String> endpoint) {
|
public ResponseEntity<?> getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
||||||
try {
|
if (!isEndpointEnabled) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
double count = 0.0;
|
double count = 0.0;
|
||||||
|
|
||||||
|
for (Meter meter : meterRegistry.getMeters()) {
|
||||||
|
if (meter.getId().getName().equals("http.requests")) {
|
||||||
|
String method = meter.getId().getTag("method");
|
||||||
|
if (method != null && method.equals("GET")) {
|
||||||
|
|
||||||
|
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
|
||||||
|
if(!endpoint.get().startsWith("/")) {
|
||||||
|
endpoint = Optional.of("/" + endpoint.get());
|
||||||
|
}
|
||||||
|
System.out.println("loads " + endpoint.get() + " vs " + meter.getId().getTag("uri"));
|
||||||
|
if(endpoint.get().equals(meter.getId().getTag("uri"))){
|
||||||
|
if (meter instanceof Counter) {
|
||||||
|
count += ((Counter) meter).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (meter instanceof Counter) {
|
||||||
|
count += ((Counter) meter).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(count);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/loads/all")
|
||||||
|
@Operation(summary = "GET requests count for all endpoints",
|
||||||
|
description = "This endpoint returns the count of GET requests for each endpoint.")
|
||||||
|
public ResponseEntity<?> getAllEndpointLoads() {
|
||||||
|
if (!isEndpointEnabled) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Map<String, Double> counts = new HashMap<>();
|
||||||
|
|
||||||
for (Meter meter : meterRegistry.getMeters()) {
|
for (Meter meter : meterRegistry.getMeters()) {
|
||||||
if (meter.getId().getName().equals("http.requests")) {
|
if (meter.getId().getName().equals("http.requests")) {
|
||||||
String method = meter.getId().getTag("method");
|
String method = meter.getId().getTag("method");
|
||||||
if (method != null && method.equals("GET")) {
|
if (method != null && method.equals("GET")) {
|
||||||
if (meter instanceof Counter) {
|
String uri = meter.getId().getTag("uri");
|
||||||
count += ((Counter) meter).count();
|
if (uri != null) {
|
||||||
|
double currentCount = counts.getOrDefault(uri, 0.0);
|
||||||
|
if (meter instanceof Counter) {
|
||||||
|
currentCount += ((Counter) meter).count();
|
||||||
|
}
|
||||||
|
counts.put(uri, currentCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
List<EndpointCount> results = counts.entrySet().stream()
|
||||||
|
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
|
||||||
|
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(results);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return -1.0;
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EndpointCount {
|
||||||
|
private String endpoint;
|
||||||
|
private double count;
|
||||||
|
|
||||||
|
public EndpointCount(String endpoint, double count) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
public String getEndpoint() {
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
public void setEndpoint(String endpoint) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
public double getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
public void setCount(double count) {
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/requests")
|
@GetMapping("/requests")
|
||||||
@Operation(summary = "POST request count",
|
@Operation(summary = "POST request count",
|
||||||
description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
|
description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
|
||||||
public Double getTotalRequests(@RequestParam Optional<String> endpoint) {
|
public ResponseEntity<?> getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
||||||
try {
|
if (!isEndpointEnabled) {
|
||||||
Counter counter;
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
if (endpoint.isPresent()) {
|
}
|
||||||
counter = meterRegistry.get("http.requests")
|
try {
|
||||||
.tags("method", "POST", "uri", endpoint.get()).counter();
|
double count = 0.0;
|
||||||
} else {
|
|
||||||
counter = meterRegistry.get("http.requests")
|
for (Meter meter : meterRegistry.getMeters()) {
|
||||||
.tags("method", "POST").counter();
|
if (meter.getId().getName().equals("http.requests")) {
|
||||||
}
|
String method = meter.getId().getTag("method");
|
||||||
return counter.count();
|
if (method != null && method.equals("POST")) {
|
||||||
} catch (Exception e) {
|
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
|
||||||
return -1.0;
|
if (!endpoint.get().startsWith("/")) {
|
||||||
|
endpoint = Optional.of("/" + endpoint.get());
|
||||||
|
}
|
||||||
|
if (endpoint.get().equals(meter.getId().getTag("uri"))) {
|
||||||
|
if (meter instanceof Counter) {
|
||||||
|
count += ((Counter) meter).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (meter instanceof Counter) {
|
||||||
|
count += ((Counter) meter).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(count);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.ok(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/requests/all")
|
||||||
|
@Operation(summary = "POST requests count for all endpoints",
|
||||||
|
description = "This endpoint returns the count of POST requests for each endpoint.")
|
||||||
|
public ResponseEntity<?> getAllPostRequests() {
|
||||||
|
if (!isEndpointEnabled) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Map<String, Double> counts = new HashMap<>();
|
||||||
|
|
||||||
|
for (Meter meter : meterRegistry.getMeters()) {
|
||||||
|
if (meter.getId().getName().equals("http.requests")) {
|
||||||
|
String method = meter.getId().getTag("method");
|
||||||
|
if (method != null && method.equals("POST")) {
|
||||||
|
String uri = meter.getId().getTag("uri");
|
||||||
|
if (uri != null) {
|
||||||
|
double currentCount = counts.getOrDefault(uri, 0.0);
|
||||||
|
if (meter instanceof Counter) {
|
||||||
|
currentCount += ((Counter) meter).count();
|
||||||
|
}
|
||||||
|
counts.put(uri, currentCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EndpointCount> results = counts.entrySet().stream()
|
||||||
|
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
|
||||||
|
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(results);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/uptime")
|
||||||
|
public ResponseEntity<?> getUptime() {
|
||||||
|
if (!isEndpointEnabled) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
Duration uptime = Duration.between(StartupApplicationListener.startTime, now);
|
||||||
|
return ResponseEntity.ok(formatDuration(uptime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDuration(Duration duration) {
|
||||||
|
long days = duration.toDays();
|
||||||
|
long hours = duration.toHoursPart();
|
||||||
|
long minutes = duration.toMinutesPart();
|
||||||
|
long seconds = duration.toSecondsPart();
|
||||||
|
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@Tag(name = "Other", description = "Other APIs")
|
||||||
public class OtherWebController {
|
public class OtherWebController {
|
||||||
@GetMapping("/compress-pdf")
|
@GetMapping("/compress-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
@@ -29,7 +31,22 @@ public class OtherWebController {
|
|||||||
modelAndView.addObject("currentPage", "extract-image-scans");
|
modelAndView.addObject("currentPage", "extract-image-scans");
|
||||||
return modelAndView;
|
return modelAndView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/show-javascript")
|
||||||
|
@Hidden
|
||||||
|
public String extractJavascriptForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "show-javascript");
|
||||||
|
return "other/show-javascript";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/add-page-numbers")
|
||||||
|
@Hidden
|
||||||
|
public String addPageNumbersForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "add-page-numbers");
|
||||||
|
return "other/add-page-numbers";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/extract-images")
|
@GetMapping("/extract-images")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String extractImagesForm(Model model) {
|
public String extractImagesForm(Model model) {
|
||||||
@@ -131,4 +148,13 @@ public class OtherWebController {
|
|||||||
return "other/auto-crop";
|
return "other/auto-crop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/auto-rename")
|
||||||
|
@Hidden
|
||||||
|
public String autoRenameForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "auto-rename");
|
||||||
|
return "other/auto-rename";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,62 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@Controller
|
|
||||||
public class SecurityWebController {
|
@Controller
|
||||||
@GetMapping("/add-password")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
@Hidden
|
public class SecurityWebController {
|
||||||
public String addPasswordForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "add-password");
|
|
||||||
return "security/add-password";
|
@GetMapping("/add-password")
|
||||||
}
|
@Hidden
|
||||||
@GetMapping("/change-permissions")
|
public String addPasswordForm(Model model) {
|
||||||
@Hidden
|
model.addAttribute("currentPage", "add-password");
|
||||||
public String permissionsForm(Model model) {
|
return "security/add-password";
|
||||||
model.addAttribute("currentPage", "change-permissions");
|
}
|
||||||
return "security/change-permissions";
|
@GetMapping("/change-permissions")
|
||||||
}
|
@Hidden
|
||||||
|
public String permissionsForm(Model model) {
|
||||||
@GetMapping("/remove-password")
|
model.addAttribute("currentPage", "change-permissions");
|
||||||
@Hidden
|
return "security/change-permissions";
|
||||||
public String removePasswordForm(Model model) {
|
}
|
||||||
model.addAttribute("currentPage", "remove-password");
|
|
||||||
return "security/remove-password";
|
@GetMapping("/remove-password")
|
||||||
}
|
@Hidden
|
||||||
|
public String removePasswordForm(Model model) {
|
||||||
@GetMapping("/add-watermark")
|
model.addAttribute("currentPage", "remove-password");
|
||||||
@Hidden
|
return "security/remove-password";
|
||||||
public String addWatermarkForm(Model model) {
|
}
|
||||||
model.addAttribute("currentPage", "add-watermark");
|
|
||||||
return "security/add-watermark";
|
@GetMapping("/add-watermark")
|
||||||
}
|
@Hidden
|
||||||
|
public String addWatermarkForm(Model model) {
|
||||||
@GetMapping("/cert-sign")
|
model.addAttribute("currentPage", "add-watermark");
|
||||||
@Hidden
|
return "security/add-watermark";
|
||||||
public String certSignForm(Model model) {
|
}
|
||||||
model.addAttribute("currentPage", "cert-sign");
|
|
||||||
return "security/cert-sign";
|
@GetMapping("/cert-sign")
|
||||||
}
|
@Hidden
|
||||||
}
|
public String certSignForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "cert-sign");
|
||||||
|
return "security/cert-sign";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sanitize-pdf")
|
||||||
|
@Hidden
|
||||||
|
public String sanitizeForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "sanitize-pdf");
|
||||||
|
return "security/sanitize-pdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get-info-on-pdf")
|
||||||
|
@Hidden
|
||||||
|
public String getInfo(Model model) {
|
||||||
|
model.addAttribute("currentPage", "get-info-on-pdf");
|
||||||
|
return "security/get-info-on-pdf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public class PipelineConfig {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@JsonProperty("pipeline")
|
||||||
|
private List<PipelineOperation> operations;
|
||||||
|
|
||||||
|
private String outputDir;
|
||||||
|
|
||||||
|
@JsonProperty("outputFileName")
|
||||||
|
private String outputPattern;
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PipelineOperation> getOperations() {
|
||||||
|
return operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperations(List<PipelineOperation> operations) {
|
||||||
|
this.operations = operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOutputDir() {
|
||||||
|
return outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputDir(String outputDir) {
|
||||||
|
this.outputDir = outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOutputPattern() {
|
||||||
|
return outputPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputPattern(String outputPattern) {
|
||||||
|
this.outputPattern = outputPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class PipelineOperation {
|
||||||
|
private String operation;
|
||||||
|
private Map<String, Object> parameters;
|
||||||
|
|
||||||
|
|
||||||
|
public String getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperation(String operation) {
|
||||||
|
this.operation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getParameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParameters(Map<String, Object> parameters) {
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PipelineOperation [operation=" + operation + ", parameters=" + parameters + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
95
src/main/java/stirling/software/SPDF/utils/FileToPdf.java
Normal file
95
src/main/java/stirling/software/SPDF/utils/FileToPdf.java
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
|
public class FileToPdf {
|
||||||
|
public static byte[] convertHtmlToPdf(byte[] fileBytes, String fileName) throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
Path tempInputFile = null;
|
||||||
|
byte[] pdfBytes;
|
||||||
|
try {
|
||||||
|
if (fileName.endsWith(".html")) {
|
||||||
|
tempInputFile = Files.createTempFile("input_", ".html");
|
||||||
|
Files.write(tempInputFile, fileBytes);
|
||||||
|
} else {
|
||||||
|
tempInputFile = unzipAndGetMainHtml(fileBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add("weasyprint");
|
||||||
|
command.add(tempInputFile.toString());
|
||||||
|
command.add(tempOutputFile.toString());
|
||||||
|
ProcessExecutorResult returnCode;
|
||||||
|
if (fileName.endsWith(".zip")) {
|
||||||
|
returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
|
.runCommandWithOutputHandling(command, tempInputFile.getParent().toFile());
|
||||||
|
} else {
|
||||||
|
|
||||||
|
returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
} finally {
|
||||||
|
// Clean up temporary files
|
||||||
|
Files.delete(tempOutputFile);
|
||||||
|
Files.delete(tempInputFile);
|
||||||
|
|
||||||
|
if (fileName.endsWith(".zip")) {
|
||||||
|
GeneralUtils.deleteDirectory(tempInputFile.getParent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pdfBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException {
|
||||||
|
Path tempDirectory = Files.createTempDirectory("unzipped_");
|
||||||
|
try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(fileBytes))) {
|
||||||
|
ZipEntry entry = zipIn.getNextEntry();
|
||||||
|
while (entry != null) {
|
||||||
|
Path filePath = tempDirectory.resolve(entry.getName());
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
Files.createDirectories(filePath); // Explicitly create the directory structure
|
||||||
|
} else {
|
||||||
|
Files.createDirectories(filePath.getParent()); // Create parent directories if they don't exist
|
||||||
|
Files.copy(zipIn, filePath);
|
||||||
|
}
|
||||||
|
zipIn.closeEntry();
|
||||||
|
entry = zipIn.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//search for the main HTML file.
|
||||||
|
try (Stream<Path> walk = Files.walk(tempDirectory)) {
|
||||||
|
List<Path> htmlFiles = walk.filter(file -> file.toString().endsWith(".html"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (htmlFiles.isEmpty()) {
|
||||||
|
throw new IOException("No HTML files found in the unzipped directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioritize 'index.html' if it exists, otherwise use the first .html file
|
||||||
|
for (Path htmlFile : htmlFiles) {
|
||||||
|
if (htmlFile.getFileName().toString().equals("index.html")) {
|
||||||
|
return htmlFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlFiles.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,91 +1,153 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
public class GeneralUtils {
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
public static Long convertSizeToBytes(String sizeStr) {
|
import java.nio.file.Path;
|
||||||
if (sizeStr == null) {
|
import java.nio.file.Paths;
|
||||||
return null;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
}
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.ArrayList;
|
||||||
sizeStr = sizeStr.trim().toUpperCase();
|
import java.util.List;
|
||||||
try {
|
|
||||||
if (sizeStr.endsWith("KB")) {
|
public class GeneralUtils {
|
||||||
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
|
||||||
} else if (sizeStr.endsWith("MB")) {
|
public static void deleteDirectory(Path path) throws IOException {
|
||||||
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024);
|
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||||
} else if (sizeStr.endsWith("GB")) {
|
@Override
|
||||||
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024);
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
} else if (sizeStr.endsWith("B")) {
|
Files.delete(file);
|
||||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
return FileVisitResult.CONTINUE;
|
||||||
} else {
|
}
|
||||||
// Input string does not have a valid format, handle this case
|
|
||||||
}
|
@Override
|
||||||
} catch (NumberFormatException e) {
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||||
// The numeric part of the input string cannot be parsed, handle this case
|
Files.delete(dir);
|
||||||
}
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
return null;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
|
public static String convertToFileName(String name) {
|
||||||
List<Integer> newPageOrder = new ArrayList<>();
|
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_");
|
||||||
|
if (safeName.length() > 50) {
|
||||||
// loop through the page order array
|
safeName = safeName.substring(0, 50);
|
||||||
for (String element : pageOrderArr) {
|
}
|
||||||
// check if the element contains a range of pages
|
return safeName;
|
||||||
if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
|
}
|
||||||
// Handle page order as a function
|
|
||||||
int coefficient = 0;
|
|
||||||
int constant = 0;
|
public static boolean isValidURL(String urlStr) {
|
||||||
boolean coefficientExists = false;
|
try {
|
||||||
boolean constantExists = false;
|
new URL(urlStr);
|
||||||
|
return true;
|
||||||
if (element.contains("n")) {
|
} catch (MalformedURLException e) {
|
||||||
String[] parts = element.split("n");
|
return false;
|
||||||
if (!parts[0].equals("") && parts[0] != null) {
|
}
|
||||||
coefficient = Integer.parseInt(parts[0]);
|
}
|
||||||
coefficientExists = true;
|
|
||||||
}
|
|
||||||
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) {
|
public static Long convertSizeToBytes(String sizeStr) {
|
||||||
constant = Integer.parseInt(parts[1]);
|
if (sizeStr == null) {
|
||||||
constantExists = true;
|
return null;
|
||||||
}
|
}
|
||||||
} else if (element.contains("+")) {
|
|
||||||
constant = Integer.parseInt(element.replace("+", ""));
|
sizeStr = sizeStr.trim().toUpperCase();
|
||||||
constantExists = true;
|
try {
|
||||||
}
|
if (sizeStr.endsWith("KB")) {
|
||||||
|
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
||||||
for (int i = 1; i <= totalPages; i++) {
|
} else if (sizeStr.endsWith("MB")) {
|
||||||
int pageNum = coefficientExists ? coefficient * i : i;
|
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024);
|
||||||
pageNum += constantExists ? constant : 0;
|
} else if (sizeStr.endsWith("GB")) {
|
||||||
|
return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024);
|
||||||
if (pageNum <= totalPages && pageNum > 0) {
|
} else if (sizeStr.endsWith("B")) {
|
||||||
newPageOrder.add(pageNum - 1);
|
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
||||||
}
|
} else {
|
||||||
}
|
// Input string does not have a valid format, handle this case
|
||||||
} else if (element.contains("-")) {
|
}
|
||||||
// split the range into start and end page
|
} catch (NumberFormatException e) {
|
||||||
String[] range = element.split("-");
|
// The numeric part of the input string cannot be parsed, handle this case
|
||||||
int start = Integer.parseInt(range[0]);
|
}
|
||||||
int end = Integer.parseInt(range[1]);
|
|
||||||
// check if the end page is greater than total pages
|
return null;
|
||||||
if (end > totalPages) {
|
}
|
||||||
end = totalPages;
|
|
||||||
}
|
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
|
||||||
// loop through the range of pages
|
List<Integer> newPageOrder = new ArrayList<>();
|
||||||
for (int j = start; j <= end; j++) {
|
|
||||||
// print the current index
|
// loop through the page order array
|
||||||
newPageOrder.add(j - 1);
|
for (String element : pageOrderArr) {
|
||||||
}
|
if (element.equalsIgnoreCase("all")) {
|
||||||
} else {
|
for (int i = 0; i < totalPages; i++) {
|
||||||
// if the element is a single page
|
newPageOrder.add(i);
|
||||||
newPageOrder.add(Integer.parseInt(element) - 1);
|
}
|
||||||
}
|
// As all pages are already added, no need to check further
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
return newPageOrder;
|
else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
|
||||||
}
|
// Handle page order as a function
|
||||||
}
|
int coefficient = 0;
|
||||||
|
int constant = 0;
|
||||||
|
boolean coefficientExists = false;
|
||||||
|
boolean constantExists = false;
|
||||||
|
|
||||||
|
if (element.contains("n")) {
|
||||||
|
String[] parts = element.split("n");
|
||||||
|
if (!parts[0].equals("") && parts[0] != null) {
|
||||||
|
coefficient = Integer.parseInt(parts[0]);
|
||||||
|
coefficientExists = true;
|
||||||
|
}
|
||||||
|
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) {
|
||||||
|
constant = Integer.parseInt(parts[1]);
|
||||||
|
constantExists = true;
|
||||||
|
}
|
||||||
|
} else if (element.contains("+")) {
|
||||||
|
constant = Integer.parseInt(element.replace("+", ""));
|
||||||
|
constantExists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i <= totalPages; i++) {
|
||||||
|
int pageNum = coefficientExists ? coefficient * i : i;
|
||||||
|
pageNum += constantExists ? constant : 0;
|
||||||
|
|
||||||
|
if (pageNum <= totalPages && pageNum > 0) {
|
||||||
|
newPageOrder.add(pageNum - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (element.contains("-")) {
|
||||||
|
// split the range into start and end page
|
||||||
|
String[] range = element.split("-");
|
||||||
|
int start = Integer.parseInt(range[0]);
|
||||||
|
int end = Integer.parseInt(range[1]);
|
||||||
|
// check if the end page is greater than total pages
|
||||||
|
if (end > totalPages) {
|
||||||
|
end = totalPages;
|
||||||
|
}
|
||||||
|
// loop through the range of pages
|
||||||
|
for (int j = start; j <= end; j++) {
|
||||||
|
// print the current index
|
||||||
|
newPageOrder.add(j - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the element is a single page
|
||||||
|
newPageOrder.add(Integer.parseInt(element) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPageOrder;
|
||||||
|
}
|
||||||
|
public static boolean createDir(String path) {
|
||||||
|
Path folder = Paths.get(path);
|
||||||
|
if (!Files.exists(folder)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(folder);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
public class PDFToFile {
|
public class PDFToFile {
|
||||||
public ResponseEntity<byte[]> processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException {
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ public class PDFToFile {
|
|||||||
// Run the LibreOffice command
|
// Run the LibreOffice command
|
||||||
List<String> command = new ArrayList<>(
|
List<String> command = new ArrayList<>(
|
||||||
Arrays.asList("soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString()));
|
Arrays.asList("soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString()));
|
||||||
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
|
ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Get output files
|
// Get output files
|
||||||
List<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
|
List<File> outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());
|
||||||
|
|||||||
@@ -27,14 +27,178 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
|||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
|
||||||
|
import com.itextpdf.kernel.pdf.canvas.parser.listener.SimpleTextExtractionStrategy;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.pdf.ImageFinder;
|
||||||
|
|
||||||
public class PdfUtils {
|
public class PdfUtils {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
||||||
|
|
||||||
|
|
||||||
|
public static PDRectangle textToPageSize(String size) {
|
||||||
|
switch (size.toUpperCase()) {
|
||||||
|
case "A0":
|
||||||
|
return PDRectangle.A0;
|
||||||
|
case "A1":
|
||||||
|
return PDRectangle.A1;
|
||||||
|
case "A2":
|
||||||
|
return PDRectangle.A2;
|
||||||
|
case "A3":
|
||||||
|
return PDRectangle.A3;
|
||||||
|
case "A4":
|
||||||
|
return PDRectangle.A4;
|
||||||
|
case "A5":
|
||||||
|
return PDRectangle.A5;
|
||||||
|
case "A6":
|
||||||
|
return PDRectangle.A6;
|
||||||
|
case "LETTER":
|
||||||
|
return PDRectangle.LETTER;
|
||||||
|
case "LEGAL":
|
||||||
|
return PDRectangle.LEGAL;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid standard page size: " + size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
|
||||||
|
String[] pageOrderArr = pagesToCheck.split(",");
|
||||||
|
List<Integer> pageList = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
||||||
|
|
||||||
|
for (int pageNumber : pageList) {
|
||||||
|
PDPage page = document.getPage(pageNumber);
|
||||||
|
if (hasImagesOnPage(page)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) throws IOException {
|
||||||
|
String[] pageOrderArr = pageNumbersToCheck.split(",");
|
||||||
|
List<Integer> pageList = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
||||||
|
|
||||||
|
for (int pageNumber : pageList) {
|
||||||
|
PDPage page = document.getPage(pageNumber);
|
||||||
|
if (hasTextOnPage(page, phrase)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean hasImagesOnPage(PDPage page) throws IOException {
|
||||||
|
ImageFinder imageFinder = new ImageFinder(page);
|
||||||
|
imageFinder.processPage(page);
|
||||||
|
return imageFinder.hasImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
|
||||||
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
|
PDDocument tempDoc = new PDDocument();
|
||||||
|
tempDoc.addPage(page);
|
||||||
|
String pageText = textStripper.getText(tempDoc);
|
||||||
|
tempDoc.close();
|
||||||
|
return pageText.contains(phrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) throws IOException {
|
||||||
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
|
String pdfText = "";
|
||||||
|
|
||||||
|
if(pagesToCheck == null || pagesToCheck.equals("all")) {
|
||||||
|
pdfText = textStripper.getText(pdfDocument);
|
||||||
|
} else {
|
||||||
|
// remove whitespaces
|
||||||
|
pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
|
||||||
|
|
||||||
|
String[] splitPoints = pagesToCheck.split(",");
|
||||||
|
for (String splitPoint : splitPoints) {
|
||||||
|
if (splitPoint.contains("-")) {
|
||||||
|
// Handle page ranges
|
||||||
|
String[] range = splitPoint.split("-");
|
||||||
|
int startPage = Integer.parseInt(range[0]);
|
||||||
|
int endPage = Integer.parseInt(range[1]);
|
||||||
|
|
||||||
|
for (int i = startPage; i <= endPage; i++) {
|
||||||
|
textStripper.setStartPage(i);
|
||||||
|
textStripper.setEndPage(i);
|
||||||
|
pdfText += textStripper.getText(pdfDocument);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle individual page
|
||||||
|
int page = Integer.parseInt(splitPoint);
|
||||||
|
textStripper.setStartPage(page);
|
||||||
|
textStripper.setEndPage(page);
|
||||||
|
pdfText += textStripper.getText(pdfDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfDocument.close();
|
||||||
|
|
||||||
|
return pdfText.contains(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) throws IOException {
|
||||||
|
int actualPageCount = pdfDocument.getNumberOfPages();
|
||||||
|
pdfDocument.close();
|
||||||
|
|
||||||
|
switch(comparator.toLowerCase()) {
|
||||||
|
case "greater":
|
||||||
|
return actualPageCount > pageCount;
|
||||||
|
case "equal":
|
||||||
|
return actualPageCount == pageCount;
|
||||||
|
case "less":
|
||||||
|
return actualPageCount < pageCount;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid comparator. Only 'greater', 'equal', and 'less' are supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException {
|
||||||
|
PDPage firstPage = pdfDocument.getPage(0);
|
||||||
|
PDRectangle mediaBox = firstPage.getMediaBox();
|
||||||
|
|
||||||
|
float actualPageWidth = mediaBox.getWidth();
|
||||||
|
float actualPageHeight = mediaBox.getHeight();
|
||||||
|
|
||||||
|
pdfDocument.close();
|
||||||
|
|
||||||
|
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
|
||||||
|
String[] dimensions = expectedPageSize.split("x");
|
||||||
|
float expectedPageWidth = Float.parseFloat(dimensions[0]);
|
||||||
|
float expectedPageHeight = Float.parseFloat(dimensions[1]);
|
||||||
|
|
||||||
|
// Checks if the actual page size matches the expected page size
|
||||||
|
return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception {
|
public static byte[] convertFromPdf(byte[] inputStream, String imageType, ImageType colorType, boolean singleImage, int DPI, String filename) throws IOException, Exception {
|
||||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
@@ -43,7 +207,7 @@ public class PdfUtils {
|
|||||||
|
|
||||||
// Create images of all pages
|
// Create images of all pages
|
||||||
for (int i = 0; i < pageCount; i++) {
|
for (int i = 0; i < pageCount; i++) {
|
||||||
images.add(pdfRenderer.renderImageWithDPI(i, 300, colorType));
|
images.add(pdfRenderer.renderImageWithDPI(i, DPI, colorType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (singleImage) {
|
if (singleImage) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -13,7 +14,7 @@ import java.util.concurrent.Semaphore;
|
|||||||
public class ProcessExecutor {
|
public class ProcessExecutor {
|
||||||
|
|
||||||
public enum Processes {
|
public enum Processes {
|
||||||
LIBRE_OFFICE, OCR_MY_PDF, PYTHON_OPENCV, GHOSTSCRIPT
|
LIBRE_OFFICE, OCR_MY_PDF, PYTHON_OPENCV, GHOSTSCRIPT, WEASYPRINT
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
||||||
@@ -25,6 +26,7 @@ public class ProcessExecutor {
|
|||||||
case OCR_MY_PDF -> 2;
|
case OCR_MY_PDF -> 2;
|
||||||
case PYTHON_OPENCV -> 8;
|
case PYTHON_OPENCV -> 8;
|
||||||
case GHOSTSCRIPT -> 16;
|
case GHOSTSCRIPT -> 16;
|
||||||
|
case WEASYPRINT -> 16;
|
||||||
};
|
};
|
||||||
return new ProcessExecutor(semaphoreLimit);
|
return new ProcessExecutor(semaphoreLimit);
|
||||||
});
|
});
|
||||||
@@ -35,14 +37,22 @@ public class ProcessExecutor {
|
|||||||
private ProcessExecutor(int semaphoreLimit) {
|
private ProcessExecutor(int semaphoreLimit) {
|
||||||
this.semaphore = new Semaphore(semaphoreLimit);
|
this.semaphore = new Semaphore(semaphoreLimit);
|
||||||
}
|
}
|
||||||
|
public ProcessExecutorResult runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
|
||||||
public int runCommandWithOutputHandling(List<String> command) throws IOException, InterruptedException {
|
return runCommandWithOutputHandling(command, null);
|
||||||
|
}
|
||||||
|
public ProcessExecutorResult runCommandWithOutputHandling(List<String> command, File workingDirectory) throws IOException, InterruptedException {
|
||||||
int exitCode = 1;
|
int exitCode = 1;
|
||||||
|
String messages = "";
|
||||||
semaphore.acquire();
|
semaphore.acquire();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
System.out.print("Running command: " + String.join(" ", command));
|
System.out.print("Running command: " + String.join(" ", command));
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||||
|
|
||||||
|
// Use the working directory if it's set
|
||||||
|
if (workingDirectory != null) {
|
||||||
|
processBuilder.directory(workingDirectory);
|
||||||
|
}
|
||||||
Process process = processBuilder.start();
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
// Read the error stream and standard output stream concurrently
|
// Read the error stream and standard output stream concurrently
|
||||||
@@ -80,14 +90,16 @@ public class ProcessExecutor {
|
|||||||
// Wait for the reader threads to finish
|
// Wait for the reader threads to finish
|
||||||
errorReaderThread.join();
|
errorReaderThread.join();
|
||||||
outputReaderThread.join();
|
outputReaderThread.join();
|
||||||
|
|
||||||
if (outputLines.size() > 0) {
|
if (outputLines.size() > 0) {
|
||||||
String outputMessage = String.join("\n", outputLines);
|
String outputMessage = String.join("\n", outputLines);
|
||||||
|
messages += outputMessage;
|
||||||
System.out.println("Command output:\n" + outputMessage);
|
System.out.println("Command output:\n" + outputMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorLines.size() > 0) {
|
if (errorLines.size() > 0) {
|
||||||
String errorMessage = String.join("\n", errorLines);
|
String errorMessage = String.join("\n", errorLines);
|
||||||
|
messages += errorMessage;
|
||||||
System.out.println("Command error output:\n" + errorMessage);
|
System.out.println("Command error output:\n" + errorMessage);
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
|
throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage);
|
||||||
@@ -96,7 +108,28 @@ public class ProcessExecutor {
|
|||||||
} finally {
|
} finally {
|
||||||
semaphore.release();
|
semaphore.release();
|
||||||
}
|
}
|
||||||
return exitCode;
|
return new ProcessExecutorResult(exitCode, messages);
|
||||||
|
}
|
||||||
|
public class ProcessExecutorResult{
|
||||||
|
int rc;
|
||||||
|
String messages;
|
||||||
|
public ProcessExecutorResult(int rc, String messages) {
|
||||||
|
this.rc = rc;
|
||||||
|
this.messages = messages;
|
||||||
|
}
|
||||||
|
public int getRc() {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
public void setRc(int rc) {
|
||||||
|
this.rc = rc;
|
||||||
|
}
|
||||||
|
public String getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
public void setMessages(String messages) {
|
||||||
|
this.messages = messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,78 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
public class WebResponseUtils {
|
|
||||||
|
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||||
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
|
||||||
}
|
public class WebResponseUtils {
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
|
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException {
|
||||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName, MediaType mediaType) throws IOException {
|
public static ResponseEntity<byte[]> boasToWebResponse(ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
|
||||||
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
|
||||||
// Return the PDF as a response
|
}
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(mediaType);
|
|
||||||
headers.setContentLength(bytes.length);
|
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file) throws IOException {
|
||||||
String encodedDocName = URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
|
String fileName = file.getOriginalFilename();
|
||||||
headers.setContentDispositionFormData("attachment", encodedDocName);
|
MediaType mediaType = MediaType.parseMediaType(file.getContentType());
|
||||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
|
||||||
}
|
byte[] bytes = file.getBytes();
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
|
return bytesToWebResponse(bytes, fileName, mediaType);
|
||||||
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);
|
}
|
||||||
}
|
|
||||||
|
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName, MediaType mediaType) throws IOException {
|
||||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException {
|
|
||||||
|
// Return the PDF as a response
|
||||||
// Open Byte Array and save document to it
|
HttpHeaders headers = new HttpHeaders();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
headers.setContentType(mediaType);
|
||||||
document.save(baos);
|
headers.setContentLength(bytes.length);
|
||||||
// Close the document
|
String encodedDocName = URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
|
||||||
document.close();
|
headers.setContentDispositionFormData("attachment", encodedDocName);
|
||||||
|
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
||||||
return boasToWebResponse(baos, docName);
|
}
|
||||||
}
|
|
||||||
|
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) throws IOException {
|
||||||
}
|
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) throws IOException {
|
||||||
|
|
||||||
|
// Open Byte Array and save document to it
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
document.save(baos);
|
||||||
|
// Close the document
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
return boasToWebResponse(baos, docName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseEntity<byte[]> pdfDocToWebResponse(PdfDocument document, String docName) throws IOException {
|
||||||
|
|
||||||
|
// Open Byte Array and save document to it
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
PdfWriter writer = new PdfWriter(baos);
|
||||||
|
PdfDocument newDocument = new PdfDocument(writer);
|
||||||
|
|
||||||
|
document.copyPagesTo(1, document.getNumberOfPages(), newDocument);
|
||||||
|
newDocument.close();
|
||||||
|
|
||||||
|
return boasToWebResponse(baos, docName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
spring.http.multipart.max-file-size=2GB
|
spring.http.multipart.max-file-size=${MAX_FILE_SIZE:2000MB}
|
||||||
spring.http.multipart.max-request-size=2GB
|
spring.http.multipart.max-request-size=${MAX_FILE_SIZE:2000MB}
|
||||||
|
|
||||||
multipart.enabled=true
|
multipart.enabled=true
|
||||||
multipart.max-file-size=2000MB
|
multipart.max-file-size=${MAX_FILE_SIZE:2000MB}
|
||||||
multipart.max-request-size=2000MB
|
multipart.max-request-size=${MAX_FILE_SIZE:2000MB}
|
||||||
|
|
||||||
spring.servlet.multipart.max-file-size=2000MB
|
spring.servlet.multipart.max-file-size=${MAX_FILE_SIZE:2000MB}
|
||||||
spring.servlet.multipart.max-request-size=2000MB
|
spring.servlet.multipart.max-request-size=${MAX_FILE_SIZE:2000MB}
|
||||||
|
|
||||||
server.forward-headers-strategy=NATIVE
|
server.forward-headers-strategy=NATIVE
|
||||||
|
|
||||||
@@ -16,13 +16,20 @@ server.error.include-stacktrace=always
|
|||||||
server.error.include-exception=true
|
server.error.include-exception=true
|
||||||
server.error.include-message=always
|
server.error.include-message=always
|
||||||
|
|
||||||
|
#logging.level.org.springframework.web=DEBUG
|
||||||
|
|
||||||
|
|
||||||
server.servlet.session.tracking-modes=cookie
|
server.servlet.session.tracking-modes=cookie
|
||||||
server.servlet.context-path=${APP_ROOT_PATH:/}
|
server.servlet.context-path=${APP_ROOT_PATH:/}
|
||||||
|
|
||||||
spring.devtools.restart.enabled=true
|
spring.devtools.restart.enabled=true
|
||||||
spring.devtools.livereload.enabled=true
|
spring.devtools.livereload.enabled=true
|
||||||
|
|
||||||
spring.thymeleaf.encoding=UTF-8
|
spring.thymeleaf.encoding=UTF-8
|
||||||
|
|
||||||
server.connection-timeout=${CONNECTION_TIMEOUT:5m}
|
server.connection-timeout=${CONNECTION_TIMEOUT:5m}
|
||||||
spring.mvc.async.request-timeout=${ASYNC_CONNECTION_TIMEOUT:300000}
|
spring.mvc.async.request-timeout=${ASYNC_CONNECTION_TIMEOUT:300000}
|
||||||
|
|
||||||
|
spring.resources.static-locations=file:customFiles/static/
|
||||||
|
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
|
||||||
|
#spring.thymeleaf.cache=false
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -20,136 +20,380 @@ close=Close
|
|||||||
filesSelected=files selected
|
filesSelected=files selected
|
||||||
noFavourites=No favourites added
|
noFavourites=No favourites added
|
||||||
bored=Bored Waiting?
|
bored=Bored Waiting?
|
||||||
|
alphabet=Alphabet
|
||||||
|
downloadPdf=Download PDF
|
||||||
|
text=Text
|
||||||
|
font=Font
|
||||||
|
selectFillter=-- Select --
|
||||||
|
pageNum=Page Number
|
||||||
|
sizes.small=Small
|
||||||
|
sizes.medium=Medium
|
||||||
|
sizes.large=Large
|
||||||
|
sizes.x-large=X-Large
|
||||||
|
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
# NAVBAR #
|
||||||
|
#############
|
||||||
|
navbar.convert=Convert
|
||||||
|
navbar.security=Security
|
||||||
|
navbar.other=Other
|
||||||
|
navbar.darkmode=Dark Mode
|
||||||
|
navbar.pageOps=Page Operations
|
||||||
|
navbar.settings=Settings
|
||||||
|
|
||||||
|
#############
|
||||||
|
# SETTINGS #
|
||||||
|
#############
|
||||||
|
settings.title=Settings
|
||||||
|
settings.update=Update available
|
||||||
|
settings.appVersion=App Version:
|
||||||
|
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||||
|
settings.downloadOption.1=Open in same window
|
||||||
|
settings.downloadOption.2=Open in new window
|
||||||
|
settings.downloadOption.3=Download file
|
||||||
|
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
home.desc=Your locally hosted one-stop-shop for all your PDF needs.
|
home.desc=Your locally hosted one-stop-shop for all your PDF needs.
|
||||||
|
|
||||||
|
|
||||||
navbar.convert=Convert
|
|
||||||
navbar.security=Security
|
|
||||||
navbar.other=Other
|
|
||||||
navbar.darkmode=Dark Mode
|
|
||||||
navbar.pageOps=Page Operations
|
|
||||||
|
|
||||||
home.multiTool.title=PDF Multi Tool
|
home.multiTool.title=PDF Multi Tool
|
||||||
home.multiTool.desc=Merge, Rotate, Rearrange, and Remove pages
|
home.multiTool.desc=Merge, Rotate, Rearrange, and Remove pages
|
||||||
|
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move
|
||||||
|
|
||||||
home.merge.title=Merge
|
home.merge.title=Merge
|
||||||
home.merge.desc=Easily merge multiple PDFs into one.
|
home.merge.desc=Easily merge multiple PDFs into one.
|
||||||
|
merge.tags=merge,Page operations,Back end,server side
|
||||||
|
|
||||||
home.split.title=Split
|
home.split.title=Split
|
||||||
home.split.desc=Split PDFs into multiple documents
|
home.split.desc=Split PDFs into multiple documents
|
||||||
|
split.tags=Page operations,divide,Multi Page,cut,server side
|
||||||
|
|
||||||
home.rotate.title=Rotate
|
home.rotate.title=Rotate
|
||||||
home.rotate.desc=Easily rotate your PDFs.
|
home.rotate.desc=Easily rotate your PDFs.
|
||||||
|
rotate.tags=server side
|
||||||
|
|
||||||
|
|
||||||
home.imageToPdf.title=Image to PDF
|
home.imageToPdf.title=Image to PDF
|
||||||
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
|
home.imageToPdf.desc=Convert a image (PNG, JPEG, GIF) to PDF.
|
||||||
|
imageToPdf.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
home.pdfToImage.title=PDF to Image
|
home.pdfToImage.title=PDF to Image
|
||||||
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
|
home.pdfToImage.desc=Convert a PDF to a image. (PNG, JPEG, GIF)
|
||||||
|
pdfToImage.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
home.pdfOrganiser.title=Organise
|
home.pdfOrganiser.title=Organise
|
||||||
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
||||||
|
pdfOrganiser.tags=duplex,even,odd,sort,move
|
||||||
|
|
||||||
|
|
||||||
home.addImage.title=Add image
|
home.addImage.title=Add image
|
||||||
home.addImage.desc=Adds a image onto a set location on the PDF
|
home.addImage.desc=Adds a image onto a set location on the PDF
|
||||||
|
addImage.tags=img,jpg,picture,photo
|
||||||
|
|
||||||
home.watermark.title=Add Watermark
|
home.watermark.title=Add Watermark
|
||||||
home.watermark.desc=Add a custom watermark to your PDF document.
|
home.watermark.desc=Add a custom watermark to your PDF document.
|
||||||
|
watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo
|
||||||
|
|
||||||
home.permissions.title=Change Permissions
|
home.permissions.title=Change Permissions
|
||||||
home.permissions.desc=Change the permissions of your PDF document
|
home.permissions.desc=Change the permissions of your PDF document
|
||||||
|
permissions.tags=read,write,edit,print
|
||||||
|
|
||||||
|
|
||||||
home.removePages.title=Remove
|
home.removePages.title=Remove
|
||||||
home.removePages.desc=Delete unwanted pages from your PDF document.
|
home.removePages.desc=Delete unwanted pages from your PDF document.
|
||||||
|
removePages.tags=Remove pages,delete pages
|
||||||
|
|
||||||
home.addPassword.title=Add Password
|
home.addPassword.title=Add Password
|
||||||
home.addPassword.desc=Encrypt your PDF document with a password.
|
home.addPassword.desc=Encrypt your PDF document with a password.
|
||||||
|
addPassword.tags=secure,security
|
||||||
|
|
||||||
home.removePassword.title=Remove Password
|
home.removePassword.title=Remove Password
|
||||||
home.removePassword.desc=Remove password protection from your PDF document.
|
home.removePassword.desc=Remove password protection from your PDF document.
|
||||||
|
removePassword.tags=secure,Decrypt,security,unpassword,delete password
|
||||||
|
|
||||||
home.compressPdfs.title=Compress
|
home.compressPdfs.title=Compress
|
||||||
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
home.compressPdfs.desc=Compress PDFs to reduce their file size.
|
||||||
|
compressPdfs.tags=squish,small,tiny
|
||||||
|
|
||||||
|
|
||||||
home.changeMetadata.title=Change Metadata
|
home.changeMetadata.title=Change Metadata
|
||||||
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
|
home.changeMetadata.desc=Change/Remove/Add metadata from a PDF document
|
||||||
|
changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats
|
||||||
|
|
||||||
home.fileToPDF.title=Convert file to PDF
|
home.fileToPDF.title=Convert file to PDF
|
||||||
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
|
home.fileToPDF.desc=Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)
|
||||||
|
fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint
|
||||||
|
|
||||||
home.ocr.title=OCR / Cleanup scans
|
home.ocr.title=OCR / Cleanup scans
|
||||||
home.ocr.desc=Cleanup scans and detects text from images within a PDF and re-adds it as text.
|
home.ocr.desc=Cleanup scans and detects text from images within a PDF and re-adds it as text.
|
||||||
|
ocr.tags=recognition,text,image,scan,read,identify,detection,editable
|
||||||
|
|
||||||
|
|
||||||
home.extractImages.title=Extract Images
|
home.extractImages.title=Extract Images
|
||||||
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
|
home.extractImages.desc=Extracts all images from a PDF and saves them to zip
|
||||||
|
extractImages.tags=picture,photo,save,archive,zip,capture,grab
|
||||||
|
|
||||||
home.pdfToPDFA.title=PDF to PDF/A
|
home.pdfToPDFA.title=PDF to PDF/A
|
||||||
home.pdfToPDFA.desc=Convert PDF to PDF/A for long-term storage
|
home.pdfToPDFA.desc=Convert PDF to PDF/A for long-term storage
|
||||||
|
pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation
|
||||||
|
|
||||||
home.PDFToWord.title=PDF to Word
|
home.PDFToWord.title=PDF to Word
|
||||||
home.PDFToWord.desc=Convert PDF to Word formats (DOC, DOCX and ODT)
|
home.PDFToWord.desc=Convert PDF to Word formats (DOC, DOCX and ODT)
|
||||||
|
PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile
|
||||||
|
|
||||||
home.PDFToPresentation.title=PDF to Presentation
|
home.PDFToPresentation.title=PDF to Presentation
|
||||||
home.PDFToPresentation.desc=Convert PDF to Presentation formats (PPT, PPTX and ODP)
|
home.PDFToPresentation.desc=Convert PDF to Presentation formats (PPT, PPTX and ODP)
|
||||||
|
PDFToPresentation.tags=slides,show,office,microsoft
|
||||||
|
|
||||||
home.PDFToText.title=PDF to Text/RTF
|
home.PDFToText.title=PDF to RTF (Text)
|
||||||
home.PDFToText.desc=Convert PDF to Text or RTF format
|
home.PDFToText.desc=Convert PDF to Text or RTF format
|
||||||
|
PDFToText.tags=richformat,richtextformat,rich text format
|
||||||
|
|
||||||
home.PDFToHTML.title=PDF to HTML
|
home.PDFToHTML.title=PDF to HTML
|
||||||
home.PDFToHTML.desc=Convert PDF to HTML format
|
home.PDFToHTML.desc=Convert PDF to HTML format
|
||||||
|
PDFToHTML.tags=web content,browser friendly
|
||||||
|
|
||||||
|
|
||||||
home.PDFToXML.title=PDF to XML
|
home.PDFToXML.title=PDF to XML
|
||||||
home.PDFToXML.desc=Convert PDF to XML format
|
home.PDFToXML.desc=Convert PDF to XML format
|
||||||
|
PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert
|
||||||
|
|
||||||
home.ScannerImageSplit.title=Detect/Split Scanned photos
|
home.ScannerImageSplit.title=Detect/Split Scanned photos
|
||||||
home.ScannerImageSplit.desc=Splits multiple photos from within a photo/PDF
|
home.ScannerImageSplit.desc=Splits multiple photos from within a photo/PDF
|
||||||
|
ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize
|
||||||
|
|
||||||
home.sign.title=Sign
|
home.sign.title=Sign
|
||||||
home.sign.desc=Adds signature to PDF by drawing, text or image
|
home.sign.desc=Adds signature to PDF by drawing, text or image
|
||||||
|
sign.tags=authorize,initials,drawn-signature,text-sign,image-signature
|
||||||
|
|
||||||
home.flatten.title=Flatten
|
home.flatten.title=Flatten
|
||||||
home.flatten.desc=Remove all interactive elements and forms from a PDF
|
home.flatten.desc=Remove all interactive elements and forms from a PDF
|
||||||
|
flatten.tags=static,deactivate,non-interactive,streamline
|
||||||
|
|
||||||
home.repair.title=Repair
|
home.repair.title=Repair
|
||||||
home.repair.desc=Tries to repair a corrupt/broken PDF
|
home.repair.desc=Tries to repair a corrupt/broken PDF
|
||||||
|
repair.tags=fix,restore,correction,recover
|
||||||
|
|
||||||
home.removeBlanks.title=Remove Blank pages
|
home.removeBlanks.title=Remove Blank pages
|
||||||
home.removeBlanks.desc=Detects and removes blank pages from a document
|
home.removeBlanks.desc=Detects and removes blank pages from a document
|
||||||
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
home.compare.title=Compare
|
home.compare.title=Compare
|
||||||
home.compare.desc=Compares and shows the differences between 2 PDF Documents
|
home.compare.desc=Compares and shows the differences between 2 PDF Documents
|
||||||
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
|
||||||
home.certSign.title=Sign with Certificate
|
home.certSign.title=Sign with Certificate
|
||||||
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
||||||
|
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||||
|
|
||||||
home.pageLayout.title=Multi-Page Layout
|
home.pageLayout.title=Multi-Page Layout
|
||||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||||
|
pageLayout.tags=merge,composite,single-view,organize
|
||||||
|
|
||||||
home.scalePages.title=Adjust page size/scale
|
home.scalePages.title=Adjust page size/scale
|
||||||
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
||||||
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
|
home.pipeline.title=Pipeline (Advanced)
|
||||||
|
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||||
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
downloadPdf=Download PDF
|
home.add-page-numbers.title=Add Page Numbers
|
||||||
text=Text
|
home.add-page-numbers.desc=Add Page numbers throughout a document in a set location
|
||||||
font=Font
|
add-page-numbers.tags=paginate,label,organize,index
|
||||||
selectFillter=-- Select --
|
|
||||||
pageNum=Page Number
|
|
||||||
|
|
||||||
|
home.auto-rename.title=Auto Rename PDF File
|
||||||
|
home.auto-rename.desc=Auto renames a PDF file based on its detected header
|
||||||
|
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||||
|
|
||||||
|
home.adjust-contrast.title=Adjust Colors/Contrast
|
||||||
|
home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF
|
||||||
|
adjust-contrast.tags=color-correction,tune,modify,enhance
|
||||||
|
|
||||||
|
home.crop.title=Crop PDF
|
||||||
|
home.crop.desc=Crop a PDF to reduce its size (maintains text!)
|
||||||
|
crop.tags=trim,shrink,edit,shape
|
||||||
|
|
||||||
|
home.autoSplitPDF.title=Auto Split Pages
|
||||||
|
home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code
|
||||||
|
autoSplitPDF.tags=QR-based,separate,scan-segment,organize
|
||||||
|
|
||||||
|
home.sanitizePdf.title=Sanitize
|
||||||
|
home.sanitizePdf.desc=Remove scripts and other elements from PDF files
|
||||||
|
sanitizePdf.tags=clean,secure,safe,remove-threats
|
||||||
|
|
||||||
|
home.URLToPDF.title=URL/Website To PDF
|
||||||
|
home.URLToPDF.desc=Converts any http(s)URL to PDF
|
||||||
|
URLToPDF.tags=web-capture,save-page,web-to-doc,archive
|
||||||
|
|
||||||
|
home.HTMLToPDF.title=HTML to PDF
|
||||||
|
home.HTMLToPDF.desc=Converts any HTML file or zip to PDF
|
||||||
|
HTMLToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
|
home.MarkdownToPDF.title=Markdown to PDF
|
||||||
|
home.MarkdownToPDF.desc=Converts any Markdown file to PDF
|
||||||
|
MarkdownToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
|
home.getPdfInfo.title=Get ALL Info on PDF
|
||||||
|
home.getPdfInfo.desc=Grabs any and all information possible on PDFs
|
||||||
|
getPdfInfo.tags=infomation,data,stats,statistics
|
||||||
|
|
||||||
|
|
||||||
|
home.extractPage.title=Extract page(s)
|
||||||
|
home.extractPage.desc=Extracts select pages from PDF
|
||||||
|
extractPage.tags=extract
|
||||||
|
|
||||||
|
|
||||||
|
home.PdfToSinglePage.title=PDF to Single Large Page
|
||||||
|
home.PdfToSinglePage.desc=Merges all PDF pages into one large single page
|
||||||
|
PdfToSinglePage.tags=single page
|
||||||
|
|
||||||
|
|
||||||
|
home.showJS.title=Show Javascript
|
||||||
|
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||||
|
showJS.tags=JS
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# #
|
||||||
|
# WEB PAGES #
|
||||||
|
# #
|
||||||
|
###########################
|
||||||
|
#showJS
|
||||||
|
showJS.title=Show Javascript
|
||||||
|
showJS.header=Show Javascript
|
||||||
|
showJS.downloadJS=Download Javascript
|
||||||
|
showJS.submit=Show
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToSinglePage
|
||||||
|
pdfToSinglePage.title=PDF To Single Page
|
||||||
|
pdfToSinglePage.header=PDF To Single Page
|
||||||
|
pdfToSinglePage.submit=Convert To Single Page
|
||||||
|
|
||||||
|
|
||||||
|
#pageExtracter
|
||||||
|
pageExtracter.title=Extract Pages
|
||||||
|
pageExtracter.header=Extract Pages
|
||||||
|
pageExtracter.submit=Extract
|
||||||
|
|
||||||
|
|
||||||
|
#getPdfInfo
|
||||||
|
getPdfInfo.title=Get Info on PDF
|
||||||
|
getPdfInfo.header=Get Info on PDF
|
||||||
|
getPdfInfo.submit=Get Info
|
||||||
|
getPdfInfo.downloadJson=Download JSON
|
||||||
|
|
||||||
|
|
||||||
|
#markdown-to-pdf
|
||||||
|
MarkdownToPDF.title=Markdown To PDF
|
||||||
|
MarkdownToPDF.header=Markdown To PDF
|
||||||
|
MarkdownToPDF.submit=Convert
|
||||||
|
MarkdownToPDF.help=Work in progress
|
||||||
|
MarkdownToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#url-to-pdf
|
||||||
|
URLToPDF.title=URL To PDF
|
||||||
|
URLToPDF.header=URL To PDF
|
||||||
|
URLToPDF.submit=Convert
|
||||||
|
URLToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#html-to-pdf
|
||||||
|
HTMLToPDF.title=HTML To PDF
|
||||||
|
HTMLToPDF.header=HTML To PDF
|
||||||
|
HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required
|
||||||
|
HTMLToPDF.submit=Convert
|
||||||
|
HTMLToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#sanitizePDF
|
||||||
|
sanitizePDF.title=Sanitize PDF
|
||||||
|
sanitizePDF.header=Sanitize a PDF file
|
||||||
|
sanitizePDF.selectText.1=Remove JavaScript actions
|
||||||
|
sanitizePDF.selectText.2=Remove embedded files
|
||||||
|
sanitizePDF.selectText.3=Remove metadata
|
||||||
|
sanitizePDF.selectText.4=Remove links
|
||||||
|
sanitizePDF.selectText.5=Remove fonts
|
||||||
|
sanitizePDF.submit=Sanitize PDF
|
||||||
|
|
||||||
|
|
||||||
|
#addPageNumbers
|
||||||
|
addPageNumbers.title=Add Page Numbers
|
||||||
|
addPageNumbers.header=Add Page Numbers
|
||||||
|
addPageNumbers.selectText.1=Select PDF file:
|
||||||
|
addPageNumbers.selectText.2=Margin Size
|
||||||
|
addPageNumbers.selectText.3=Position
|
||||||
|
addPageNumbers.selectText.4=Starting Number
|
||||||
|
addPageNumbers.selectText.5=Pages to Number
|
||||||
|
addPageNumbers.selectText.6=Custom Text
|
||||||
|
addPageNumbers.submit=Add Page Numbers
|
||||||
|
|
||||||
|
|
||||||
|
#auto-rename
|
||||||
|
auto-rename.title=Auto Rename
|
||||||
|
auto-rename.header=Auto Rename PDF
|
||||||
|
auto-rename.submit=Auto Rename
|
||||||
|
|
||||||
|
|
||||||
|
#adjustContrast
|
||||||
|
adjustContrast.title=Adjust Contrast
|
||||||
|
adjustContrast.header=Adjust Contrast
|
||||||
|
adjustContrast.contrast=Contrast:
|
||||||
|
adjustContrast.brightness=Brightness:
|
||||||
|
adjustContrast.saturation=Saturation:
|
||||||
|
adjustContrast.download=Download
|
||||||
|
|
||||||
|
|
||||||
|
#crop
|
||||||
|
crop.title=Crop
|
||||||
|
crop.header=Crop Image
|
||||||
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#autoSplitPDF
|
||||||
|
autoSplitPDF.title=Auto Split PDF
|
||||||
|
autoSplitPDF.header=Auto Split PDF
|
||||||
|
autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed.
|
||||||
|
autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine).
|
||||||
|
autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them.
|
||||||
|
autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest.
|
||||||
|
autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document.
|
||||||
|
autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers:
|
||||||
|
autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning)
|
||||||
|
autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf'
|
||||||
|
autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf'
|
||||||
|
autoSplitPDF.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#pipeline
|
||||||
|
pipeline.title=Pipeline
|
||||||
|
|
||||||
|
|
||||||
|
#pageLayout
|
||||||
pageLayout.title=Multi Page Layout
|
pageLayout.title=Multi Page Layout
|
||||||
pageLayout.header=Multi Page Layout
|
pageLayout.header=Multi Page Layout
|
||||||
pageLayout.pagesPerSheet=Pages per sheet:
|
pageLayout.pagesPerSheet=Pages per sheet:
|
||||||
pageLayout.submit=Submit
|
pageLayout.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#scalePages
|
||||||
scalePages.title=Adjust page-scale
|
scalePages.title=Adjust page-scale
|
||||||
scalePages.header=Adjust page-scale
|
scalePages.header=Adjust page-scale
|
||||||
scalePages.pageSize=Size of a page of the document.
|
scalePages.pageSize=Size of a page of the document.
|
||||||
scalePages.scaleFactor=Zoom level (crop) of a page.
|
scalePages.scaleFactor=Zoom level (crop) of a page.
|
||||||
scalePages.submit=Submit
|
scalePages.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#certSign
|
||||||
certSign.title=Certificate Signing
|
certSign.title=Certificate Signing
|
||||||
certSign.header=Sign a PDF with your certificate (Work in progress)
|
certSign.header=Sign a PDF with your certificate (Work in progress)
|
||||||
certSign.selectPDF=Select a PDF File for Signing:
|
certSign.selectPDF=Select a PDF File for Signing:
|
||||||
@@ -161,26 +405,29 @@ certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
|||||||
certSign.showSig=Show Signature
|
certSign.showSig=Show Signature
|
||||||
certSign.reason=Reason
|
certSign.reason=Reason
|
||||||
certSign.location=Location
|
certSign.location=Location
|
||||||
certSign.name=Name
|
certSign.name=Name
|
||||||
|
|
||||||
certSign.submit=Sign PDF
|
certSign.submit=Sign PDF
|
||||||
|
|
||||||
|
|
||||||
|
#removeBlanks
|
||||||
removeBlanks.title=Remove Blanks
|
removeBlanks.title=Remove Blanks
|
||||||
removeBlanks.header=Remove Blank Pages
|
removeBlanks.header=Remove Blank Pages
|
||||||
removeBlanks.threshold=Threshold:
|
removeBlanks.threshold=Pixel Whiteness Threshold:
|
||||||
removeBlanks.thresholdDesc=Threshold for determining how white a white pixel must be
|
removeBlanks.thresholdDesc=Threshold for determining how white a white pixel must be to be classed as 'White'. 0 = Black, 255 pure white.
|
||||||
removeBlanks.whitePercent=White Percent (%):
|
removeBlanks.whitePercent=White Percent (%):
|
||||||
removeBlanks.whitePercentDesc=Percent of page that must be white to be removed
|
removeBlanks.whitePercentDesc=Percent of page that must be 'white' pixels to be removed
|
||||||
removeBlanks.submit=Remove Blanks
|
removeBlanks.submit=Remove Blanks
|
||||||
|
|
||||||
|
|
||||||
|
#compare
|
||||||
compare.title=Compare
|
compare.title=Compare
|
||||||
compare.header=Compare PDFs
|
compare.header=Compare PDFs
|
||||||
compare.document.1=Document 1
|
compare.document.1=Document 1
|
||||||
compare.document.2=Document 2
|
compare.document.2=Document 2
|
||||||
compare.submit=Compare
|
compare.submit=Compare
|
||||||
|
|
||||||
|
|
||||||
|
#sign
|
||||||
sign.title=Sign
|
sign.title=Sign
|
||||||
sign.header=Sign PDFs
|
sign.header=Sign PDFs
|
||||||
sign.upload=Upload Image
|
sign.upload=Upload Image
|
||||||
@@ -189,14 +436,20 @@ sign.text=Text Input
|
|||||||
sign.clear=Clear
|
sign.clear=Clear
|
||||||
sign.add=Add
|
sign.add=Add
|
||||||
|
|
||||||
|
|
||||||
|
#repair
|
||||||
repair.title=Repair
|
repair.title=Repair
|
||||||
repair.header=Repair PDFs
|
repair.header=Repair PDFs
|
||||||
repair.submit=Repair
|
repair.submit=Repair
|
||||||
|
|
||||||
|
|
||||||
|
#flatten
|
||||||
flatten.title=Flatten
|
flatten.title=Flatten
|
||||||
flatten.header=Flatten PDFs
|
flatten.header=Flatten PDFs
|
||||||
flatten.submit=Flatten
|
flatten.submit=Flatten
|
||||||
|
|
||||||
|
|
||||||
|
#ScannerImageSplit
|
||||||
ScannerImageSplit.selectText.1=Angle Threshold:
|
ScannerImageSplit.selectText.1=Angle Threshold:
|
||||||
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).
|
ScannerImageSplit.selectText.2=Sets the minimum absolute angle required for the image to be rotated (default: 10).
|
||||||
ScannerImageSplit.selectText.3=Tolerance:
|
ScannerImageSplit.selectText.3=Tolerance:
|
||||||
@@ -208,19 +461,6 @@ ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a pho
|
|||||||
ScannerImageSplit.selectText.9=Border Size:
|
ScannerImageSplit.selectText.9=Border Size:
|
||||||
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
|
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
|
||||||
|
|
||||||
navbar.settings=Settings
|
|
||||||
settings.title=Settings
|
|
||||||
settings.update=Update available
|
|
||||||
settings.appVersion=App Version:
|
|
||||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
|
||||||
settings.downloadOption.1=Open in same window
|
|
||||||
settings.downloadOption.2=Open in new window
|
|
||||||
settings.downloadOption.3=Download file
|
|
||||||
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
ocr.title=OCR / Scan Cleanup
|
ocr.title=OCR / Scan Cleanup
|
||||||
@@ -242,7 +482,7 @@ ocr.credit=This service uses OCRmyPDF and Tesseract for OCR.
|
|||||||
ocr.submit=Process PDF with OCR
|
ocr.submit=Process PDF with OCR
|
||||||
|
|
||||||
|
|
||||||
|
#extractImages
|
||||||
extractImages.title=Extract Images
|
extractImages.title=Extract Images
|
||||||
extractImages.header=Extract Images
|
extractImages.header=Extract Images
|
||||||
extractImages.selectText=Select image format to convert extracted images to
|
extractImages.selectText=Select image format to convert extracted images to
|
||||||
@@ -273,6 +513,7 @@ compress.submit=Compress
|
|||||||
addImage.title=Add Image
|
addImage.title=Add Image
|
||||||
addImage.header=Add image to PDF
|
addImage.header=Add image to PDF
|
||||||
addImage.everyPage=Every Page?
|
addImage.everyPage=Every Page?
|
||||||
|
addImage.upload=Add image
|
||||||
addImage.submit=Add image
|
addImage.submit=Add image
|
||||||
|
|
||||||
|
|
||||||
@@ -281,11 +522,13 @@ merge.title=Merge
|
|||||||
merge.header=Merge multiple PDFs (2+)
|
merge.header=Merge multiple PDFs (2+)
|
||||||
merge.submit=Merge
|
merge.submit=Merge
|
||||||
|
|
||||||
|
|
||||||
#pdfOrganiser
|
#pdfOrganiser
|
||||||
pdfOrganiser.title=Page Organiser
|
pdfOrganiser.title=Page Organiser
|
||||||
pdfOrganiser.header=PDF Page Organiser
|
pdfOrganiser.header=PDF Page Organiser
|
||||||
pdfOrganiser.submit=Rearrange Pages
|
pdfOrganiser.submit=Rearrange Pages
|
||||||
|
|
||||||
|
|
||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title=PDF Multi Tool
|
multiTool.title=PDF Multi Tool
|
||||||
multiTool.header=PDF Multi Tool
|
multiTool.header=PDF Multi Tool
|
||||||
@@ -297,6 +540,7 @@ pageRemover.header=PDF Page remover
|
|||||||
pageRemover.pagesToDelete=Pages to delete (Enter a comma-separated list of page numbers) :
|
pageRemover.pagesToDelete=Pages to delete (Enter a comma-separated list of page numbers) :
|
||||||
pageRemover.submit=Delete Pages
|
pageRemover.submit=Delete Pages
|
||||||
|
|
||||||
|
|
||||||
#rotate
|
#rotate
|
||||||
rotate.title=Rotate PDF
|
rotate.title=Rotate PDF
|
||||||
rotate.header=Rotate PDF
|
rotate.header=Rotate PDF
|
||||||
@@ -304,8 +548,6 @@ rotate.selectAngle=Select rotation angle (in multiples of 90 degrees):
|
|||||||
rotate.submit=Rotate
|
rotate.submit=Rotate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
split.title=Split PDF
|
split.title=Split PDF
|
||||||
split.header=Split PDF
|
split.header=Split PDF
|
||||||
@@ -330,6 +572,7 @@ imageToPDF.selectText.2=Auto rotate PDF
|
|||||||
imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images)
|
imageToPDF.selectText.3=Multi file logic (Only enabled if working with multiple images)
|
||||||
imageToPDF.selectText.4=Merge into single PDF
|
imageToPDF.selectText.4=Merge into single PDF
|
||||||
imageToPDF.selectText.5=Convert to separate PDFs
|
imageToPDF.selectText.5=Convert to separate PDFs
|
||||||
|
|
||||||
|
|
||||||
#pdfToImage
|
#pdfToImage
|
||||||
pdfToImage.title=PDF to Image
|
pdfToImage.title=PDF to Image
|
||||||
@@ -344,6 +587,7 @@ pdfToImage.grey=Greyscale
|
|||||||
pdfToImage.blackwhite=Black and White (May lose data!)
|
pdfToImage.blackwhite=Black and White (May lose data!)
|
||||||
pdfToImage.submit=Convert
|
pdfToImage.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
addPassword.title=Add Password
|
addPassword.title=Add Password
|
||||||
addPassword.header=Add password (Encrypt)
|
addPassword.header=Add password (Encrypt)
|
||||||
@@ -365,6 +609,7 @@ addPassword.selectText.15=Restricts what can be done with the document once it i
|
|||||||
addPassword.selectText.16=Restricts the opening of the document itself
|
addPassword.selectText.16=Restricts the opening of the document itself
|
||||||
addPassword.submit=Encrypt
|
addPassword.submit=Encrypt
|
||||||
|
|
||||||
|
|
||||||
#watermark
|
#watermark
|
||||||
watermark.title=Add Watermark
|
watermark.title=Add Watermark
|
||||||
watermark.header=Add Watermark
|
watermark.header=Add Watermark
|
||||||
@@ -377,6 +622,7 @@ watermark.selectText.6=heightSpacer (Space between each watermark vertically):
|
|||||||
watermark.selectText.7=Opacity (0% - 100%):
|
watermark.selectText.7=Opacity (0% - 100%):
|
||||||
watermark.submit=Add Watermark
|
watermark.submit=Add Watermark
|
||||||
|
|
||||||
|
|
||||||
#remove-watermark
|
#remove-watermark
|
||||||
remove-watermark.title=Remove Watermark
|
remove-watermark.title=Remove Watermark
|
||||||
remove-watermark.header=Remove Watermark
|
remove-watermark.header=Remove Watermark
|
||||||
@@ -384,6 +630,7 @@ remove-watermark.selectText.1=Select PDF to remove watermark from:
|
|||||||
remove-watermark.selectText.2=Watermark Text:
|
remove-watermark.selectText.2=Watermark Text:
|
||||||
remove-watermark.submit=Remove Watermark
|
remove-watermark.submit=Remove Watermark
|
||||||
|
|
||||||
|
|
||||||
#Change permissions
|
#Change permissions
|
||||||
permissions.title=Change Permissions
|
permissions.title=Change Permissions
|
||||||
permissions.header=Change Permissions
|
permissions.header=Change Permissions
|
||||||
@@ -400,6 +647,7 @@ permissions.selectText.9=Prevent printing
|
|||||||
permissions.selectText.10=Prevent printing different formats
|
permissions.selectText.10=Prevent printing different formats
|
||||||
permissions.submit=Change
|
permissions.submit=Change
|
||||||
|
|
||||||
|
|
||||||
#remove password
|
#remove password
|
||||||
removePassword.title=Remove password
|
removePassword.title=Remove password
|
||||||
removePassword.header=Remove password (Decrypt)
|
removePassword.header=Remove password (Decrypt)
|
||||||
@@ -407,6 +655,8 @@ removePassword.selectText.1=Select PDF to Decrypt
|
|||||||
removePassword.selectText.2=Password
|
removePassword.selectText.2=Password
|
||||||
removePassword.submit=Remove
|
removePassword.submit=Remove
|
||||||
|
|
||||||
|
|
||||||
|
#changeMetadata
|
||||||
changeMetadata.title=Change Metadata
|
changeMetadata.title=Change Metadata
|
||||||
changeMetadata.header=Change Metadata
|
changeMetadata.header=Change Metadata
|
||||||
changeMetadata.selectText.1=Please edit the variables you wish to change
|
changeMetadata.selectText.1=Please edit the variables you wish to change
|
||||||
@@ -425,27 +675,30 @@ changeMetadata.selectText.4=Other Metadata:
|
|||||||
changeMetadata.selectText.5=Add Custom Metadata Entry
|
changeMetadata.selectText.5=Add Custom Metadata Entry
|
||||||
changeMetadata.submit=Change
|
changeMetadata.submit=Change
|
||||||
|
|
||||||
|
|
||||||
|
#xlsToPdf
|
||||||
xlsToPdf.title=Excel to PDF
|
xlsToPdf.title=Excel to PDF
|
||||||
xlsToPdf.header=Excel to PDF
|
xlsToPdf.header=Excel to PDF
|
||||||
xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert
|
xlsToPdf.selectText.1=Select XLS or XLSX Excel sheet to convert
|
||||||
xlsToPdf.convert=convert
|
xlsToPdf.convert=convert
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToPDFA
|
||||||
|
|
||||||
pdfToPDFA.title=PDF To PDF/A
|
pdfToPDFA.title=PDF To PDF/A
|
||||||
pdfToPDFA.header=PDF To PDF/A
|
pdfToPDFA.header=PDF To PDF/A
|
||||||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||||
pdfToPDFA.submit=Convert
|
pdfToPDFA.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToWord
|
||||||
PDFToWord.title=PDF to Word
|
PDFToWord.title=PDF to Word
|
||||||
PDFToWord.header=PDF to Word
|
PDFToWord.header=PDF to Word
|
||||||
PDFToWord.selectText.1=Output file format
|
PDFToWord.selectText.1=Output file format
|
||||||
PDFToWord.credit=This service uses LibreOffice for file conversion.
|
PDFToWord.credit=This service uses LibreOffice for file conversion.
|
||||||
PDFToWord.submit=Convert
|
PDFToWord.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToPresentation
|
||||||
PDFToPresentation.title=PDF to Presentation
|
PDFToPresentation.title=PDF to Presentation
|
||||||
PDFToPresentation.header=PDF to Presentation
|
PDFToPresentation.header=PDF to Presentation
|
||||||
PDFToPresentation.selectText.1=Output file format
|
PDFToPresentation.selectText.1=Output file format
|
||||||
@@ -453,31 +706,23 @@ PDFToPresentation.credit=This service uses LibreOffice for file conversion.
|
|||||||
PDFToPresentation.submit=Convert
|
PDFToPresentation.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
PDFToText.title=PDF to Text/RTF
|
#PDFToText
|
||||||
PDFToText.header=PDF to Text/RTF
|
PDFToText.title=PDF to RTF (Text)
|
||||||
|
PDFToText.header=PDF to RTF (Text)
|
||||||
PDFToText.selectText.1=Output file format
|
PDFToText.selectText.1=Output file format
|
||||||
PDFToText.credit=This service uses LibreOffice for file conversion.
|
PDFToText.credit=This service uses LibreOffice for file conversion.
|
||||||
PDFToText.submit=Convert
|
PDFToText.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToHTML
|
||||||
PDFToHTML.title=PDF to HTML
|
PDFToHTML.title=PDF to HTML
|
||||||
PDFToHTML.header=PDF to HTML
|
PDFToHTML.header=PDF to HTML
|
||||||
PDFToHTML.credit=This service uses LibreOffice for file conversion.
|
PDFToHTML.credit=This service uses LibreOffice for file conversion.
|
||||||
PDFToHTML.submit=Convert
|
PDFToHTML.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToXML
|
||||||
PDFToXML.title=PDF to XML
|
PDFToXML.title=PDF to XML
|
||||||
PDFToXML.header=PDF to XML
|
PDFToXML.header=PDF to XML
|
||||||
PDFToXML.credit=This service uses LibreOffice for file conversion.
|
PDFToXML.credit=This service uses LibreOffice for file conversion.
|
||||||
PDFToXML.submit=Convert
|
PDFToXML.submit=Convert
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
734
src/main/resources/messages_eu_ES.properties
Normal file
734
src/main/resources/messages_eu_ES.properties
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
###########
|
||||||
|
# Generic #
|
||||||
|
###########
|
||||||
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
|
language.direction=ltr
|
||||||
|
|
||||||
|
pdfPrompt=Hautatu PDFa(k)
|
||||||
|
multiPdfPrompt=Hautatu PDFak (2+)
|
||||||
|
multiPdfDropPrompt=Hautatu (edo arrastatu eta jaregin) nahi dituzun PDFak
|
||||||
|
imgPrompt=Hautatu Irudia(k)
|
||||||
|
genericSubmit=Bidali
|
||||||
|
processTimeWarning=Oharra: prozesu honetarako minutu bat ere beharko da fitxategiaren tamaiaren arabera
|
||||||
|
pageOrderPrompt=Orrialdeen ordena (sartu komaz bereizitako orrialde-zenbakien zerrenda)
|
||||||
|
goToPage=Joan
|
||||||
|
true=Egiazkoa
|
||||||
|
false=Faltsua
|
||||||
|
unknown=Ezezaguna
|
||||||
|
save=Gorde
|
||||||
|
close=Itxi
|
||||||
|
filesSelected=Hautatutako fitxategiak
|
||||||
|
noFavourites=Ez dira gogokoak gehitu
|
||||||
|
bored=Itxaroten aspertuta?
|
||||||
|
alphabet=Alfabetoa
|
||||||
|
downloadPdf=PDFa deskargatu
|
||||||
|
text=Testua
|
||||||
|
font=Letra-tipoa
|
||||||
|
selectFillter=-- Select --
|
||||||
|
pageNum=Orrialde-zenbakia
|
||||||
|
sizes.small=Small
|
||||||
|
sizes.medium=Medium
|
||||||
|
sizes.large=Large
|
||||||
|
sizes.x-large=X-Large
|
||||||
|
error.pdfPassword=PDF dokumentua pasahitzarekin babestuta dago eta pasahitza ez da sartu edo akastuna da
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
# NAVBAR #
|
||||||
|
#############
|
||||||
|
navbar.convert=Bihurtu
|
||||||
|
navbar.security=Segurtasuna
|
||||||
|
navbar.other=Beste bat
|
||||||
|
navbar.darkmode=Modu iluna
|
||||||
|
navbar.pageOps=Orrialde-eragiketak
|
||||||
|
navbar.settings=Ezarpenak
|
||||||
|
|
||||||
|
#############
|
||||||
|
# SETTINGS #
|
||||||
|
#############
|
||||||
|
settings.title=Ezarpenak
|
||||||
|
settings.update=Eguneratze eskuragarria
|
||||||
|
settings.appVersion=Aplikazioaren bertsioa:
|
||||||
|
settings.downloadOption.title=Hautatu deskargatzeko aukera (fitxategi bakarra deskargatzeko ZIP gabe):
|
||||||
|
settings.downloadOption.1=Ireki leiho berean
|
||||||
|
settings.downloadOption.2=Ireki leiho berrian
|
||||||
|
settings.downloadOption.3=Deskargatu fitxategia
|
||||||
|
settings.zipThreshold=ZIP fitxategiak deskargatutako fitxategi kopurua gainditzen denean
|
||||||
|
|
||||||
|
#############
|
||||||
|
# HOME-PAGE #
|
||||||
|
#############
|
||||||
|
home.desc=Zure leihatila bakarra autoostatatua zure PDF behar guztietarako
|
||||||
|
|
||||||
|
|
||||||
|
home.multiTool.title=Erabilera anitzeko tresna PDF
|
||||||
|
home.multiTool.desc=Orriak konbinatu, biratu, berrantolatu eta ezabatu
|
||||||
|
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side
|
||||||
|
|
||||||
|
home.merge.title=Elkartu
|
||||||
|
home.merge.desc=Elkartu zenbait PDF dokumentu bakar batean modu errazean
|
||||||
|
merge.tags=merge,Page operations,Back end,server side
|
||||||
|
|
||||||
|
home.split.title=Zatitu
|
||||||
|
home.split.desc=Zatitu PDFak zenbait dokumentutan
|
||||||
|
split.tags=Page operations,divide,Multi Page,cut,server side
|
||||||
|
|
||||||
|
home.rotate.title=Biratu
|
||||||
|
home.rotate.desc=Biratu PDFak modu errazean
|
||||||
|
rotate.tags=server side
|
||||||
|
|
||||||
|
|
||||||
|
home.imageToPdf.title=Irudia PDF bihurtu
|
||||||
|
home.imageToPdf.desc=Irudi bat(PNG, JPEG, GIF)PDF bihurtu
|
||||||
|
imageToPdf.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.pdfToImage.title=PDFa irudi bihurtu
|
||||||
|
home.pdfToImage.desc=PDF bat irudi (PNG, JPEG, GIF) bihurtu
|
||||||
|
pdfToImage.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.pdfOrganiser.title=Antolatzailea
|
||||||
|
home.pdfOrganiser.desc=Ezabatu/Berrantolatu orrialdeak edozein ordenatan
|
||||||
|
pdfOrganiser.tags=duplex,even,odd,sort,move
|
||||||
|
|
||||||
|
|
||||||
|
home.addImage.title=Gehitu irudia PDFari
|
||||||
|
home.addImage.desc=Gehitu irudi bat PDFan ezarritako kokaleku batean (lanean)
|
||||||
|
addImage.tags=img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.watermark.title=Gehitu ur-marka
|
||||||
|
home.watermark.desc=Gehitu aurrez zehaztutako ur-marka bat PFD dokumentuari
|
||||||
|
watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.permissions.title=Aldatu baimenak
|
||||||
|
home.permissions.desc=Aldatu PDF dokumentuaren baimenak
|
||||||
|
permissions.tags=read,write,edit,print
|
||||||
|
|
||||||
|
|
||||||
|
home.removePages.title=Ezabatu
|
||||||
|
home.removePages.desc=Ezabatu nahi ez dituzun orrialdeak PDF dokumentutik
|
||||||
|
removePages.tags=Remove pages,delete pages
|
||||||
|
|
||||||
|
home.addPassword.title=Gehitu pasahitza
|
||||||
|
home.addPassword.desc=Enkriptatu PDF dokumentua pasahitz batekin
|
||||||
|
addPassword.tags=secure,security
|
||||||
|
|
||||||
|
home.removePassword.title=Ezabatu pasahitza
|
||||||
|
home.removePassword.desc=Ezabatu pasahitza PDF dokumentutik
|
||||||
|
removePassword.tags=secure,Decrypt,security,unpassword,delete password
|
||||||
|
|
||||||
|
home.compressPdfs.title=Konprimatu
|
||||||
|
home.compressPdfs.desc=Konprimatu PDFak fitxategiaren tamaina murrizteko
|
||||||
|
compressPdfs.tags=squish,small,tiny
|
||||||
|
|
||||||
|
|
||||||
|
home.changeMetadata.title=Aldatu metadatuak
|
||||||
|
home.changeMetadata.desc=Aldatu/Ezabatu/Gehitu metadatuak PDF dokumentuari
|
||||||
|
changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats
|
||||||
|
|
||||||
|
home.fileToPDF.title=Fitxategia PDF bihurtu
|
||||||
|
home.fileToPDF.desc=PDF bihurtu ia edozein fitxategi (DOCX, PNG, XLS, PPT, TXT eta gehiago)
|
||||||
|
fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint
|
||||||
|
|
||||||
|
home.ocr.title=OCR exekutatu PDFan eta/edo garbiketa-eskaneatzeak
|
||||||
|
home.ocr.desc=Garbiketa-eskaneatzeak eta irudi-testuak detektatu PDF baten barruan eta berriz ere gehitu testu gisa
|
||||||
|
ocr.tags=recognition,text,image,scan,read,identify,detection,editable
|
||||||
|
|
||||||
|
|
||||||
|
home.extractImages.title=Atera irudiak
|
||||||
|
home.extractImages.desc=Atera irudi guztiak PDF batetik eta ZIPen gorde
|
||||||
|
extractImages.tags=picture,photo,save,archive,zip,capture,grab
|
||||||
|
|
||||||
|
home.pdfToPDFA.title=PDFa PDF/A bihurtu
|
||||||
|
home.pdfToPDFA.desc=PDFa PDF/A bihurtu luzaro biltegiratzeko
|
||||||
|
pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation
|
||||||
|
|
||||||
|
home.PDFToWord.title=PDFa Word Bihurtu
|
||||||
|
home.PDFToWord.desc=PDF formatuak Word bihurtu (DOC, DOCX y ODT)
|
||||||
|
PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile
|
||||||
|
|
||||||
|
home.PDFToPresentation.title=PDFa aurkezpen bihurtu
|
||||||
|
home.PDFToPresentation.desc=PDFa aurkezpen formatu bihurtu (PPT, PPTX y ODP)
|
||||||
|
PDFToPresentation.tags=slides,show,office,microsoft
|
||||||
|
|
||||||
|
home.PDFToText.title=PDFa TXT edo RTF bihurtu
|
||||||
|
home.PDFToText.desc=PDFa TXT edo RTF formatu bihurtu
|
||||||
|
PDFToText.tags=richformat,richtextformat,rich text format
|
||||||
|
|
||||||
|
home.PDFToHTML.title=PDFa HTML bihurtu
|
||||||
|
home.PDFToHTML.desc=PDFa HTML formatu bihurtu
|
||||||
|
PDFToHTML.tags=web content,browser friendly
|
||||||
|
|
||||||
|
|
||||||
|
home.PDFToXML.title=PDFa XML bihurtu
|
||||||
|
home.PDFToXML.desc=PDFa XML formatu bihurtu
|
||||||
|
PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert
|
||||||
|
|
||||||
|
home.ScannerImageSplit.title=Detektatu/Zatitu argazki eskaneatuak
|
||||||
|
home.ScannerImageSplit.desc=Hainbat argazki zatitu argazki/PDF baten barruan
|
||||||
|
ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize
|
||||||
|
|
||||||
|
home.sign.title=Sinatu
|
||||||
|
home.sign.desc=Gehitu sinadura PDFari marrazki, testu edo irudi bidez
|
||||||
|
sign.tags=authorize,initials,drawn-signature,text-sign,image-signature
|
||||||
|
|
||||||
|
home.flatten.title=Lautu
|
||||||
|
home.flatten.desc=PDF batetik elementu eta inprimaki interaktibo guztiak ezabatu
|
||||||
|
flatten.tags=static,deactivate,non-interactive,streamline
|
||||||
|
|
||||||
|
home.repair.title=Konpondu
|
||||||
|
home.repair.desc=Saiatu PDF hondatu/kaltetu bat konpontzen
|
||||||
|
repair.tags=fix,restore,correction,recover
|
||||||
|
|
||||||
|
home.removeBlanks.title=Ezabatu orrialde zuriak
|
||||||
|
home.removeBlanks.desc=Detektatu orrialde zuriak eta dokumentutik ezabatu
|
||||||
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.compare.title=Konparatu
|
||||||
|
home.compare.desc=Konparatu eta erakutsi 2 PDF dokumenturen aldeak
|
||||||
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
|
||||||
|
home.certSign.title=Sinatu ziurtagiriarekin
|
||||||
|
home.certSign.desc=Sinatu PDF bat Ziurtagiri/Gako batekin (PEM/P12)
|
||||||
|
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
home.scalePages.title=Eskalatu/Doitu orrialdearen tamaina
|
||||||
|
home.scalePages.desc=Eskalatu/Aldatu orrialde baten tamaina eta/edo edukia
|
||||||
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
|
home.pipeline.title=Pipeline (Advanced)
|
||||||
|
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||||
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
|
home.add-page-numbers.title=Add Page Numbers
|
||||||
|
home.add-page-numbers.desc=Add Page numbers throughout a document in a set location
|
||||||
|
add-page-numbers.tags=paginate,label,organize,index
|
||||||
|
|
||||||
|
home.auto-rename.title=Auto Rename PDF File
|
||||||
|
home.auto-rename.desc=Auto renames a PDF file based on its detected header
|
||||||
|
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||||
|
|
||||||
|
home.adjust-contrast.title=Adjust Colors/Contrast
|
||||||
|
home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF
|
||||||
|
adjust-contrast.tags=color-correction,tune,modify,enhance
|
||||||
|
|
||||||
|
home.crop.title=Crop PDF
|
||||||
|
home.crop.desc=Crop a PDF to reduce its size (maintains text!)
|
||||||
|
crop.tags=trim,shrink,edit,shape
|
||||||
|
|
||||||
|
home.autoSplitPDF.title=Auto Split Pages
|
||||||
|
home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code
|
||||||
|
autoSplitPDF.tags=QR-based,separate,scan-segment,organize
|
||||||
|
|
||||||
|
home.sanitizePdf.title=Sanitize
|
||||||
|
home.sanitizePdf.desc=Remove scripts and other elements from PDF files
|
||||||
|
sanitizePdf.tags=clean,secure,safe,remove-threats
|
||||||
|
|
||||||
|
home.URLToPDF.title=URL/Website To PDF
|
||||||
|
home.URLToPDF.desc=Converts any http(s)URL to PDF
|
||||||
|
URLToPDF.tags=web-capture,save-page,web-to-doc,archive
|
||||||
|
|
||||||
|
home.HTMLToPDF.title=HTML to PDF
|
||||||
|
home.HTMLToPDF.desc=Converts any HTML file or zip to PDF
|
||||||
|
HTMLToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
|
home.MarkdownToPDF.title=Markdown to PDF
|
||||||
|
home.MarkdownToPDF.desc=Converts any Markdown file to PDF
|
||||||
|
MarkdownToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
|
home.getPdfInfo.title=Get ALL Info on PDF
|
||||||
|
home.getPdfInfo.desc=Grabs any and all information possible on PDFs
|
||||||
|
getPdfInfo.tags=infomation,data,stats,statistics
|
||||||
|
|
||||||
|
|
||||||
|
home.extractPage.title=Extract page(s)
|
||||||
|
home.extractPage.desc=Extracts select pages from PDF
|
||||||
|
extractPage.tags=extract
|
||||||
|
|
||||||
|
|
||||||
|
home.PdfToSinglePage.title=PDF to Single Large Page
|
||||||
|
home.PdfToSinglePage.desc=Merges all PDF pages into one large single page
|
||||||
|
PdfToSinglePage.tags=single page
|
||||||
|
|
||||||
|
|
||||||
|
##########################
|
||||||
|
### TODO: Translate ###
|
||||||
|
##########################
|
||||||
|
home.showJS.title=Show Javascript
|
||||||
|
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||||
|
showJS.tags=JS
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# #
|
||||||
|
# WEB PAGES #
|
||||||
|
# #
|
||||||
|
###########################
|
||||||
|
#showJS
|
||||||
|
##########################
|
||||||
|
### TODO: Translate ###
|
||||||
|
##########################
|
||||||
|
showJS.title=Show Javascript
|
||||||
|
showJS.header=Show Javascript
|
||||||
|
showJS.downloadJS=Download Javascript
|
||||||
|
showJS.submit=Show
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToSinglePage
|
||||||
|
pdfToSinglePage.title=PDF To Single Page
|
||||||
|
pdfToSinglePage.header=PDF To Single Page
|
||||||
|
pdfToSinglePage.submit=Convert To Single Page
|
||||||
|
|
||||||
|
|
||||||
|
#pageExtracter
|
||||||
|
pageExtracter.title=Extract Pages
|
||||||
|
pageExtracter.header=Extract Pages
|
||||||
|
pageExtracter.submit=Extract
|
||||||
|
|
||||||
|
|
||||||
|
#getPdfInfo
|
||||||
|
getPdfInfo.title=Get Info on PDF
|
||||||
|
getPdfInfo.header=Get Info on PDF
|
||||||
|
getPdfInfo.submit=Get Info
|
||||||
|
getPdfInfo.downloadJson=Download JSON
|
||||||
|
|
||||||
|
|
||||||
|
#markdown-to-pdf
|
||||||
|
MarkdownToPDF.title=Markdown To PDF
|
||||||
|
MarkdownToPDF.header=Markdown To PDF
|
||||||
|
MarkdownToPDF.submit=Convert
|
||||||
|
MarkdownToPDF.help=Work in progress
|
||||||
|
MarkdownToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#url-to-pdf
|
||||||
|
URLToPDF.title=URL To PDF
|
||||||
|
URLToPDF.header=URL To PDF
|
||||||
|
URLToPDF.submit=Convert
|
||||||
|
URLToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#html-to-pdf
|
||||||
|
HTMLToPDF.title=HTML To PDF
|
||||||
|
HTMLToPDF.header=HTML To PDF
|
||||||
|
HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required
|
||||||
|
HTMLToPDF.submit=Convert
|
||||||
|
HTMLToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#sanitizePDF
|
||||||
|
sanitizePDF.title=Sanitize PDF
|
||||||
|
sanitizePDF.header=Sanitize a PDF file
|
||||||
|
sanitizePDF.selectText.1=Remove JavaScript actions
|
||||||
|
sanitizePDF.selectText.2=Remove embedded files
|
||||||
|
sanitizePDF.selectText.3=Remove metadata
|
||||||
|
sanitizePDF.selectText.4=Remove links
|
||||||
|
sanitizePDF.selectText.5=Remove fonts
|
||||||
|
sanitizePDF.submit=Sanitize PDF
|
||||||
|
|
||||||
|
|
||||||
|
#addPageNumbers
|
||||||
|
addPageNumbers.title=Add Page Numbers
|
||||||
|
addPageNumbers.header=Add Page Numbers
|
||||||
|
addPageNumbers.selectText.1=Select PDF file:
|
||||||
|
addPageNumbers.selectText.2=Margin Size
|
||||||
|
addPageNumbers.selectText.3=Position
|
||||||
|
addPageNumbers.selectText.4=Starting Number
|
||||||
|
addPageNumbers.selectText.5=Pages to Number
|
||||||
|
addPageNumbers.selectText.6=Custom Text
|
||||||
|
addPageNumbers.submit=Add Page Numbers
|
||||||
|
|
||||||
|
|
||||||
|
#auto-rename
|
||||||
|
auto-rename.title=Auto Rename
|
||||||
|
auto-rename.header=Auto Rename PDF
|
||||||
|
auto-rename.submit=Auto Rename
|
||||||
|
|
||||||
|
|
||||||
|
#adjustContrast
|
||||||
|
adjustContrast.title=Adjust Contrast
|
||||||
|
adjustContrast.header=Adjust Contrast
|
||||||
|
adjustContrast.contrast=Contrast:
|
||||||
|
adjustContrast.brightness=Brightness:
|
||||||
|
adjustContrast.saturation=Saturation:
|
||||||
|
adjustContrast.download=Download
|
||||||
|
|
||||||
|
|
||||||
|
#crop
|
||||||
|
crop.title=Crop
|
||||||
|
crop.header=Crop Image
|
||||||
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#autoSplitPDF
|
||||||
|
autoSplitPDF.title=Auto Split PDF
|
||||||
|
autoSplitPDF.header=Auto Split PDF
|
||||||
|
autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed.
|
||||||
|
autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine).
|
||||||
|
autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them.
|
||||||
|
autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest.
|
||||||
|
autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document.
|
||||||
|
autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers:
|
||||||
|
autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning)
|
||||||
|
autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf'
|
||||||
|
autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf'
|
||||||
|
autoSplitPDF.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#pipeline
|
||||||
|
pipeline.title=Pipeline
|
||||||
|
|
||||||
|
|
||||||
|
#pageLayout
|
||||||
|
pageLayout.title=Hainbat orrialderen diseinua
|
||||||
|
pageLayout.header=Hainbat orrialderen diseinua
|
||||||
|
pageLayout.pagesPerSheet=Orrialdeak orriko:
|
||||||
|
pageLayout.submit=Entregatu
|
||||||
|
|
||||||
|
|
||||||
|
#scalePages
|
||||||
|
scalePages.title=Doitu orrialdearen eskala
|
||||||
|
scalePages.header=Doitu orrialdearen eskala
|
||||||
|
scalePages.pageSize=Dokumentuaren orrialdearen tamaina
|
||||||
|
scalePages.scaleFactor=Orriaren zoom maila (moztea)
|
||||||
|
scalePages.submit=Entregatu
|
||||||
|
|
||||||
|
|
||||||
|
#certSign
|
||||||
|
certSign.title=Ziurtagiriaren sinadura
|
||||||
|
certSign.header=Sinatu PDF bat haren ziurtagiriarekin (lanean)
|
||||||
|
certSign.selectPDF=Hautatu PDF fitxategi bat sinatzeko:
|
||||||
|
certSign.selectKey=Hautatu gako pribatuko fitxategia (PKCS#8 formatua, .pem edo .der izan liteke):
|
||||||
|
certSign.selectCert=Hautatu ziurtagiridun fitxategia (X.509 formatua, .pem edo .der izan liteke):
|
||||||
|
certSign.selectP12=Hautatu gakoak gordetzeko fitxategia PKCS#12 (.p12 o .pfx) (Aukerakoa, ematen bada, gako pribatua eta ziurtagiria izan beharko ditu):
|
||||||
|
certSign.certType=Ziurtagiri-mota
|
||||||
|
certSign.password=Sartu zure gakoen biltegia edo gako pribatuko pasahitza (hala badagokio):
|
||||||
|
certSign.showSig=Erakutsi sinadura
|
||||||
|
certSign.reason=Arrazoia
|
||||||
|
certSign.location=Kokalekua
|
||||||
|
certSign.name=Izena
|
||||||
|
certSign.submit=Sinatu PDFa
|
||||||
|
|
||||||
|
|
||||||
|
#removeBlanks
|
||||||
|
removeBlanks.title=Ezabatu zuriuneak
|
||||||
|
removeBlanks.header=Ezabatu orrialde zuriak
|
||||||
|
removeBlanks.threshold=Gutxieneko balioa:
|
||||||
|
removeBlanks.thresholdDesc=Pixel bat zeinen zuri izan behar den ezartzeko gutxieneko balioa
|
||||||
|
removeBlanks.whitePercent=Zuriaren protzentajea (%):
|
||||||
|
removeBlanks.whitePercentDesc=Zuria izan behar den orriaren ehunekoa ezabatua izan dadin
|
||||||
|
removeBlanks.submit=Ezabatu zuriuneak
|
||||||
|
|
||||||
|
|
||||||
|
#compare
|
||||||
|
compare.title=Konparatu
|
||||||
|
compare.header=Konparatu PDF fitxategiak
|
||||||
|
compare.document.1=1. dokumentua
|
||||||
|
compare.document.2=2. dokumentua
|
||||||
|
compare.submit=Konparatu
|
||||||
|
|
||||||
|
|
||||||
|
#sign
|
||||||
|
sign.title=Sinatu
|
||||||
|
sign.header=Sinatu PDF fitxategiak
|
||||||
|
sign.upload=Igo irudia
|
||||||
|
sign.draw=Marraztu sinadura
|
||||||
|
sign.text=Testua sartzea
|
||||||
|
sign.clear=Garbitu
|
||||||
|
sign.add=Gehitu
|
||||||
|
|
||||||
|
|
||||||
|
#repair
|
||||||
|
repair.title=Konpondu
|
||||||
|
repair.header=Konpondu PDF fitxategiak
|
||||||
|
repair.submit=Konpondu
|
||||||
|
|
||||||
|
|
||||||
|
#flatten
|
||||||
|
flatten.title=Lautu
|
||||||
|
flatten.header=Akoplatu PDF fitxategiak
|
||||||
|
flatten.submit=Lautu
|
||||||
|
|
||||||
|
|
||||||
|
#ScannerImageSplit
|
||||||
|
ScannerImageSplit.selectText.1=Angeluaren gutxieneko balioa:
|
||||||
|
ScannerImageSplit.selectText.2=Ezarri eskatutako gutxieneko angelu absolutua irudia biratzeko (lehenetsia: 10).
|
||||||
|
ScannerImageSplit.selectText.3=Tolerantzia:
|
||||||
|
ScannerImageSplit.selectText.4=Ezarri kalkulatutako atzeko kolorearen inguruko kolorearen aldakuntza tartea (lehenetsia: 30).
|
||||||
|
ScannerImageSplit.selectText.5=Gutxieneko area:
|
||||||
|
ScannerImageSplit.selectText.6=Ezarri arearen gutxieneko balioa argazki batentzat (lehenetsia: 10000).
|
||||||
|
ScannerImageSplit.selectText.7=Inguruko area gutxienekoa:
|
||||||
|
ScannerImageSplit.selectText.8=Ezarri inguruko arearen gutxieneko balioa argazki batentzat
|
||||||
|
ScannerImageSplit.selectText.9=Ertzaren tamaina:
|
||||||
|
ScannerImageSplit.selectText.10=Ezarri gehitutako eta ezabatutako ertzaren tamaina irteeran ertz zuriak saihesteko (lehenetsia: 1).
|
||||||
|
|
||||||
|
|
||||||
|
#OCR
|
||||||
|
ocr.title=OCR / Garbiketa-eskaneatzea
|
||||||
|
ocr.header=Garbiketa-eskaneatzea / OCR (Karaktere-ezagutze optikoa)
|
||||||
|
ocr.selectText.1=Hautatu PDFan detektatuko diren hizkuntzak (zerrendatutakoak gaur egun detektatzen dituenak dira):
|
||||||
|
ocr.selectText.2=Sortu OCR testua duen testu-fitxategi bat OCR-ren bidez editatutako PDFarekin batera
|
||||||
|
ocr.selectText.3=Zuzendu angelu okertu batean eskaneatu ziren orrialdeak berriro beren lekura biratuta
|
||||||
|
ocr.selectText.4=Garbitu orrialdea OCRk hondoko zaratan testua aurkitzeko probabilitate txikiagoa izan dezan (Irteeran aldatu gabe)
|
||||||
|
ocr.selectText.5=Garbitu orrialdea OCRk hondoko zaratan testua aurkitzeko probabilitate txikiagoa izan dezan, irteeran garbi mantentzen du.
|
||||||
|
ocr.selectText.6=Alde batera utzi testu interaktiboa duten orrialdeak, bakarrik irudi diren OCR orrialdeak
|
||||||
|
ocr.selectText.7=OCR behartu, OCRk orrialde bakoitzean jatorrizko testu guztia ezabatuko du
|
||||||
|
ocr.selectText.8=Normala (Errorea gertatuko da PDFak testua baldin badu)
|
||||||
|
ocr.selectText.9=Ezarpen gehigarriak
|
||||||
|
ocr.selectText.10=OCR modua
|
||||||
|
ocr.selectText.11=Irudiak ezabatu OCR-ren ondoren (Irudi GUZTIAK ezabatzen ditu, bakarrik da erabilgarri bihurketa urratsaren parte baldin bada)
|
||||||
|
ocr.selectText.12=Prozesaketa-mota (aurreratua)
|
||||||
|
ocr.help=Irakurri honen erabilerari buruzko dokumentazioa beste hizkuntza batzuetarako eta/edo ez erabili Docker-en
|
||||||
|
ocr.credit=Zerbitzu honek OCRmyPDF eta OCR-rako Tesseract erabiltzen ditu
|
||||||
|
ocr.submit=PDF prozesatu OCR-rekin
|
||||||
|
|
||||||
|
|
||||||
|
#extractImages
|
||||||
|
extractImages.title=Atera irudiak
|
||||||
|
extractImages.header=Atera irudiak
|
||||||
|
extractImages.selectText=Hautatu irudi-formatua ateratako irudiak bihurtzeko
|
||||||
|
extractImages.submit=Atera
|
||||||
|
|
||||||
|
|
||||||
|
#File to PDF
|
||||||
|
fileToPDF.title=Fitxategia PDF bihurtu
|
||||||
|
fileToPDF.header=Edozein fitxategi PDF bihurtu
|
||||||
|
fileToPDF.credit=Zerbitzu honek LibreOffice eta Unoconv erabiltzen ditu fitxategiak bihurtzeko
|
||||||
|
fileToPDF.supportedFileTypes=Jasandako fitxategi-motek behekoak barne hartu behar dituzte; hala ere, jasandako formatuen zerrenda osoa eta eguneratua izateko, kontsultatu, mesedez, LibreOffice-en dokumentazioa
|
||||||
|
fileToPDF.submit=PDF bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#compress
|
||||||
|
compress.title=Konprimatu
|
||||||
|
compress.header=PDFa konprimatu
|
||||||
|
compress.credit=Zerbitzu honek Ghostscript erabiltzen du PDFak komprimatzeko/optimizatzeko
|
||||||
|
compress.selectText.1=Eskuz 1etik 4ra
|
||||||
|
compress.selectText.2=Optimizazio maila:
|
||||||
|
compress.selectText.3=4 (Izugarria testu-irudietarako)
|
||||||
|
compress.selectText.4=Automatikoa: automatikoki egokitzen du kalitatea PDFak tamaina doi-doia izan dezan
|
||||||
|
compress.selectText.5=PDFaren espero den tamaina (adibidez, 25 MB, 10.8 MB, 25 KB)
|
||||||
|
compress.submit=Konprimatu
|
||||||
|
|
||||||
|
|
||||||
|
#Add image
|
||||||
|
addImage.title=Gehitu irudia
|
||||||
|
addImage.header=Gehitu PDF-irudia
|
||||||
|
addImage.everyPage=Orrialde guztiak?
|
||||||
|
addImage.upload=Gehitu irudia
|
||||||
|
addImage.submit=Gehitu irudia
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
merge.title=Elkartu
|
||||||
|
merge.header=Elkartu zenbait PDF (2+)
|
||||||
|
merge.submit=Elkartu
|
||||||
|
|
||||||
|
|
||||||
|
#pdfOrganiser
|
||||||
|
pdfOrganiser.title=Orrialdeen antolatzailea
|
||||||
|
pdfOrganiser.header=PDF orrialdeen antolatzailea
|
||||||
|
pdfOrganiser.submit=Antolatu orrialdeak
|
||||||
|
|
||||||
|
|
||||||
|
#multiTool
|
||||||
|
multiTool.title=PDF erabilera anitzeko tresna
|
||||||
|
multiTool.header=PDF erabilera anitzeko tresna
|
||||||
|
|
||||||
|
|
||||||
|
#pageRemover
|
||||||
|
pageRemover.title=Orrialdeen ezabatzailea
|
||||||
|
pageRemover.header=PDF orrialdeen ezabatzailea
|
||||||
|
pageRemover.pagesToDelete=Ezabatu beharreko orrialdeak (sartu komaz bereizitako orrialde-zenbakien zerrenda):
|
||||||
|
pageRemover.submit=Ezabatu orrialdeak
|
||||||
|
|
||||||
|
|
||||||
|
#rotate
|
||||||
|
rotate.title=Biratu PDFa
|
||||||
|
rotate.header=Biratu PDFa
|
||||||
|
rotate.selectAngle=Select rotation angle (in multiples of 90 degrees):
|
||||||
|
rotate.submit=Biratu
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
split.title=Zatitu PDFa
|
||||||
|
split.header=Zatitu PDFa
|
||||||
|
split.desc.1=Hautatzen dituzun zenbakiak zatiketa egin nahi duzun orrialde-zenbakiak dira
|
||||||
|
split.desc.2=Beraz, 1,3,7-8 hautatzean 10 orrialdeko dokumentua zatituko luke 6 PDF fitxategi bereizituetan
|
||||||
|
split.desc.3=#1 Dokumentua: 1. orrialdea
|
||||||
|
split.desc.4=#2 Dokumentua: 2. eta 3. orrialdeak
|
||||||
|
split.desc.5=#3 Dokumentua: 4., 5. eta 6. orrialdeak
|
||||||
|
split.desc.6=#4 Dokumentua: 7. orrialdea
|
||||||
|
split.desc.7=#5 Dokumentua: 8. orrialdea
|
||||||
|
split.desc.8=#6 Dokumentua: 9. eta 10. orrialdeak
|
||||||
|
split.splitPages=Sartu orrialdeak zatitzeko:
|
||||||
|
split.submit=Zatitu
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
imageToPDF.title=Irudia PDF bihurtu
|
||||||
|
imageToPDF.header=Irudia PDF bihurtu
|
||||||
|
imageToPDF.submit=Bihurtu
|
||||||
|
imageToPDF.selectText.1=Zabaldu doitzeko
|
||||||
|
imageToPDF.selectText.2=PDFaren errotazio automatikoa
|
||||||
|
imageToPDF.selectText.3=Fitxategi askoren logika (gaituta bakarrik zenbait irudirekin ari denean)
|
||||||
|
imageToPDF.selectText.4=Elkartu PDF bakar batean
|
||||||
|
imageToPDF.selectText.5=Bihurtu eta PDF bereizituak sortu
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToImage
|
||||||
|
pdfToImage.title=PDFa irudi bihurtu
|
||||||
|
pdfToImage.header=PDFa irudi bihurtu
|
||||||
|
pdfToImage.selectText=Irudi-formatua
|
||||||
|
pdfToImage.singleOrMultiple=Ondoriozko irudi-mota
|
||||||
|
pdfToImage.single=Irudi handi bakarra
|
||||||
|
pdfToImage.multi=Zenbait irudi
|
||||||
|
pdfToImage.colorType=Kolore-mota
|
||||||
|
pdfToImage.color=Kolorea
|
||||||
|
pdfToImage.grey=Gris-eskala
|
||||||
|
pdfToImage.blackwhite=Zuria eta Beltza (Datuak galdu ditzake!)
|
||||||
|
pdfToImage.submit=Bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#addPassword
|
||||||
|
addPassword.title=Gehitu pasahitza
|
||||||
|
addPassword.header=Gehitu pasahitza (enkriptatu)
|
||||||
|
addPassword.selectText.1=Hautatu PDFa enkriptatzeko
|
||||||
|
addPassword.selectText.2=Pasahitza
|
||||||
|
addPassword.selectText.3=Gakoaren luzera
|
||||||
|
addPassword.selectText.4=Balio altuak sendoagoak dira, baina balio baxuek bateragarritasun hobea dute
|
||||||
|
addPassword.selectText.5=Ezartzeko baimenak
|
||||||
|
addPassword.selectText.6=Galarazi dokumentuaren mihiztaketa
|
||||||
|
addPassword.selectText.7=Galarazi edukia ateratzea
|
||||||
|
addPassword.selectText.8=Galarazi ateratzea irisgarritasunerako
|
||||||
|
addPassword.selectText.9=Galarazi inprimakia betetzea
|
||||||
|
addPassword.selectText.10=Galarazi aldaketak egitea
|
||||||
|
addPassword.selectText.11=Galarazi oharrak aldatzea
|
||||||
|
addPassword.selectText.12=Galarazi inprimatzea
|
||||||
|
addPassword.selectText.13=Galarazi zenbait formatu inprimatzea
|
||||||
|
addPassword.selectText.14=Pasahitza
|
||||||
|
addPassword.selectText.15=Mugatu zer egin daitekeen dokumentuarekin behin zabalduta (Irakurle guztiek onartu gabe)
|
||||||
|
addPassword.selectText.16=Mugatu dokumentu bera zabaltzeko aukera
|
||||||
|
addPassword.submit=Enkriptatu
|
||||||
|
|
||||||
|
|
||||||
|
#watermark
|
||||||
|
watermark.title=Gehitu ur-marka
|
||||||
|
watermark.header=Gehitu ur-marka
|
||||||
|
watermark.selectText.1=Hautatu PDFa ur-marka gehitzeko:
|
||||||
|
watermark.selectText.2=Ur-markaren testua:
|
||||||
|
watermark.selectText.3=Letra-tipoaren tamaina:
|
||||||
|
watermark.selectText.4=Errotazioa (0-360):
|
||||||
|
watermark.selectText.5=Zabalera (ur-marka bakoitzaren arteko espazioa horizontalean):
|
||||||
|
watermark.selectText.6=Altuera (ur-marka bakoitzaren arteko espazioa bertikalean):
|
||||||
|
watermark.selectText.7=Opakutasuna (0% - 100%):
|
||||||
|
watermark.submit=Gehitu ur-marka
|
||||||
|
|
||||||
|
|
||||||
|
#remove-watermark
|
||||||
|
remove-watermark.title=Ezabatu ur-marka
|
||||||
|
remove-watermark.header=Ezabatu ur-marka
|
||||||
|
remove-watermark.selectText.1=Hautatu PDFa ur-marka ezabatzeko:
|
||||||
|
remove-watermark.selectText.2=Ur-markaren testua:
|
||||||
|
remove-watermark.submit=Ezabatu ur-marka
|
||||||
|
|
||||||
|
|
||||||
|
#Change permissions
|
||||||
|
permissions.title=Aldatu baimenak
|
||||||
|
permissions.header=Aldatu baimenak
|
||||||
|
permissions.warning=Oharra: baimen hauek aldatzea ezinezkoa izan dadin, gomendatzen da pasahitz batekin konfiguratzea pasahitza aldatzeko orriaren bitartez
|
||||||
|
permissions.selectText.1=Hautatu PDFa baimenak aldatzeko
|
||||||
|
permissions.selectText.2=Baimenak, ezarri beharrekoak
|
||||||
|
permissions.selectText.3=Galarazi dokumentuaren mihiztaketa
|
||||||
|
permissions.selectText.4=Galarazi edukia ateratzea
|
||||||
|
permissions.selectText.5=Galarazi ateratzea irisgarritasunerako
|
||||||
|
permissions.selectText.6=Galarazi inprimakia betetzea
|
||||||
|
permissions.selectText.7=Galarazi aldaketak egitea
|
||||||
|
permissions.selectText.8=Galarazi oharrak aldatzea
|
||||||
|
permissions.selectText.9=Galarazi inprimatzea
|
||||||
|
permissions.selectText.10=Galarazi zenbait formatu inprimatzea
|
||||||
|
permissions.submit=Aldatu
|
||||||
|
|
||||||
|
|
||||||
|
#remove password
|
||||||
|
removePassword.title=Ezabatu pasahitza
|
||||||
|
removePassword.header=Ezabatu pasahitza (desenkriptatu)
|
||||||
|
removePassword.selectText.1=Hautatu PDFa desenkriptatzeko
|
||||||
|
removePassword.selectText.2=Pasahitza
|
||||||
|
removePassword.submit=Ezabatu
|
||||||
|
|
||||||
|
|
||||||
|
#changeMetadata
|
||||||
|
changeMetadata.title=Izenburua:
|
||||||
|
changeMetadata.header=Aldatu metadatuak
|
||||||
|
changeMetadata.selectText.1=Editatu aldatu nahi dituzun aldagaiak
|
||||||
|
changeMetadata.selectText.2=Ezabatu metadatu guztiak
|
||||||
|
changeMetadata.selectText.3=Erakutsi metadatu pertsonalizatuak:
|
||||||
|
changeMetadata.author=Egilea:
|
||||||
|
changeMetadata.creationDate=Sortze-data (aaaa/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.creator=Sortzailea:
|
||||||
|
changeMetadata.keywords=Gako-hitzak:
|
||||||
|
changeMetadata.modDate=Aldatze-data (aaaa/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.producer=Ekoizlea:
|
||||||
|
changeMetadata.subject=Gaia:
|
||||||
|
changeMetadata.title=Izenburua:
|
||||||
|
changeMetadata.trapped=Trapped:
|
||||||
|
changeMetadata.selectText.4=Beste metadatu batzuk:
|
||||||
|
changeMetadata.selectText.5=Gehitu metadatu pertsonalizatuen sarrera
|
||||||
|
changeMetadata.submit=Aldatu
|
||||||
|
|
||||||
|
|
||||||
|
#xlsToPdf
|
||||||
|
xlsToPdf.title=Excela PDF bihurtu
|
||||||
|
xlsToPdf.header=Excela PDF bihurtu
|
||||||
|
xlsToPdf.selectText.1=Hautatu Excel XLSren edo XLSXren kalkulu-orria bihurtzeko
|
||||||
|
xlsToPdf.convert=Bikurtu
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToPDFA
|
||||||
|
pdfToPDFA.title=PDFa PDF/A bihurtu
|
||||||
|
pdfToPDFA.header=PDFa PDF/A bihurtu
|
||||||
|
pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko
|
||||||
|
pdfToPDFA.submit=Bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToWord
|
||||||
|
PDFToWord.title=PDFa Word bihurtu
|
||||||
|
PDFToWord.header=PDFa Word bihurtu
|
||||||
|
PDFToWord.selectText.1=Irteerako fitxategiaren formatua
|
||||||
|
PDFToWord.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko
|
||||||
|
PDFToWord.submit=Bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToPresentation
|
||||||
|
PDFToPresentation.title=PDFa aurkezpen bihurtu
|
||||||
|
PDFToPresentation.header=PDFa aurkezpen bihurtu
|
||||||
|
PDFToPresentation.selectText.1=Irteerako fitxategiaren formatua
|
||||||
|
PDFToPresentation.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko
|
||||||
|
PDFToPresentation.submit=Bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToText
|
||||||
|
PDFToText.title=PDFa TXT/RTF bihurtu
|
||||||
|
PDFToText.header=PDFa TXT/RTF bihurtu
|
||||||
|
PDFToText.selectText.1=Irteerako fitxategiaren formatua
|
||||||
|
PDFToText.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko
|
||||||
|
PDFToText.submit=Bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToHTML
|
||||||
|
PDFToHTML.title=PDFa HTML bihurtu
|
||||||
|
PDFToHTML.header=PDFa HTML bihurtu
|
||||||
|
PDFToHTML.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko
|
||||||
|
PDFToHTML.submit=Bihurtu
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToXML
|
||||||
|
PDFToXML.title=PDFa XML bihurtu
|
||||||
|
PDFToXML.header=PDFa XML bihurtu
|
||||||
|
PDFToXML.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko
|
||||||
|
PDFToXML.submit=Bihurtu
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
734
src/main/resources/messages_ja_JP.properties
Normal file
734
src/main/resources/messages_ja_JP.properties
Normal file
@@ -0,0 +1,734 @@
|
|||||||
|
###########
|
||||||
|
# Generic #
|
||||||
|
###########
|
||||||
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
|
language.direction=ltr
|
||||||
|
|
||||||
|
pdfPrompt=PDFを選択
|
||||||
|
multiPdfPrompt=PDFを選択 (2つ以上)
|
||||||
|
multiPdfDropPrompt=PDFを選択 (又はドラッグ&ドロップ)
|
||||||
|
imgPrompt=画像を選択
|
||||||
|
genericSubmit=送信
|
||||||
|
processTimeWarning=警告:この処理はファイルサイズによって1分程度かかることがあります
|
||||||
|
pageOrderPrompt=ページ順序 (ページ番号をカンマ区切り又は2n+1のような関数で入力):
|
||||||
|
goToPage=移動
|
||||||
|
true=True
|
||||||
|
false=False
|
||||||
|
unknown=不明
|
||||||
|
save=保存
|
||||||
|
close=閉じる
|
||||||
|
filesSelected=選択されたファイル
|
||||||
|
noFavourites=お気に入りはありません
|
||||||
|
bored=待ち時間が退屈
|
||||||
|
alphabet=\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8
|
||||||
|
downloadPdf=PDFをダウンロード
|
||||||
|
text=テキスト
|
||||||
|
font=フォント
|
||||||
|
selectFillter=-- 選択 --
|
||||||
|
pageNum=ページ番号
|
||||||
|
sizes.small=Small
|
||||||
|
sizes.medium=Medium
|
||||||
|
sizes.large=Large
|
||||||
|
sizes.x-large=X-Large
|
||||||
|
error.pdfPassword=PDFにパスワードが設定されてますが、パスワードが入力されてないか間違ってます。
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
# NAVBAR #
|
||||||
|
#############
|
||||||
|
navbar.convert=変換
|
||||||
|
navbar.security=セキュリティ
|
||||||
|
navbar.other=その他
|
||||||
|
navbar.darkmode=ダークモード
|
||||||
|
navbar.pageOps=ページ操作
|
||||||
|
navbar.settings=設定
|
||||||
|
|
||||||
|
#############
|
||||||
|
# SETTINGS #
|
||||||
|
#############
|
||||||
|
settings.title=設定
|
||||||
|
settings.update=利用可能なアップデート
|
||||||
|
settings.appVersion=Appバージョン:
|
||||||
|
settings.downloadOption.title=ダウンロードオプション (zip以外の単一ファイル):
|
||||||
|
settings.downloadOption.1=同じウィンドウで開く
|
||||||
|
settings.downloadOption.2=新しいウィンドウで開く
|
||||||
|
settings.downloadOption.3=ファイルをダウンロード
|
||||||
|
settings.zipThreshold=このファイル数を超えたときにファイルを圧縮する
|
||||||
|
|
||||||
|
#############
|
||||||
|
# HOME-PAGE #
|
||||||
|
#############
|
||||||
|
home.desc=PDFのあらゆるニーズに対応するローカルホスティングされた総合窓口です。
|
||||||
|
|
||||||
|
|
||||||
|
home.multiTool.title=PDFマルチツール
|
||||||
|
home.multiTool.desc=ページの結合、回転、並べ替え、削除します。
|
||||||
|
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move
|
||||||
|
|
||||||
|
home.merge.title=結合
|
||||||
|
home.merge.desc=複数のPDFを1つに結合します。
|
||||||
|
merge.tags=merge,Page operations,Back end,server side
|
||||||
|
|
||||||
|
home.split.title=分割
|
||||||
|
home.split.desc=PDFを複数のドキュメントに分割します。
|
||||||
|
split.tags=Page operations,divide,Multi Page,cut,server side
|
||||||
|
|
||||||
|
home.rotate.title=回転
|
||||||
|
home.rotate.desc=PDFを回転します。
|
||||||
|
rotate.tags=server side
|
||||||
|
|
||||||
|
|
||||||
|
home.imageToPdf.title=画像をPDFに変換
|
||||||
|
home.imageToPdf.desc=画像 (PNG, JPEG, GIF) をPDFに変換します。
|
||||||
|
imageToPdf.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.pdfToImage.title=PDFを画像に変換
|
||||||
|
home.pdfToImage.desc=PDFを画像 (PNG, JPEG, GIF) に変換します。
|
||||||
|
pdfToImage.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.pdfOrganiser.title=整理
|
||||||
|
home.pdfOrganiser.desc=ページの削除/並べ替えします。
|
||||||
|
pdfOrganiser.tags=duplex,even,odd,sort,move
|
||||||
|
|
||||||
|
|
||||||
|
home.addImage.title=画像の追加
|
||||||
|
home.addImage.desc=PDF上の任意の場所に画像を追加します。
|
||||||
|
addImage.tags=img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.watermark.title=透かしの追加
|
||||||
|
home.watermark.desc=PDFに独自の透かしを追加します。
|
||||||
|
watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo
|
||||||
|
|
||||||
|
home.permissions.title=権限の変更
|
||||||
|
home.permissions.desc=PDFの権限を変更します。
|
||||||
|
permissions.tags=read,write,edit,print
|
||||||
|
|
||||||
|
|
||||||
|
home.removePages.title=削除
|
||||||
|
home.removePages.desc=PDFから不要なページを削除します。
|
||||||
|
removePages.tags=Remove pages,delete pages
|
||||||
|
|
||||||
|
home.addPassword.title=パスワードの追加
|
||||||
|
home.addPassword.desc=PDFをパスワードで暗号化します。
|
||||||
|
addPassword.tags=secure,security
|
||||||
|
|
||||||
|
home.removePassword.title=パスワードの削除
|
||||||
|
home.removePassword.desc=PDFからパスワードの削除します。
|
||||||
|
removePassword.tags=secure,Decrypt,security,unpassword,delete password
|
||||||
|
|
||||||
|
home.compressPdfs.title=圧縮
|
||||||
|
home.compressPdfs.desc=PDFを圧縮してファイルサイズを小さくします。
|
||||||
|
compressPdfs.tags=squish,small,tiny
|
||||||
|
|
||||||
|
|
||||||
|
home.changeMetadata.title=メタデータの変更
|
||||||
|
home.changeMetadata.desc=PDFのメタデータを変更/削除/追加します。
|
||||||
|
changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats
|
||||||
|
|
||||||
|
home.fileToPDF.title=ファイルをPDFに変換
|
||||||
|
home.fileToPDF.desc=ほぼすべてのファイルをPDFに変換します。 (DOCX, PNG, XLS, PPT, TXTなど)
|
||||||
|
fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint
|
||||||
|
|
||||||
|
home.ocr.title=OCR / クリーンアップ
|
||||||
|
home.ocr.desc=クリーンアップはPDF内の画像からテキストを検出してテキストとして再追加します。
|
||||||
|
ocr.tags=recognition,text,image,scan,read,identify,detection,editable
|
||||||
|
|
||||||
|
|
||||||
|
home.extractImages.title=画像の抽出
|
||||||
|
home.extractImages.desc=PDFからすべての画像を抽出してzipで保存します。
|
||||||
|
extractImages.tags=picture,photo,save,archive,zip,capture,grab
|
||||||
|
|
||||||
|
home.pdfToPDFA.title=PDFをPDF/Aに変換
|
||||||
|
home.pdfToPDFA.desc=長期保存のためにPDFをPDF/Aに変換。
|
||||||
|
pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation
|
||||||
|
|
||||||
|
home.PDFToWord.title=PDFをWordに変換
|
||||||
|
home.PDFToWord.desc=PDFをWord形式に変換します。 (DOC, DOCX および ODT)
|
||||||
|
PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile
|
||||||
|
|
||||||
|
home.PDFToPresentation.title=PDFをプレゼンテーションに変換
|
||||||
|
home.PDFToPresentation.desc=PDFをプレゼンテーション形式に変換します。 (PPT, PPTX および ODP)
|
||||||
|
PDFToPresentation.tags=slides,show,office,microsoft
|
||||||
|
|
||||||
|
home.PDFToText.title=PDFをText/RTFに変換
|
||||||
|
home.PDFToText.desc=PDFをTextまたはRTF形式に変換します。
|
||||||
|
PDFToText.tags=richformat,richtextformat,rich text format
|
||||||
|
|
||||||
|
home.PDFToHTML.title=PDFをHTMLに変換
|
||||||
|
home.PDFToHTML.desc=PDFをHTML形式に変換します。
|
||||||
|
PDFToHTML.tags=web content,browser friendly
|
||||||
|
|
||||||
|
|
||||||
|
home.PDFToXML.title=PDFをXMLに変換
|
||||||
|
home.PDFToXML.desc=PDFをXML形式に変換します。
|
||||||
|
PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert
|
||||||
|
|
||||||
|
home.ScannerImageSplit.title=スキャンされた画像の検出/分割
|
||||||
|
home.ScannerImageSplit.desc=1枚の画像/PDFから複数の写真を分割します。
|
||||||
|
ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize
|
||||||
|
|
||||||
|
home.sign.title=署名
|
||||||
|
home.sign.desc=手書き、テキストまたは画像によってPDFに署名を追加します。
|
||||||
|
sign.tags=authorize,initials,drawn-signature,text-sign,image-signature
|
||||||
|
|
||||||
|
home.flatten.title=平坦化
|
||||||
|
home.flatten.desc=PDFからインタラクティブな要素とフォームをすべて削除します。
|
||||||
|
flatten.tags=static,deactivate,non-interactive,streamline
|
||||||
|
|
||||||
|
home.repair.title=修復
|
||||||
|
home.repair.desc=破損したPDFの修復を試みます。
|
||||||
|
repair.tags=fix,restore,correction,recover
|
||||||
|
|
||||||
|
home.removeBlanks.title=空白ページの削除
|
||||||
|
home.removeBlanks.desc=ドキュメントから空白ページを検出して削除します。
|
||||||
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
|
home.compare.title=比較
|
||||||
|
home.compare.desc=2つのPDFを比較して表示します。
|
||||||
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
|
||||||
|
home.certSign.title=証明書による署名
|
||||||
|
home.certSign.desc=証明書/キーを使用してPDFに署名します。 (PEM/P12)
|
||||||
|
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||||
|
|
||||||
|
home.pageLayout.title=マルチページレイアウト
|
||||||
|
home.pageLayout.desc=PDFの複数のページを1ページに結合します。
|
||||||
|
pageLayout.tags=merge,composite,single-view,organize
|
||||||
|
|
||||||
|
home.scalePages.title=ページの縮尺の調整
|
||||||
|
home.scalePages.desc=ページやコンテンツの縮尺を変更します。
|
||||||
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
|
home.pipeline.title=Pipeline (Advanced)
|
||||||
|
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||||
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
|
home.add-page-numbers.title=Add Page Numbers
|
||||||
|
home.add-page-numbers.desc=Add Page numbers throughout a document in a set location
|
||||||
|
add-page-numbers.tags=paginate,label,organize,index
|
||||||
|
|
||||||
|
home.auto-rename.title=Auto Rename PDF File
|
||||||
|
home.auto-rename.desc=Auto renames a PDF file based on its detected header
|
||||||
|
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||||
|
|
||||||
|
home.adjust-contrast.title=Adjust Colors/Contrast
|
||||||
|
home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF
|
||||||
|
adjust-contrast.tags=color-correction,tune,modify,enhance
|
||||||
|
|
||||||
|
home.crop.title=Crop PDF
|
||||||
|
home.crop.desc=Crop a PDF to reduce its size (maintains text!)
|
||||||
|
crop.tags=trim,shrink,edit,shape
|
||||||
|
|
||||||
|
home.autoSplitPDF.title=Auto Split Pages
|
||||||
|
home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code
|
||||||
|
autoSplitPDF.tags=QR-based,separate,scan-segment,organize
|
||||||
|
|
||||||
|
home.sanitizePdf.title=Sanitize
|
||||||
|
home.sanitizePdf.desc=Remove scripts and other elements from PDF files
|
||||||
|
sanitizePdf.tags=clean,secure,safe,remove-threats
|
||||||
|
|
||||||
|
home.URLToPDF.title=URL/Website To PDF
|
||||||
|
home.URLToPDF.desc=Converts any http(s)URL to PDF
|
||||||
|
URLToPDF.tags=web-capture,save-page,web-to-doc,archive
|
||||||
|
|
||||||
|
home.HTMLToPDF.title=HTML to PDF
|
||||||
|
home.HTMLToPDF.desc=Converts any HTML file or zip to PDF
|
||||||
|
HTMLToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
|
home.MarkdownToPDF.title=Markdown to PDF
|
||||||
|
home.MarkdownToPDF.desc=Converts any Markdown file to PDF
|
||||||
|
MarkdownToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
|
home.getPdfInfo.title=Get ALL Info on PDF
|
||||||
|
home.getPdfInfo.desc=Grabs any and all information possible on PDFs
|
||||||
|
getPdfInfo.tags=infomation,data,stats,statistics
|
||||||
|
|
||||||
|
|
||||||
|
home.extractPage.title=Extract page(s)
|
||||||
|
home.extractPage.desc=Extracts select pages from PDF
|
||||||
|
extractPage.tags=extract
|
||||||
|
|
||||||
|
|
||||||
|
home.PdfToSinglePage.title=PDF to Single Large Page
|
||||||
|
home.PdfToSinglePage.desc=Merges all PDF pages into one large single page
|
||||||
|
PdfToSinglePage.tags=single page
|
||||||
|
|
||||||
|
|
||||||
|
##########################
|
||||||
|
### TODO: Translate ###
|
||||||
|
##########################
|
||||||
|
home.showJS.title=Show Javascript
|
||||||
|
home.showJS.desc=Searches and displays any JS injected into a PDF
|
||||||
|
showJS.tags=JS
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# #
|
||||||
|
# WEB PAGES #
|
||||||
|
# #
|
||||||
|
###########################
|
||||||
|
#showJS
|
||||||
|
##########################
|
||||||
|
### TODO: Translate ###
|
||||||
|
##########################
|
||||||
|
showJS.title=Show Javascript
|
||||||
|
showJS.header=Show Javascript
|
||||||
|
showJS.downloadJS=Download Javascript
|
||||||
|
showJS.submit=Show
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToSinglePage
|
||||||
|
pdfToSinglePage.title=PDF To Single Page
|
||||||
|
pdfToSinglePage.header=PDF To Single Page
|
||||||
|
pdfToSinglePage.submit=Convert To Single Page
|
||||||
|
|
||||||
|
|
||||||
|
#pageExtracter
|
||||||
|
pageExtracter.title=Extract Pages
|
||||||
|
pageExtracter.header=Extract Pages
|
||||||
|
pageExtracter.submit=Extract
|
||||||
|
|
||||||
|
|
||||||
|
#getPdfInfo
|
||||||
|
getPdfInfo.title=Get Info on PDF
|
||||||
|
getPdfInfo.header=Get Info on PDF
|
||||||
|
getPdfInfo.submit=Get Info
|
||||||
|
getPdfInfo.downloadJson=Download JSON
|
||||||
|
|
||||||
|
|
||||||
|
#markdown-to-pdf
|
||||||
|
MarkdownToPDF.title=Markdown To PDF
|
||||||
|
MarkdownToPDF.header=Markdown To PDF
|
||||||
|
MarkdownToPDF.submit=Convert
|
||||||
|
MarkdownToPDF.help=Work in progress
|
||||||
|
MarkdownToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#url-to-pdf
|
||||||
|
URLToPDF.title=URL To PDF
|
||||||
|
URLToPDF.header=URL To PDF
|
||||||
|
URLToPDF.submit=Convert
|
||||||
|
URLToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#html-to-pdf
|
||||||
|
HTMLToPDF.title=HTML To PDF
|
||||||
|
HTMLToPDF.header=HTML To PDF
|
||||||
|
HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required
|
||||||
|
HTMLToPDF.submit=Convert
|
||||||
|
HTMLToPDF.credit=Uses WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#sanitizePDF
|
||||||
|
sanitizePDF.title=Sanitize PDF
|
||||||
|
sanitizePDF.header=Sanitize a PDF file
|
||||||
|
sanitizePDF.selectText.1=Remove JavaScript actions
|
||||||
|
sanitizePDF.selectText.2=Remove embedded files
|
||||||
|
sanitizePDF.selectText.3=Remove metadata
|
||||||
|
sanitizePDF.selectText.4=Remove links
|
||||||
|
sanitizePDF.selectText.5=Remove fonts
|
||||||
|
sanitizePDF.submit=Sanitize PDF
|
||||||
|
|
||||||
|
|
||||||
|
#addPageNumbers
|
||||||
|
addPageNumbers.title=Add Page Numbers
|
||||||
|
addPageNumbers.header=Add Page Numbers
|
||||||
|
addPageNumbers.selectText.1=Select PDF file:
|
||||||
|
addPageNumbers.selectText.2=Margin Size
|
||||||
|
addPageNumbers.selectText.3=Position
|
||||||
|
addPageNumbers.selectText.4=Starting Number
|
||||||
|
addPageNumbers.selectText.5=Pages to Number
|
||||||
|
addPageNumbers.selectText.6=Custom Text
|
||||||
|
addPageNumbers.submit=Add Page Numbers
|
||||||
|
|
||||||
|
|
||||||
|
#auto-rename
|
||||||
|
auto-rename.title=Auto Rename
|
||||||
|
auto-rename.header=Auto Rename PDF
|
||||||
|
auto-rename.submit=Auto Rename
|
||||||
|
|
||||||
|
|
||||||
|
#adjustContrast
|
||||||
|
adjustContrast.title=Adjust Contrast
|
||||||
|
adjustContrast.header=Adjust Contrast
|
||||||
|
adjustContrast.contrast=Contrast:
|
||||||
|
adjustContrast.brightness=Brightness:
|
||||||
|
adjustContrast.saturation=Saturation:
|
||||||
|
adjustContrast.download=Download
|
||||||
|
|
||||||
|
|
||||||
|
#crop
|
||||||
|
crop.title=Crop
|
||||||
|
crop.header=Crop Image
|
||||||
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#autoSplitPDF
|
||||||
|
autoSplitPDF.title=Auto Split PDF
|
||||||
|
autoSplitPDF.header=Auto Split PDF
|
||||||
|
autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed.
|
||||||
|
autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine).
|
||||||
|
autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them.
|
||||||
|
autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest.
|
||||||
|
autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document.
|
||||||
|
autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers:
|
||||||
|
autoSplitPDF.duplexMode=Duplex Mode (Front and back scanning)
|
||||||
|
autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf'
|
||||||
|
autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf'
|
||||||
|
autoSplitPDF.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
#pipeline
|
||||||
|
pipeline.title=Pipeline
|
||||||
|
|
||||||
|
|
||||||
|
#pageLayout
|
||||||
|
pageLayout.title=マルチページレイアウト
|
||||||
|
pageLayout.header=マルチページレイアウト
|
||||||
|
pageLayout.pagesPerSheet=1枚あたりのページ数:
|
||||||
|
pageLayout.submit=送信
|
||||||
|
|
||||||
|
|
||||||
|
#scalePages
|
||||||
|
scalePages.title=ページの縮尺の調整
|
||||||
|
scalePages.header=ページの縮尺の調整
|
||||||
|
scalePages.pageSize=1ページのサイズ
|
||||||
|
scalePages.scaleFactor=1ページの拡大レベル (トリミング)。
|
||||||
|
scalePages.submit=送信
|
||||||
|
|
||||||
|
|
||||||
|
#certSign
|
||||||
|
certSign.title=証明書による署名
|
||||||
|
certSign.header=証明書を使用してPDFに署名します。 (進行中)
|
||||||
|
certSign.selectPDF=署名するPDFファイルを選択:
|
||||||
|
certSign.selectKey=秘密キーファイルを選択 (PKCS#8形式、.pemまたは.der) :
|
||||||
|
certSign.selectCert=証明書ファイルを選択 (X.509形式、.pemまたは.der) :
|
||||||
|
certSign.selectP12=PKCS#12キーストアファイルを選択 (.p12または.pfx) (オプション。指定する場合は秘密キーと証明書が含まれている必要があります。):
|
||||||
|
certSign.certType=証明書の種類
|
||||||
|
certSign.password=キーストアまたは秘密キーのパスワードを入力 (ある場合) :
|
||||||
|
certSign.showSig=署名を表示
|
||||||
|
certSign.reason=理由
|
||||||
|
certSign.location=場所
|
||||||
|
certSign.name=名前
|
||||||
|
certSign.submit=PDFに署名
|
||||||
|
|
||||||
|
|
||||||
|
#removeBlanks
|
||||||
|
removeBlanks.title=空白の削除
|
||||||
|
removeBlanks.header=空白ページの削除
|
||||||
|
removeBlanks.threshold=しきい値 :
|
||||||
|
removeBlanks.thresholdDesc=白色ピクセルの白さを決めるためのしきい値
|
||||||
|
removeBlanks.whitePercent=白比率
|
||||||
|
removeBlanks.whitePercentDesc=削除するページの白の割合
|
||||||
|
removeBlanks.submit=空白ページの削除
|
||||||
|
|
||||||
|
|
||||||
|
#compare
|
||||||
|
compare.title=比較
|
||||||
|
compare.header=PDFの比較
|
||||||
|
compare.document.1=ドキュメント 1
|
||||||
|
compare.document.2=ドキュメント 2
|
||||||
|
compare.submit=比較
|
||||||
|
|
||||||
|
|
||||||
|
#sign
|
||||||
|
sign.title=署名
|
||||||
|
sign.header=PDFに署名
|
||||||
|
sign.upload=画像をアップロード
|
||||||
|
sign.draw=署名を書く
|
||||||
|
sign.text=テキスト入力
|
||||||
|
sign.clear=クリア
|
||||||
|
sign.add=追加
|
||||||
|
|
||||||
|
|
||||||
|
#repair
|
||||||
|
repair.title=修復
|
||||||
|
repair.header=PDFを修復
|
||||||
|
repair.submit=修復
|
||||||
|
|
||||||
|
|
||||||
|
#flatten
|
||||||
|
flatten.title=平坦化
|
||||||
|
flatten.header=PDFを平坦化する
|
||||||
|
flatten.submit=平坦化
|
||||||
|
|
||||||
|
|
||||||
|
#ScannerImageSplit
|
||||||
|
ScannerImageSplit.selectText.1=角度のしきい値:
|
||||||
|
ScannerImageSplit.selectText.2=画像を回転させるために必要な絶対角度の最小値を設定 (初期値:10)。
|
||||||
|
ScannerImageSplit.selectText.3=許容範囲:
|
||||||
|
ScannerImageSplit.selectText.4=推定された背景色周辺のカラーバリエーションの範囲を決定 (初期値:30)。
|
||||||
|
ScannerImageSplit.selectText.5=最小面積:
|
||||||
|
ScannerImageSplit.selectText.6=画像の最小面積のしきい値を設定 (初期値:10000)。
|
||||||
|
ScannerImageSplit.selectText.7=最小輪郭面積:
|
||||||
|
ScannerImageSplit.selectText.8=画像の最小の輪郭面積のしきい値を設定。
|
||||||
|
ScannerImageSplit.selectText.9=境界線サイズ:
|
||||||
|
ScannerImageSplit.selectText.10=出力に白い縁取りが出ないように追加・削除される境界線の大きさを設定 (初期値:1)。
|
||||||
|
|
||||||
|
|
||||||
|
#OCR
|
||||||
|
ocr.title=OCR / クリーンアップ
|
||||||
|
ocr.header=クリーンアップ / OCR (光学式文字認識)
|
||||||
|
ocr.selectText.1=PDF内で検出される言語を選択 (リストされているものは現在検出されているものです):
|
||||||
|
ocr.selectText.2=OCR処理されたPDFと一緒に、OCRしたテキストを含むテキストファイルを作成する
|
||||||
|
ocr.selectText.3=斜めにスキャンされたページを回転させて修正する
|
||||||
|
ocr.selectText.4=ページをきれいにして背景ノイズの中からテキストを検出しにくくする。(出力は変わりません)
|
||||||
|
ocr.selectText.5=ページをきれいにして背景ノイズの中からテキストを検出しにくくし、出力はクリーンアップを維持する。
|
||||||
|
ocr.selectText.6=インタラクティブなテキストを含むページを無視し、画像ページのみをOCRする
|
||||||
|
ocr.selectText.7=強制OCR、全てのページで元のテキスト要素を全て削除してOCRする
|
||||||
|
ocr.selectText.8=Normal (PDFにテキストが含まれている場合はエラーになります。)
|
||||||
|
ocr.selectText.9=追加設定
|
||||||
|
ocr.selectText.10=OCRモード
|
||||||
|
ocr.selectText.11=OCR後に画像を削除する (すべての画像を削除します。変換ステップの一部である場合にのみ有効です)。
|
||||||
|
ocr.selectText.12=レンダリングタイプ (高度)
|
||||||
|
ocr.help=他の言語でこれを使用する方法やDocker以外で使用する方法についてはこのドキュメントをお読みください。
|
||||||
|
ocr.credit=本サービスにはOCRにOCRmyPDFとTesseractを使用しています。
|
||||||
|
ocr.submit=OCRでPDFを処理する
|
||||||
|
|
||||||
|
|
||||||
|
#extractImages
|
||||||
|
extractImages.title=画像の抽出
|
||||||
|
extractImages.header=画像の抽出
|
||||||
|
extractImages.selectText=抽出した画像のフォーマットを選択
|
||||||
|
extractImages.submit=抽出
|
||||||
|
|
||||||
|
|
||||||
|
#File to PDF
|
||||||
|
fileToPDF.title=ファイルをPDFに変換
|
||||||
|
fileToPDF.header=あらゆるファイルをPDFに変換
|
||||||
|
fileToPDF.credit=本サービスはファイル変換にLibreOfficeとUnoconvを使用しています。
|
||||||
|
fileToPDF.supportedFileTypes=サポートされるファイル形式には以下が含まれますが、完全な更新リストについてはLibreOfficeのドキュメントを参照してください。
|
||||||
|
fileToPDF.submit=PDFを変換
|
||||||
|
|
||||||
|
|
||||||
|
#compress
|
||||||
|
compress.title=圧縮
|
||||||
|
compress.header=PDFを圧縮
|
||||||
|
compress.credit=本サービスはPDFの圧縮/最適化にGhostscriptを使用しています。
|
||||||
|
compress.selectText.1=手動モード - 1 から 4
|
||||||
|
compress.selectText.2=品質レベル:
|
||||||
|
compress.selectText.3=4 (テキスト画像は最悪)
|
||||||
|
compress.selectText.4=自動モード - PDFを正確なサイズにするために品質を自動調整する。
|
||||||
|
compress.selectText.5=PDFサイズ (例 25MB, 10.8MB, 25KB)
|
||||||
|
compress.submit=圧縮
|
||||||
|
|
||||||
|
|
||||||
|
#Add image
|
||||||
|
addImage.title=画像の追加
|
||||||
|
addImage.header=PDFに画像を追加
|
||||||
|
addImage.everyPage=全ページ?
|
||||||
|
addImage.upload=画像の追加
|
||||||
|
addImage.submit=画像の追加
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
merge.title=結合
|
||||||
|
merge.header=複数のPDFを結合 (2ファイル以上)
|
||||||
|
merge.submit=結合
|
||||||
|
|
||||||
|
|
||||||
|
#pdfOrganiser
|
||||||
|
pdfOrganiser.title=整理
|
||||||
|
pdfOrganiser.header=PDFページの整理
|
||||||
|
pdfOrganiser.submit=ページの整理
|
||||||
|
|
||||||
|
|
||||||
|
#multiTool
|
||||||
|
multiTool.title=PDFマルチツール
|
||||||
|
multiTool.header=PDFマルチツール
|
||||||
|
|
||||||
|
|
||||||
|
#pageRemover
|
||||||
|
pageRemover.title=ページ削除
|
||||||
|
pageRemover.header=PDFページ削除
|
||||||
|
pageRemover.pagesToDelete=削除するページ (ページ番号のカンマ区切りリストを入力してください):
|
||||||
|
pageRemover.submit=ページ削除
|
||||||
|
|
||||||
|
|
||||||
|
#rotate
|
||||||
|
rotate.title=PDFの回転
|
||||||
|
rotate.header=PDFの回転
|
||||||
|
rotate.selectAngle=回転角度を選択 (90度の倍数):
|
||||||
|
rotate.submit=回転
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
split.title=PDFの分割
|
||||||
|
split.header=PDFの分割
|
||||||
|
split.desc.1=選択する番号は分割するページ番号です。
|
||||||
|
split.desc.2=したがって、1,3,7-8を選択すると、10ページのドキュメントが以下のように6つのPDFに分割されることになります。
|
||||||
|
split.desc.3=ドキュメント #1: ページ 1
|
||||||
|
split.desc.4=ドキュメント #2: ページ 2, 3
|
||||||
|
split.desc.5=ドキュメント #3: ページ 4, 5, 6
|
||||||
|
split.desc.6=ドキュメント #4: ページ 7
|
||||||
|
split.desc.7=ドキュメント #5: ページ 8
|
||||||
|
split.desc.8=ドキュメント #6: ページ 9, 10
|
||||||
|
split.splitPages=分割するページ番号を入力:
|
||||||
|
split.submit=分割
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
imageToPDF.title=画像をPDFに変換
|
||||||
|
imageToPDF.header=画像をPDFに変換
|
||||||
|
imageToPDF.submit=変換
|
||||||
|
imageToPDF.selectText.1=フィットするように引き伸ばす
|
||||||
|
imageToPDF.selectText.2=PDFの自動回転
|
||||||
|
imageToPDF.selectText.3=マルチファイルの処理 (複数の画像を操作する場合に有効になります)
|
||||||
|
imageToPDF.selectText.4=1つのPDFに結合
|
||||||
|
imageToPDF.selectText.5=個別のPDFに変換
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToImage
|
||||||
|
pdfToImage.title=PDFを画像に変換
|
||||||
|
pdfToImage.header=PDFを画像に変換
|
||||||
|
pdfToImage.selectText=画像の形式
|
||||||
|
pdfToImage.singleOrMultiple=画像出力タイプ
|
||||||
|
pdfToImage.single=単一の大きな画像
|
||||||
|
pdfToImage.multi=複数の画像
|
||||||
|
pdfToImage.colorType=カラーモード
|
||||||
|
pdfToImage.color=カラー
|
||||||
|
pdfToImage.grey=グレースケール
|
||||||
|
pdfToImage.blackwhite=白黒 (データが失われる可能性があります!)
|
||||||
|
pdfToImage.submit=変換
|
||||||
|
|
||||||
|
|
||||||
|
#addPassword
|
||||||
|
addPassword.title=パスワードの追加
|
||||||
|
addPassword.header=パスワードの追加 (暗号化)
|
||||||
|
addPassword.selectText.1=暗号化するPDFを選択
|
||||||
|
addPassword.selectText.2=ユーザーパスワード
|
||||||
|
addPassword.selectText.3=暗号化キーの長さ
|
||||||
|
addPassword.selectText.4=値が大きいほど強力ですが、値が小さいほど互換性が高くなります。
|
||||||
|
addPassword.selectText.5=権限の設定 (所有者パスワードとの併用をおすすめします)
|
||||||
|
addPassword.selectText.6=ドキュメントの組立を禁止
|
||||||
|
addPassword.selectText.7=コンテンツの抽出を禁止
|
||||||
|
addPassword.selectText.8=アクセシビリティのための抽出を禁止
|
||||||
|
addPassword.selectText.9=フォームへの入力を禁止
|
||||||
|
addPassword.selectText.10=変更を禁止
|
||||||
|
addPassword.selectText.11=注釈の変更を禁止
|
||||||
|
addPassword.selectText.12=印刷を禁止
|
||||||
|
addPassword.selectText.13=異なる形式の印刷を禁止
|
||||||
|
addPassword.selectText.14=所有者パスワード
|
||||||
|
addPassword.selectText.15=ドキュメントを開いた後に実行できる操作を制限します (すべてのリーダーでサポートされているわけではありません)
|
||||||
|
addPassword.selectText.16=ドキュメントを開くことを制限します
|
||||||
|
addPassword.submit=暗号化
|
||||||
|
|
||||||
|
|
||||||
|
#watermark
|
||||||
|
watermark.title=透かしの追加
|
||||||
|
watermark.header=透かしの追加
|
||||||
|
watermark.selectText.1=透かしを追加するPDFを選択:
|
||||||
|
watermark.selectText.2=透かしのテキスト:
|
||||||
|
watermark.selectText.3=文字サイズ:
|
||||||
|
watermark.selectText.4=回転 (0-360):
|
||||||
|
watermark.selectText.5=幅スペース (各透かし間の水平方向のスペース):
|
||||||
|
watermark.selectText.6=高さスペース (各透かし間の垂直方向のスペース):
|
||||||
|
watermark.selectText.7=不透明度 (0% - 100%):
|
||||||
|
watermark.submit=透かしを追加
|
||||||
|
|
||||||
|
|
||||||
|
#remove-watermark
|
||||||
|
remove-watermark.title=透かしの削除
|
||||||
|
remove-watermark.header=透かしの削除
|
||||||
|
remove-watermark.selectText.1=透かしを削除するPDFを選択:
|
||||||
|
remove-watermark.selectText.2=透かしのテキスト:
|
||||||
|
remove-watermark.submit=透かしを削除
|
||||||
|
|
||||||
|
|
||||||
|
#Change permissions
|
||||||
|
permissions.title=権限の変更
|
||||||
|
permissions.header=権限の変更
|
||||||
|
permissions.warning=警告、これらの権限を変更できないようにするため、パスワードの追加ページでパスワードを設定することを推奨します。
|
||||||
|
permissions.selectText.1=権限を変更するPDFを選択
|
||||||
|
permissions.selectText.2=権限の設定
|
||||||
|
permissions.selectText.3=ドキュメントの組立を禁止
|
||||||
|
permissions.selectText.4=コンテンツの抽出を禁止
|
||||||
|
permissions.selectText.5=アクセシビリティのための抽出を禁止
|
||||||
|
permissions.selectText.6=フォームへの入力を禁止
|
||||||
|
permissions.selectText.7=変更を禁止
|
||||||
|
permissions.selectText.8=注釈の変更を禁止
|
||||||
|
permissions.selectText.9=印刷を禁止
|
||||||
|
permissions.selectText.10=異なる形式の印刷を禁止
|
||||||
|
permissions.submit=変更
|
||||||
|
|
||||||
|
|
||||||
|
#remove password
|
||||||
|
removePassword.title=パスワードの削除
|
||||||
|
removePassword.header=パスワードの削除 (復号化)
|
||||||
|
removePassword.selectText.1=復号化するPDFを選択
|
||||||
|
removePassword.selectText.2=パスワード
|
||||||
|
removePassword.submit=削除
|
||||||
|
|
||||||
|
|
||||||
|
#changeMetadata
|
||||||
|
changeMetadata.title=タイトル:
|
||||||
|
changeMetadata.header=メタデータの変更
|
||||||
|
changeMetadata.selectText.1=変更したい変数を編集してください
|
||||||
|
changeMetadata.selectText.2=すべてのメタデータを削除
|
||||||
|
changeMetadata.selectText.3=カスタムメタデータを表示
|
||||||
|
changeMetadata.author=著者:
|
||||||
|
changeMetadata.creationDate=作成日 (yyyy/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.creator=作成者:
|
||||||
|
changeMetadata.keywords=キーワード:
|
||||||
|
changeMetadata.modDate=変更日 (yyyy/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.producer=プロデューサー:
|
||||||
|
changeMetadata.subject=主題:
|
||||||
|
changeMetadata.title=タイトル:
|
||||||
|
changeMetadata.trapped=トラッピング:
|
||||||
|
changeMetadata.selectText.4=その他のメタデータ:
|
||||||
|
changeMetadata.selectText.5=カスタムメタデータの追加
|
||||||
|
changeMetadata.submit=変更
|
||||||
|
|
||||||
|
|
||||||
|
#xlsToPdf
|
||||||
|
xlsToPdf.title=ExcelをPDFに変換
|
||||||
|
xlsToPdf.header=ExcelをPDFに変換
|
||||||
|
xlsToPdf.selectText.1=変換するXLSまたはXLSX Execlシートを選択
|
||||||
|
xlsToPdf.convert=変換
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToPDFA
|
||||||
|
pdfToPDFA.title=PDFをPDF/Aに変換
|
||||||
|
pdfToPDFA.header=PDFをPDF/Aに変換
|
||||||
|
pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用しています。
|
||||||
|
pdfToPDFA.submit=変換
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToWord
|
||||||
|
PDFToWord.title=PDFをWordに変換
|
||||||
|
PDFToWord.header=PDFをWordに変換
|
||||||
|
PDFToWord.selectText.1=出力ファイル形式
|
||||||
|
PDFToWord.credit=本サービスはファイル変換にLibreOfficeを使用しています。
|
||||||
|
PDFToWord.submit=変換
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToPresentation
|
||||||
|
PDFToPresentation.title=PDFをプレゼンテーションに変換
|
||||||
|
PDFToPresentation.header=PDFをプレゼンテーションに変換
|
||||||
|
PDFToPresentation.selectText.1=出力ファイル形式
|
||||||
|
PDFToPresentation.credit=本サービスはファイル変換にLibreOfficeを使用しています。
|
||||||
|
PDFToPresentation.submit=変換
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToText
|
||||||
|
PDFToText.title=PDFをText/RTFに変換
|
||||||
|
PDFToText.header=PDFをText/RTFに変換
|
||||||
|
PDFToText.selectText.1=出力ファイル形式
|
||||||
|
PDFToText.credit=本サービスはファイル変換にLibreOfficeを使用しています。
|
||||||
|
PDFToText.submit=変換
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToHTML
|
||||||
|
PDFToHTML.title=PDFをHTMLに変換
|
||||||
|
PDFToHTML.header=PDFをHTMLに変換
|
||||||
|
PDFToHTML.credit=本サービスはファイル変換にLibreOfficeを使用しています。
|
||||||
|
PDFToHTML.submit=変換
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToXML
|
||||||
|
PDFToXML.title=PDFをXMLに変換
|
||||||
|
PDFToXML.header=PDFをXMLに変換
|
||||||
|
PDFToXML.credit=本サービスはファイル変換にLibreOfficeを使用しています。
|
||||||
|
PDFToXML.submit=変換
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,68 +1,94 @@
|
|||||||
#page-container {
|
#page-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content-wrap {
|
#content-wrap {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.navbar {
|
||||||
html[lang-direction=ltr] * {
|
height: auto; /* Adjusts height automatically based on content */
|
||||||
direction: ltr;
|
white-space: nowrap; /* Prevents wrapping of navbar contents */
|
||||||
}
|
}
|
||||||
html[lang-direction=rtl] * {
|
/* TODO enable later
|
||||||
direction: rtl;
|
.navbar .container {
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.ignore-rtl {
|
max-width: 100%; //Allows the container to expand up to full width
|
||||||
direction: ltr !important;
|
margin-left: auto;
|
||||||
text-align: left !important;
|
margin-right: auto;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
.align-top {
|
html[lang-direction=ltr] * {
|
||||||
position: absolute;
|
direction: ltr;
|
||||||
top: 0;
|
}
|
||||||
}
|
html[lang-direction=rtl] * {
|
||||||
.align-center-right {
|
direction: rtl;
|
||||||
position: absolute;
|
text-align: right;
|
||||||
right: 0;
|
}
|
||||||
top: 50%;
|
.ignore-rtl {
|
||||||
}
|
direction: ltr !important;
|
||||||
|
text-align: left !important;
|
||||||
.align-center-left {
|
}
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
.align-top {
|
||||||
top: 50%;
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
|
}
|
||||||
.align-bottom {
|
.align-center-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
right: 0;
|
||||||
}
|
top: 50%;
|
||||||
|
}
|
||||||
.btn-group > label:first-of-type {
|
|
||||||
border-top-left-radius: 0.25rem !important;
|
.align-center-left {
|
||||||
border-bottom-left-radius: 0.25rem !important;
|
position: absolute;
|
||||||
}
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
html[lang-direction="rtl"] input.form-check-input {
|
}
|
||||||
position: relative;
|
|
||||||
margin-left: 0px;
|
.align-bottom {
|
||||||
}
|
position: absolute;
|
||||||
html[lang-direction="rtl"] label.form-check-label {
|
bottom: 0;
|
||||||
display: inline;
|
}
|
||||||
}
|
|
||||||
|
.btn-group > label:first-of-type {
|
||||||
.margin-auto-parent {
|
border-top-left-radius: 0.25rem !important;
|
||||||
width: 100%;
|
border-bottom-left-radius: 0.25rem !important;
|
||||||
display: flex;
|
}
|
||||||
}
|
|
||||||
.margin-center {
|
html[lang-direction="rtl"] input.form-check-input {
|
||||||
margin: 0 auto;
|
position: relative;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
html[lang-direction="rtl"] label.form-check-label {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-auto-parent {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.margin-center {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
#pdf-canvas {
|
||||||
|
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.fixed-shadow-canvas {
|
||||||
|
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.shadow-canvas {
|
||||||
|
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384);
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
|
#searchBar {
|
||||||
|
background-image: url('/images/search.svg');
|
||||||
|
background-position: 16px 16px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px 20px 12px 40px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.features-container {
|
.features-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr));
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
.list-group-item {
|
.list-group-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filename {
|
.filename {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrows {
|
.arrows {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
.arrows .btn {
|
||||||
.move-up span,
|
margin: 0 3px;
|
||||||
.move-down span {
|
}
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2em;
|
.move-up span,
|
||||||
}
|
.move-down span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,41 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#navbarSearch {
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchForm {
|
||||||
|
width: 200px; /* Adjust this value as needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the search results to match the navbar */
|
||||||
|
#searchResults {
|
||||||
|
max-height: 200px; /* Adjust this value as needed */
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults .dropdown-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 50px; /* Fixed height */
|
||||||
|
overflow: hidden; /* Hide overflow */
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults .icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults .icon-text {
|
||||||
|
display: inline;
|
||||||
|
overflow: hidden; /* Hide overflow */
|
||||||
|
text-overflow: ellipsis; /* Add ellipsis for long text */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.main-icon {
|
.main-icon {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
|||||||
3
src/main/resources/static/css/prism.css
Normal file
3
src/main/resources/static/css/prism.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/* PrismJS 1.29.0
|
||||||
|
https://prismjs.com/download.html#themes=prism-coy&languages=clike+javascript */
|
||||||
|
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{position:relative;margin:.5em 0;overflow:visible;padding:1px}pre[class*=language-]>code{position:relative;z-index:1;border-left:10px solid #358ccb;box-shadow:-1px 0 0 0 #358ccb,0 0 0 1px #dfdfdf;background-color:#fdfdfd;background-image:linear-gradient(transparent 50%,rgba(69,142,209,.04) 50%);background-size:3em 3em;background-origin:content-box;background-attachment:local}code[class*=language-]{max-height:inherit;height:inherit;padding:0 1em;display:block;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background-color:#fdfdfd;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-bottom:1em}:not(pre)>code[class*=language-]{position:relative;padding:.2em;border-radius:.3em;color:#c92c2c;border:1px solid rgba(0,0,0,.1);display:inline;white-space:normal}pre[class*=language-]:after,pre[class*=language-]:before{content:'';display:block;position:absolute;bottom:.75em;left:.18em;width:40%;height:20%;max-height:13em;box-shadow:0 13px 8px #979797;-webkit-transform:rotate(-2deg);-moz-transform:rotate(-2deg);-ms-transform:rotate(-2deg);-o-transform:rotate(-2deg);transform:rotate(-2deg)}pre[class*=language-]:after{right:.75em;left:auto;-webkit-transform:rotate(2deg);-moz-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#7d8b99}.token.punctuation{color:#5f6364}.token.boolean,.token.constant,.token.deleted,.token.function-name,.token.number,.token.property,.token.symbol,.token.tag{color:#c92c2c}.token.attr-name,.token.builtin,.token.char,.token.function,.token.inserted,.token.selector,.token.string{color:#2f9c0a}.token.entity,.token.operator,.token.url,.token.variable{color:#a67f59;background:rgba(255,255,255,.5)}.token.atrule,.token.attr-value,.token.class-name,.token.keyword{color:#1990b8}.token.important,.token.regex{color:#e90}.language-css .token.string,.style .token.string{color:#a67f59;background:rgba(255,255,255,.5)}.token.important{font-weight:400}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.7}@media screen and (max-width:767px){pre[class*=language-]:after,pre[class*=language-]:before{bottom:14px;box-shadow:none}}pre[class*=language-].line-numbers.line-numbers{padding-left:0}pre[class*=language-].line-numbers.line-numbers code{padding-left:3.8em}pre[class*=language-].line-numbers.line-numbers .line-numbers-rows{left:0}pre[class*=language-][data-line]{padding-top:0;padding-bottom:0;padding-left:0}pre[data-line] code{position:relative;padding-left:4em}pre .line-highlight{margin-top:0}
|
||||||
BIN
src/main/resources/static/favicon.ico
Normal file
BIN
src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user