diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 00000000..ce4c63a7
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,2 @@
+# Formatting
+5f771b785130154ed47952635b7acef371ffe0ec
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index a35c2aa1..adf9dcf0 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
+ - package-ecosystem: "docker"
+ directory: "/" # Location of Dockerfile
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..74d1f8f5
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,34 @@
+name: "Build repo"
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+
+ - uses: gradle/gradle-build-action@v2.4.2
+ with:
+ gradle-version: 7.6
+ arguments: build --no-build-cache
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
deleted file mode 100644
index 55500892..00000000
--- a/.github/workflows/codeql.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-
-name: "Build repo"
-
-on:
- push:
- branches: [ "main" ]
- pull_request:
- # The branches below must be a subset of the branches above
- branches: [ "main" ]
- schedule:
- - cron: '15 12 * * 1'
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- - name: Set up JDK 17
- uses: actions/setup-java@v3
- with:
- java-version: '17'
- distribution: 'temurin'
-
- # - name: Initialize CodeQL
- # uses: github/codeql-action/init@v2
- # with:
- # languages: java
-
- - uses: gradle/gradle-build-action@v2.4.2
- with:
- gradle-version: 7.6
- arguments: assemble --no-build-cache
-
- #- name: Perform CodeQL analysis
- # uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..b131a6df
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,56 @@
+name: Docker Compose Tests
+
+on:
+ pull_request:
+ paths:
+ - 'src/**'
+ - '**.gradle'
+ - 'exampleYmlFiles/**'
+ - 'Dockerfile'
+ - 'Dockerfile**'
+ paths-ignore:
+ - 'src/main/java/resources/messages*'
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v2
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Run Docker Compose Tests
+ run: |
+ chmod +x ./gradlew
+
+ - name: Get version number
+ id: versionNumber
+ run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
+
+
+ - name: Cache Docker layers
+ uses: actions/cache@v2
+ with:
+ path: /tmp/.buildx-cache
+ key: ${{ runner.os }}-buildx-${{ steps.versionNumber.outputs.versionNumber }}
+ restore-keys: |
+ ${{ runner.os }}-buildx-
+
+ - name: Install Docker Compose
+ run: |
+ sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
+ sudo chmod +x /usr/local/bin/docker-compose
+
+
+ - name: Run Docker Compose Tests
+ run: |
+ chmod +x ./test.sh
+ ./test.sh
diff --git a/HowToAddNewLanguage.md b/HowToAddNewLanguage.md
index b6a7fa4a..4cdcedfe 100644
--- a/HowToAddNewLanguage.md
+++ b/HowToAddNewLanguage.md
@@ -1,4 +1,4 @@
-
Stirling-PDF
+
Stirling-PDF
@@ -8,9 +8,9 @@ Fork Stirling-PDF and make a new branch out of Main
Then add reference to the language in the navbar by adding a new language entry to the dropdown
-https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html
+https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html
and add a flag svg file to
-https://github.com/Frooodle/Stirling-PDF/tree/main/src/main/resources/static/images/flags
+https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
If your language isnt represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
@@ -25,7 +25,7 @@ The data-language-code is the code used to reference the file in the next step.
Start by copying the existing english property file
-[https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
+[https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties
diff --git a/LocalRunGuide.md b/LocalRunGuide.md
index c7eaca0b..824c1ae7 100644
--- a/LocalRunGuide.md
+++ b/LocalRunGuide.md
@@ -109,7 +109,7 @@ pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
```bash
cd ~/.git &&\
-git clone https://github.com/Frooodle/Stirling-PDF.git &&\
+git clone https://github.com/Stirling-Tools/Stirling-PDF.git &&\
cd Stirling-PDF &&\
chmod +x ./gradlew &&\
./gradlew build
diff --git a/README.md b/README.md
index d9515ca6..f9b42a42 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
-
Stirling-PDF
+
Stirling-PDF
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ)
-[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Frooodle/Stirling-PDF/)
-[![GitHub Repo stars](https://img.shields.io/github/stars/frooodle/stirling-pdf?style=social)](https://github.com/Frooodle/stirling-pdf)
+[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
+[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
[![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex)
[![Github Sponser](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle)
-[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Frooodle/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
+[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
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.
@@ -22,10 +22,10 @@ Please feel free to submit feature requests or report bugs either through GitHub
## Features
- 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/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example)
- Parallel file processing and downloads
- API for integration with external scripts
-- Optional Login and Authentication support (see [here](https://github.com/Frooodle/Stirling-PDF/tree/main#login-authentication) for documentation)
+- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
## **PDF Features**
@@ -80,7 +80,7 @@ Please feel free to submit feature requests or report bugs either through GitHub
- Get all information on a PDF to view or export as JSON.
-For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Frooodle/Stirling-PDF/blob/main/Endpoint-groups.md)
+For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de
## Technologies used
@@ -96,13 +96,13 @@ Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) h
## How to use
### Locally
-Please view https://github.com/Frooodle/Stirling-PDF/blob/main/LocalRunGuide.md
+Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md
### Docker / Podman
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)
+To see what the different versions offer please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md)
For people that don't mind about space optimization just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-lite?label=Stirling-PDF%20Lite)
@@ -144,7 +144,7 @@ services:
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
## Enable OCR/Compression feature
-Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
+Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md
## Want to add your own language?
Stirling PDF currently supports 21!
@@ -172,7 +172,7 @@ Stirling PDF currently supports 21!
- Hindi (हिंदी) (hi_IN)
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/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md
And please create a PR to merge it back in so others can use it!
@@ -224,7 +224,7 @@ metrics:
enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable
```
### Extra notes
-- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/Endpoint-groups.md)
+- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
- customStaticFilePath. Customise static files such as the app logo by placing files in the /customFiles/static/ directory. An example of customising app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
### Environment only parameters
@@ -234,7 +234,7 @@ metrics:
## 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
-[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)
+[here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
## Login authentication
diff --git a/build.gradle b/build.gradle
index 8002ee7a..c9839555 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,11 @@
plugins {
- id 'java'
- id 'org.springframework.boot' version '3.1.2'
- id 'io.spring.dependency-management' version '1.1.3'
- id 'org.springdoc.openapi-gradle-plugin' version '1.8.0'
- id "io.swagger.swaggerhub" version "1.3.2"
- id 'edu.sc.seis.launch4j' version '3.0.5'
+ id 'java'
+ id 'org.springframework.boot' version '3.2.1'
+ id 'io.spring.dependency-management' version '1.1.3'
+ id 'org.springdoc.openapi-gradle-plugin' version '1.8.0'
+ id "io.swagger.swaggerhub" version "1.3.2"
+ id 'edu.sc.seis.launch4j' version '3.0.5'
+ id 'com.diffplug.spotless' version '6.23.3'
}
group = 'stirling.software'
@@ -12,7 +13,7 @@ version = '0.18.1'
sourceCompatibility = '17'
repositories {
- mavenCentral()
+ mavenCentral()
}
sourceSets {
@@ -61,21 +62,37 @@ launch4j {
messagesInstanceAlreadyExists="Stirling-PDF is already running."
}
+spotless {
+ java {
+ target project.fileTree('src/main/java')
+
+ googleJavaFormat('1.19.1').aosp().reorderImports(false)
+
+ importOrder('java', 'javax', 'org', 'com', 'net', 'io')
+ toggleOffOn()
+ trimTrailingWhitespace()
+ indentWithSpaces()
+ endWithNewline()
+ }
+}
+
dependencies {
- //security updates
- implementation 'ch.qos.logback:logback-classic:1.4.14'
- implementation 'ch.qos.logback:logback-core:1.4.14'
- implementation 'org.springframework:spring-webmvc:6.0.15'
-
- implementation 'org.yaml:snakeyaml:2.1'
+ //security updates
+ implementation 'ch.qos.logback:logback-classic:1.4.14'
+ implementation 'ch.qos.logback:logback-core:1.4.14'
+ implementation 'org.springframework:spring-webmvc:6.1.2'
+
+ implementation 'org.yaml:snakeyaml:2.2'
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.1'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.1'
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.1'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
- implementation "org.springframework.boot:spring-boot-starter-data-jpa"
- implementation "com.h2database:h2"
+ implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.1"
+
+ //2.2.x requires rebuild of DB file.. need migration path
+ implementation "com.h2database:h2:2.1.214"
}
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.1'
@@ -107,7 +124,7 @@ dependencies {
//general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv
- implementation ('com.opencsv:opencsv:5.7.1') {
+ implementation ('com.opencsv:opencsv:5.9') {
exclude group: 'commons-logging', module: 'commons-logging'
}
@@ -134,6 +151,9 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok:1.18.28'
}
+tasks.withType(JavaCompile) {
+ dependsOn 'spotlessApply'
+}
task writeVersion {
def propsFile = file('src/main/resources/version.properties')
@@ -164,7 +184,7 @@ jar {
}
tasks.named('test') {
- useJUnitPlatform()
+ useJUnitPlatform()
}
task printVersion {
diff --git a/chart/stirling-pdf/Chart.yaml b/chart/stirling-pdf/Chart.yaml
index a69894a0..686d2422 100644
--- a/chart/stirling-pdf/Chart.yaml
+++ b/chart/stirling-pdf/Chart.yaml
@@ -1,15 +1,15 @@
apiVersion: v2
appVersion: 0.14.2
description: locally hosted web application that allows you to perform various operations on PDF files
-home: https://github.com/Frooodle/Stirling-PDF
+home: https://github.com/Stirling-Tools/Stirling-PDF
keywords:
- stirling-pdf
- helm
- charts repo
maintainers:
-- name: Frooodle
- url: https://github.com/Frooodle/Stirling-PDF
+- name: Stirling-Tools
+ url: https://github.com/Stirling-Tools/Stirling-PDF
name: stirling-pdf-chart
sources:
-- https://github.com/Frooodle/Stirling-PDF
+- https://github.com/Stirling-Tools/Stirling-PDF
version: 1.0.0
diff --git a/exampleYmlFiles/docker-compose-latest-lite-security.yml b/exampleYmlFiles/docker-compose-latest-lite-security.yml
new file mode 100644
index 00000000..41dc4567
--- /dev/null
+++ b/exampleYmlFiles/docker-compose-latest-lite-security.yml
@@ -0,0 +1,31 @@
+version: '3.3'
+services:
+ stirling-pdf:
+ container_name: Stirling-PDF-Lite-Security
+ image: frooodle/s-pdf:latest-lite
+ deploy:
+ resources:
+ limits:
+ memory: 2G
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+ ports:
+ - 8080:8080
+ volumes:
+ - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
+ - /stirling/latest/config:/configs:rw
+ - /stirling/latest/logs:/logs:rw
+ environment:
+ DOCKER_ENABLE_SECURITY: "true"
+ SECURITY_ENABLELOGIN: "true"
+ SYSTEM_DEFAULTLOCALE: en_US
+ UI_APPNAME: Stirling-PDF-Lite
+ UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
+ UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
+ SYSTEM_MAXFILESIZE: "100"
+ METRICS_ENABLED: "true"
+ SYSTEM_GOOGLEVISIBILITY: "true"
+ restart: on-failure:5
diff --git a/exampleYmlFiles/docker-compose-latest-lite.yml b/exampleYmlFiles/docker-compose-latest-lite.yml
new file mode 100644
index 00000000..7b374688
--- /dev/null
+++ b/exampleYmlFiles/docker-compose-latest-lite.yml
@@ -0,0 +1,30 @@
+version: '3.3'
+services:
+ stirling-pdf:
+ container_name: Stirling-PDF-Lite
+ image: frooodle/s-pdf:latest-lite
+ deploy:
+ resources:
+ limits:
+ memory: 2G
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+ ports:
+ - 8080:8080
+ volumes:
+ - /stirling/latest/config:/configs:rw
+ - /stirling/latest/logs:/logs:rw
+ environment:
+ DOCKER_ENABLE_SECURITY: "false"
+ SECURITY_ENABLELOGIN: "false"
+ SYSTEM_DEFAULTLOCALE: en_US
+ UI_APPNAME: Stirling-PDF-Lite
+ UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest
+ UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
+ SYSTEM_MAXFILESIZE: "100"
+ METRICS_ENABLED: "true"
+ SYSTEM_GOOGLEVISIBILITY: "true"
+ restart: on-failure:5
diff --git a/exampleYmlFiles/docker-compose-latest-security.yml b/exampleYmlFiles/docker-compose-latest-security.yml
new file mode 100644
index 00000000..807a755e
--- /dev/null
+++ b/exampleYmlFiles/docker-compose-latest-security.yml
@@ -0,0 +1,31 @@
+version: '3.3'
+services:
+ stirling-pdf:
+ container_name: Stirling-PDF-Security
+ image: frooodle/s-pdf:latest
+ deploy:
+ resources:
+ limits:
+ memory: 4G
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+ ports:
+ - 8080:8080
+ volumes:
+ - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
+ - /stirling/latest/config:/configs:rw
+ - /stirling/latest/logs:/logs:rw
+ environment:
+ DOCKER_ENABLE_SECURITY: "true"
+ SECURITY_ENABLELOGIN: "true"
+ SYSTEM_DEFAULTLOCALE: en_US
+ UI_APPNAME: Stirling-PDF
+ UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
+ UI_APPNAMENAVBAR: Stirling-PDF Latest
+ SYSTEM_MAXFILESIZE: "100"
+ METRICS_ENABLED: "true"
+ SYSTEM_GOOGLEVISIBILITY: "true"
+ restart: on-failure:5
diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml
new file mode 100644
index 00000000..7971eeb7
--- /dev/null
+++ b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml
@@ -0,0 +1,31 @@
+version: '3.3'
+services:
+ stirling-pdf:
+ container_name: Stirling-PDF-Ultra-Lite-Security
+ image: frooodle/s-pdf:latest-ultra-lite
+ deploy:
+ resources:
+ limits:
+ memory: 1G
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+ ports:
+ - 8080:8080
+ volumes:
+ - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
+ - /stirling/latest/config:/configs:rw
+ - /stirling/latest/logs:/logs:rw
+ environment:
+ DOCKER_ENABLE_SECURITY: "true"
+ SECURITY_ENABLELOGIN: "true"
+ SYSTEM_DEFAULTLOCALE: en_US
+ UI_APPNAME: Stirling-PDF-Lite
+ UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
+ UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
+ SYSTEM_MAXFILESIZE: "100"
+ METRICS_ENABLED: "true"
+ SYSTEM_GOOGLEVISIBILITY: "true"
+ restart: on-failure:5
diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite.yml
new file mode 100644
index 00000000..31fec67b
--- /dev/null
+++ b/exampleYmlFiles/docker-compose-latest-ultra-lite.yml
@@ -0,0 +1,30 @@
+version: '3.3'
+services:
+ stirling-pdf:
+ container_name: Stirling-PDF-Ultra-Lite
+ image: frooodle/s-pdf:latest-ultra-lite
+ deploy:
+ resources:
+ limits:
+ memory: 1G
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+ ports:
+ - 8080:8080
+ volumes:
+ - /stirling/latest/config:/configs:rw
+ - /stirling/latest/logs:/logs:rw
+ environment:
+ DOCKER_ENABLE_SECURITY: "false"
+ SECURITY_ENABLELOGIN: "false"
+ SYSTEM_DEFAULTLOCALE: en_US
+ UI_APPNAME: Stirling-PDF-Ultra-lite
+ UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Ultra-lite Latest
+ UI_APPNAMENAVBAR: Stirling-PDF-Ultra-lite Latest
+ SYSTEM_MAXFILESIZE: "100"
+ METRICS_ENABLED: "true"
+ SYSTEM_GOOGLEVISIBILITY: "true"
+ restart: on-failure:5
diff --git a/exampleYmlFiles/docker-compose-latest.yml b/exampleYmlFiles/docker-compose-latest.yml
new file mode 100644
index 00000000..a9dc1f31
--- /dev/null
+++ b/exampleYmlFiles/docker-compose-latest.yml
@@ -0,0 +1,31 @@
+version: '3.3'
+services:
+ stirling-pdf:
+ container_name: Stirling-PDF
+ image: frooodle/s-pdf:latest
+ deploy:
+ resources:
+ limits:
+ memory: 4G
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
+ interval: 5s
+ timeout: 10s
+ retries: 16
+ ports:
+ - 8080:8080
+ volumes:
+ - /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
+ - /stirling/latest/config:/configs:rw
+ - /stirling/latest/logs:/logs:rw
+ environment:
+ DOCKER_ENABLE_SECURITY: "false"
+ SECURITY_ENABLELOGIN: "false"
+ SYSTEM_DEFAULTLOCALE: en_US
+ UI_APPNAME: Stirling-PDF
+ UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
+ UI_APPNAMENAVBAR: Stirling-PDF Latest
+ SYSTEM_MAXFILESIZE: "100"
+ METRICS_ENABLED: "true"
+ SYSTEM_GOOGLEVISIBILITY: "true"
+ restart: on-failure:5
diff --git a/scripts/download-security-jar.sh b/scripts/download-security-jar.sh
index c0b710ad..64a5fa25 100644
--- a/scripts/download-security-jar.sh
+++ b/scripts/download-security-jar.sh
@@ -2,13 +2,13 @@ echo "Running Stirling PDF with DOCKER_ENABLE_SECURITY=${DOCKER_ENABLE_SECURITY}
# Check for DOCKER_ENABLE_SECURITY and download the appropriate JAR if required
if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
if [ ! -f app-security.jar ]; then
- echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar"
- curl -L -o app-security.jar https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar
+ echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar"
+ curl -L -o app-security.jar https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar
# If the first download attempt failed, try with the 'v' prefix
if [ $? -ne 0 ]; then
- echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar"
- curl -L -o app-security.jar https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar
+ echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar"
+ curl -L -o app-security.jar https://github.com/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar
fi
if [ $? -eq 0 ]; then # checks if curl was successful
@@ -16,4 +16,4 @@ if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
ln -s app-security.jar app.jar
fi
fi
-fi
\ No newline at end of file
+fi
diff --git a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java
index 7f4fc160..6d32adc3 100644
--- a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java
+++ b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java
@@ -22,14 +22,14 @@ public class LibreOfficeListener {
private Process process;
- private LibreOfficeListener() {
- }
+ private LibreOfficeListener() {}
private boolean isListenerRunning() {
try {
System.out.println("waiting for listener to start");
Socket socket = new Socket();
- socket.connect(new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
+ socket.connect(
+ new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
socket.close();
return true;
} catch (IOException e) {
@@ -49,21 +49,22 @@ public class LibreOfficeListener {
// Start a background thread to monitor the activity timeout
executorService = Executors.newSingleThreadExecutor();
- executorService.submit(() -> {
- while (true) {
- long idleTime = System.currentTimeMillis() - lastActivityTime;
- if (idleTime >= ACTIVITY_TIMEOUT) {
- // If there has been no activity for too long, tear down the listener
- process.destroy();
- break;
- }
- try {
- Thread.sleep(5000); // Check for inactivity every 5 seconds
- } catch (InterruptedException e) {
- break;
- }
- }
- });
+ executorService.submit(
+ () -> {
+ while (true) {
+ long idleTime = System.currentTimeMillis() - lastActivityTime;
+ if (idleTime >= ACTIVITY_TIMEOUT) {
+ // If there has been no activity for too long, tear down the listener
+ process.destroy();
+ break;
+ }
+ try {
+ Thread.sleep(5000); // Check for inactivity every 5 seconds
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ });
// Wait for the listener to start up
long startTime = System.currentTimeMillis();
@@ -92,5 +93,4 @@ public class LibreOfficeListener {
process.destroy();
}
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java
index a7ce7f14..ef3733c6 100644
--- a/src/main/java/stirling/software/SPDF/SPdfApplication.java
+++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java
@@ -13,13 +13,12 @@ import org.springframework.scheduling.annotation.EnableScheduling;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.utils.GeneralUtils;
-@SpringBootApplication
+@SpringBootApplication
@EnableScheduling
public class SPdfApplication {
- @Autowired
- private Environment env;
+ @Autowired private Environment env;
@PostConstruct
public void init() {
@@ -44,21 +43,24 @@ public class SPdfApplication {
}
public static void main(String[] args) {
- SpringApplication app = new SpringApplication(SPdfApplication.class);
- app.addInitializers(new ConfigInitializer());
- if (Files.exists(Paths.get("configs/settings.yml"))) {
- app.setDefaultProperties(Collections.singletonMap("spring.config.additional-location", "file:configs/settings.yml"));
+ SpringApplication app = new SpringApplication(SPdfApplication.class);
+ app.addInitializers(new ConfigInitializer());
+ if (Files.exists(Paths.get("configs/settings.yml"))) {
+ app.setDefaultProperties(
+ Collections.singletonMap(
+ "spring.config.additional-location", "file:configs/settings.yml"));
} else {
- System.out.println("External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
+ System.out.println(
+ "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
}
app.run(args);
try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
GeneralUtils.createDir("customFiles/static/");
GeneralUtils.createDir("customFiles/templates/");
diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java
index faf85b28..273de957 100644
--- a/src/main/java/stirling/software/SPDF/config/AppConfig.java
+++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java
@@ -5,13 +5,12 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import stirling.software.SPDF.model.ApplicationProperties;
+
@Configuration
public class AppConfig {
-
- @Autowired
- ApplicationProperties applicationProperties;
-
+ @Autowired ApplicationProperties applicationProperties;
+
@Bean(name = "loginEnabled")
public boolean loginEnabled() {
return applicationProperties.getSecurity().getEnableLogin();
@@ -19,7 +18,7 @@ public class AppConfig {
@Bean(name = "appName")
public String appName() {
- String homeTitle = applicationProperties.getUi().getAppName();
+ String homeTitle = applicationProperties.getUi().getAppName();
return (homeTitle != null) ? homeTitle : "Stirling PDF";
}
@@ -31,28 +30,31 @@ public class AppConfig {
@Bean(name = "homeText")
public String homeText() {
- return (applicationProperties.getUi().getHomeDescription() != null) ? applicationProperties.getUi().getHomeDescription() : "null";
+ return (applicationProperties.getUi().getHomeDescription() != null)
+ ? applicationProperties.getUi().getHomeDescription()
+ : "null";
}
-
@Bean(name = "navBarText")
public String navBarText() {
- String defaultNavBar = applicationProperties.getUi().getAppNameNavbar() != null ? applicationProperties.getUi().getAppNameNavbar() : applicationProperties.getUi().getAppName();
+ String defaultNavBar =
+ applicationProperties.getUi().getAppNameNavbar() != null
+ ? applicationProperties.getUi().getAppNameNavbar()
+ : applicationProperties.getUi().getAppName();
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
}
-
+
@Bean(name = "enableAlphaFunctionality")
- public boolean enableAlphaFunctionality() {
- return applicationProperties.getSystem().getEnableAlphaFunctionality() != null ? applicationProperties.getSystem().getEnableAlphaFunctionality() : false;
+ public boolean enableAlphaFunctionality() {
+ return applicationProperties.getSystem().getEnableAlphaFunctionality() != null
+ ? applicationProperties.getSystem().getEnableAlphaFunctionality()
+ : false;
}
-
- @Bean(name = "rateLimit")
+
+ @Bean(name = "rateLimit")
public boolean rateLimit() {
String appName = System.getProperty("rateLimit");
- if (appName == null)
- appName = System.getenv("rateLimit");
+ if (appName == null) appName = System.getenv("rateLimit");
return (appName != null) ? Boolean.valueOf(appName) : false;
}
-
-
-}
\ No newline at end of file
+}
diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java
index d1c6a03b..9230a0a0 100644
--- a/src/main/java/stirling/software/SPDF/config/Beans.java
+++ b/src/main/java/stirling/software/SPDF/config/Beans.java
@@ -15,10 +15,9 @@ import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
public class Beans implements WebMvcConfigurer {
-
- @Autowired
- ApplicationProperties applicationProperties;
-
+
+ @Autowired ApplicationProperties applicationProperties;
+
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
@@ -35,25 +34,26 @@ public class Beans implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
-
-
+
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
- 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()) {
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
String tempLanguageTag = tempLocale.toLanguageTag();
- if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
+ if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
defaultLocale = tempLocale;
} else {
- tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_","-"));
+ tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
tempLanguageTag = tempLocale.toLanguageTag();
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
defaultLocale = tempLocale;
} else {
- System.err.println("Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
+ System.err.println(
+ "Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
}
}
}
@@ -61,5 +61,4 @@ public class Beans implements WebMvcConfigurer {
slr.setDefaultLocale(defaultLocale);
return slr;
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java
index 894d50d8..472fb951 100644
--- a/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java
+++ b/src/main/java/stirling/software/SPDF/config/CleanUrlInterceptor.java
@@ -13,56 +13,62 @@ import jakarta.servlet.http.HttpServletResponse;
public class CleanUrlInterceptor implements HandlerInterceptor {
- private static final List ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
+ private static final List ALLOWED_PARAMS =
+ Arrays.asList(
+ "lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- String queryString = request.getQueryString();
- if (queryString != null && !queryString.isEmpty()) {
- String requestURI = request.getRequestURI();
- Map parameters = new HashMap<>();
+ @Override
+ public boolean preHandle(
+ HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws Exception {
+ String queryString = request.getQueryString();
+ if (queryString != null && !queryString.isEmpty()) {
+ String requestURI = request.getRequestURI();
+ Map parameters = new HashMap<>();
- // Keep only the allowed parameters
- 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]);
- }
- }
+ // Keep only the allowed parameters
+ 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 there are any parameters that are not allowed
- if (parameters.size() != queryParameters.length) {
- // Construct new query string
- StringBuilder newQueryString = new StringBuilder();
- for (Map.Entry entry : parameters.entrySet()) {
- if (newQueryString.length() > 0) {
- newQueryString.append("&");
- }
- newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
- }
+ // If there are any parameters that are not allowed
+ if (parameters.size() != queryParameters.length) {
+ // Construct new query string
+ StringBuilder newQueryString = new StringBuilder();
+ for (Map.Entry entry : parameters.entrySet()) {
+ if (newQueryString.length() > 0) {
+ newQueryString.append("&");
+ }
+ newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
+ }
- // Redirect to the URL with only allowed query parameters
- String redirectUrl = requestURI + "?" + newQueryString;
- response.sendRedirect(redirectUrl);
- return false;
- }
- }
- return true;
- }
+ // Redirect to the URL with only allowed query parameters
+ String redirectUrl = requestURI + "?" + newQueryString;
+ response.sendRedirect(redirectUrl);
+ return false;
+ }
+ }
+ return true;
+ }
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
- ModelAndView modelAndView) {
- }
+ @Override
+ public void postHandle(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler,
+ ModelAndView modelAndView) {}
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
- Exception ex) {
- }
+ @Override
+ public void afterCompletion(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler,
+ Exception ex) {}
}
diff --git a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java
index 862f5718..6435c955 100644
--- a/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java
+++ b/src/main/java/stirling/software/SPDF/config/ConfigInitializer.java
@@ -19,111 +19,125 @@ import java.util.stream.Collectors;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
-public class ConfigInitializer implements ApplicationContextInitializer {
+public class ConfigInitializer
+ implements ApplicationContextInitializer {
- @Override
- public void initialize(ConfigurableApplicationContext applicationContext) {
- try {
- ensureConfigExists();
- } catch (IOException e) {
- throw new RuntimeException("Failed to initialize application configuration", e);
- }
- }
+ @Override
+ public void initialize(ConfigurableApplicationContext applicationContext) {
+ try {
+ ensureConfigExists();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to initialize application configuration", e);
+ }
+ }
- public void ensureConfigExists() throws IOException {
- // Define the path to the external config directory
- Path destPath = Paths.get("configs", "settings.yml");
+ public void ensureConfigExists() throws IOException {
+ // Define the path to the external config directory
+ Path destPath = Paths.get("configs", "settings.yml");
- // Check if the file already exists
- if (Files.notExists(destPath)) {
- // Ensure the destination directory exists
- Files.createDirectories(destPath.getParent());
+ // Check if the file already exists
+ if (Files.notExists(destPath)) {
+ // Ensure the destination directory exists
+ Files.createDirectories(destPath.getParent());
- // Copy the resource from classpath to the external directory
- try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
- if (in != null) {
- Files.copy(in, destPath);
- } else {
- throw new FileNotFoundException("Resource file not found: settings.yml.template");
- }
- }
- } else {
- // If user file exists, we need to merge it with the template from the classpath
- List templateLines;
- try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
- templateLines = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines()
- .collect(Collectors.toList());
- }
+ // Copy the resource from classpath to the external directory
+ try (InputStream in =
+ getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
+ if (in != null) {
+ Files.copy(in, destPath);
+ } else {
+ throw new FileNotFoundException(
+ "Resource file not found: settings.yml.template");
+ }
+ }
+ } else {
+ // If user file exists, we need to merge it with the template from the classpath
+ List templateLines;
+ try (InputStream in =
+ getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
+ templateLines =
+ new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
+ .lines()
+ .collect(Collectors.toList());
+ }
- mergeYamlFiles(templateLines, destPath, destPath);
- }
- }
+ mergeYamlFiles(templateLines, destPath, destPath);
+ }
+ }
- public void mergeYamlFiles(List templateLines, Path userFilePath, Path outputPath) throws IOException {
- List userLines = Files.readAllLines(userFilePath);
- List mergedLines = new ArrayList<>();
- boolean insideAutoGenerated = false;
- boolean beforeFirstKey = true;
+ public void mergeYamlFiles(List templateLines, Path userFilePath, Path outputPath)
+ throws IOException {
+ List userLines = Files.readAllLines(userFilePath);
+ List mergedLines = new ArrayList<>();
+ boolean insideAutoGenerated = false;
+ boolean beforeFirstKey = true;
- Function isCommented = line -> line.trim().startsWith("#");
- Function extractKey = line -> {
- String[] parts = line.split(":");
- return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
- };
+ Function isCommented = line -> line.trim().startsWith("#");
+ Function extractKey =
+ line -> {
+ String[] parts = line.split(":");
+ return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
+ };
- Set userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
+ Set userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
- for (String line : templateLines) {
- String key = extractKey.apply(line);
+ for (String line : templateLines) {
+ String key = extractKey.apply(line);
- if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) {
- insideAutoGenerated = true;
- mergedLines.add(line);
- continue;
- } else if (insideAutoGenerated && line.trim().isEmpty()) {
- insideAutoGenerated = false;
- mergedLines.add(line);
- continue;
- }
+ if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) {
+ insideAutoGenerated = true;
+ mergedLines.add(line);
+ continue;
+ } else if (insideAutoGenerated && line.trim().isEmpty()) {
+ insideAutoGenerated = false;
+ mergedLines.add(line);
+ continue;
+ }
- if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
- // Handle top comments and empty lines before the first key.
- mergedLines.add(line);
- continue;
- }
+ if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
+ // Handle top comments and empty lines before the first key.
+ mergedLines.add(line);
+ continue;
+ }
- if (!key.isEmpty())
- beforeFirstKey = false;
+ if (!key.isEmpty()) beforeFirstKey = false;
- if (userKeys.contains(key)) {
- // If user has any version (commented or uncommented) of this key, skip the
- // template line
- Optional userValue = userLines.stream()
- .filter(l -> extractKey.apply(l).equalsIgnoreCase(key) && !isCommented.apply(l)).findFirst();
- if (userValue.isPresent())
- mergedLines.add(userValue.get());
- continue;
- }
+ if (userKeys.contains(key)) {
+ // If user has any version (commented or uncommented) of this key, skip the
+ // template line
+ Optional userValue =
+ userLines.stream()
+ .filter(
+ l ->
+ extractKey.apply(l).equalsIgnoreCase(key)
+ && !isCommented.apply(l))
+ .findFirst();
+ if (userValue.isPresent()) mergedLines.add(userValue.get());
+ continue;
+ }
- if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) {
- mergedLines.add(line); // If line is commented, empty or key not present in user's file, retain the
- // template line
- continue;
- }
- }
+ if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) {
+ mergedLines.add(
+ line); // If line is commented, empty or key not present in user's file,
+ // retain the
+ // template line
+ continue;
+ }
+ }
- // Add any additional uncommented user lines that are not present in the
- // template
- for (String userLine : userLines) {
- String userKey = extractKey.apply(userLine);
- boolean isPresentInTemplate = templateLines.stream().map(extractKey)
- .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
- if (!isPresentInTemplate && !isCommented.apply(userLine)) {
- mergedLines.add(userLine);
- }
- }
+ // Add any additional uncommented user lines that are not present in the
+ // template
+ for (String userLine : userLines) {
+ String userKey = extractKey.apply(userLine);
+ boolean isPresentInTemplate =
+ templateLines.stream()
+ .map(extractKey)
+ .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
+ if (!isPresentInTemplate && !isCommented.apply(userLine)) {
+ mergedLines.add(userLine);
+ }
+ }
- Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
- }
-
-}
\ No newline at end of file
+ Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
+ }
+}
diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java
index ebba9815..ddba4623 100644
--- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java
+++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java
@@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import stirling.software.SPDF.model.ApplicationProperties;
+
@Service
public class EndpointConfiguration {
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
@@ -26,16 +27,16 @@ public class EndpointConfiguration {
init();
processEnvironmentConfigs();
}
-
+
public void enableEndpoint(String endpoint) {
- endpointStatuses.put(endpoint, true);
+ endpointStatuses.put(endpoint, true);
}
public void disableEndpoint(String endpoint) {
- if(!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
- logger.info("Disabling {}", endpoint);
- endpointStatuses.put(endpoint, false);
- }
+ if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
+ logger.info("Disabling {}", endpoint);
+ endpointStatuses.put(endpoint, false);
+ }
}
public boolean isEndpointEnabled(String endpoint) {
@@ -66,7 +67,7 @@ public class EndpointConfiguration {
}
}
}
-
+
public void init() {
// Adding endpoints to "PageOps" group
addEndpointToGroup("PageOps", "remove-pages");
@@ -84,8 +85,7 @@ public class EndpointConfiguration {
addEndpointToGroup("PageOps", "split-by-size-or-count");
addEndpointToGroup("PageOps", "overlay-pdf");
addEndpointToGroup("PageOps", "split-pdf-by-sections");
-
-
+
// Adding endpoints to "Convert" group
addEndpointToGroup("Convert", "pdf-to-img");
addEndpointToGroup("Convert", "img-to-pdf");
@@ -101,8 +101,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Convert", "url-to-pdf");
addEndpointToGroup("Convert", "markdown-to-pdf");
addEndpointToGroup("Convert", "pdf-to-csv");
-
-
+
// Adding endpoints to "Security" group
addEndpointToGroup("Security", "add-password");
addEndpointToGroup("Security", "remove-password");
@@ -111,8 +110,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Security", "cert-sign");
addEndpointToGroup("Security", "sanitize-pdf");
addEndpointToGroup("Security", "auto-redact");
-
-
+
// Adding endpoints to "Other" group
addEndpointToGroup("Other", "ocr-pdf");
addEndpointToGroup("Other", "add-image");
@@ -130,10 +128,8 @@ public class EndpointConfiguration {
addEndpointToGroup("Other", "auto-rename");
addEndpointToGroup("Other", "get-info-on-pdf");
addEndpointToGroup("Other", "show-javascript");
-
-
-
- //CLI
+
+ // CLI
addEndpointToGroup("CLI", "compress-pdf");
addEndpointToGroup("CLI", "extract-image-scans");
addEndpointToGroup("CLI", "remove-blanks");
@@ -149,19 +145,18 @@ public class EndpointConfiguration {
addEndpointToGroup("CLI", "ocr-pdf");
addEndpointToGroup("CLI", "html-to-pdf");
addEndpointToGroup("CLI", "url-to-pdf");
-
-
- //python
+
+ // python
addEndpointToGroup("Python", "extract-image-scans");
addEndpointToGroup("Python", "remove-blanks");
addEndpointToGroup("Python", "html-to-pdf");
addEndpointToGroup("Python", "url-to-pdf");
-
- //openCV
+
+ // openCV
addEndpointToGroup("OpenCV", "extract-image-scans");
addEndpointToGroup("OpenCV", "remove-blanks");
- //LibreOffice
+ // LibreOffice
addEndpointToGroup("LibreOffice", "repair");
addEndpointToGroup("LibreOffice", "file-to-pdf");
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
@@ -170,14 +165,13 @@ public class EndpointConfiguration {
addEndpointToGroup("LibreOffice", "pdf-to-text");
addEndpointToGroup("LibreOffice", "pdf-to-html");
addEndpointToGroup("LibreOffice", "pdf-to-xml");
-
-
- //OCRmyPDF
+
+ // OCRmyPDF
addEndpointToGroup("OCRmyPDF", "compress-pdf");
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
-
- //Java
+
+ // Java
addEndpointToGroup("Java", "merge-pdfs");
addEndpointToGroup("Java", "remove-pages");
addEndpointToGroup("Java", "split-pdfs");
@@ -210,16 +204,14 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections");
-
- //Javascript
+
+ // Javascript
addEndpointToGroup("Javascript", "pdf-organizer");
addEndpointToGroup("Javascript", "sign");
addEndpointToGroup("Javascript", "compare");
addEndpointToGroup("Javascript", "adjust-contrast");
-
-
}
-
+
private void processEnvironmentConfigs() {
List endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
@@ -236,6 +228,4 @@ public class EndpointConfiguration {
}
}
}
-
}
-
diff --git a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java
index 77191a41..d408b9ea 100644
--- a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java
+++ b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java
@@ -10,11 +10,11 @@ import jakarta.servlet.http.HttpServletResponse;
@Component
public class EndpointInterceptor implements HandlerInterceptor {
- @Autowired
- private EndpointConfiguration endpointConfiguration;
+ @Autowired private EndpointConfiguration endpointConfiguration;
@Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ public boolean preHandle(
+ HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestURI = request.getRequestURI();
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
@@ -23,4 +23,4 @@ public class EndpointInterceptor implements HandlerInterceptor {
}
return true;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java
index 1cdc99e3..3877c566 100644
--- a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java
+++ b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.config;
+
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -21,4 +22,4 @@ public class MetricsConfig {
}
};
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java
index 9abb68bf..9207fd07 100644
--- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java
+++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java
@@ -8,6 +8,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
+
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -16,35 +17,48 @@ import jakarta.servlet.http.HttpServletResponse;
@Component
public class MetricsFilter extends OncePerRequestFilter {
- private final MeterRegistry meterRegistry;
+ private final MeterRegistry meterRegistry;
- @Autowired
- public MetricsFilter(MeterRegistry meterRegistry) {
- this.meterRegistry = meterRegistry;
- }
+ @Autowired
+ public MetricsFilter(MeterRegistry meterRegistry) {
+ this.meterRegistry = meterRegistry;
+ }
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
- String uri = request.getRequestURI();
+ @Override
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ String uri = request.getRequestURI();
- // System.out.println("uri="+uri + ", method=" + request.getMethod() );
- // Ignore static resources
- if (!(uri.startsWith("/js") || uri.startsWith("/v1/api-docs") || uri.endsWith("robots.txt")
- || uri.startsWith("/images") || uri.startsWith("/images")|| uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".map")
- || uri.endsWith(".svg") || uri.endsWith(".js") || uri.contains("swagger")
- || uri.startsWith("/api/v1/info") || uri.startsWith("/site.webmanifest") || uri.startsWith("/fonts") || uri.startsWith("/pdfjs") )) {
-
-
-
- Counter counter = Counter.builder("http.requests").tag("uri", uri).tag("method", request.getMethod())
- .register(meterRegistry);
+ // System.out.println("uri="+uri + ", method=" + request.getMethod() );
+ // Ignore static resources
+ if (!(uri.startsWith("/js")
+ || uri.startsWith("/v1/api-docs")
+ || uri.endsWith("robots.txt")
+ || uri.startsWith("/images")
+ || uri.startsWith("/images")
+ || uri.endsWith(".png")
+ || uri.endsWith(".ico")
+ || uri.endsWith(".css")
+ || uri.endsWith(".map")
+ || uri.endsWith(".svg")
+ || uri.endsWith(".js")
+ || uri.contains("swagger")
+ || uri.startsWith("/api/v1/info")
+ || uri.startsWith("/site.webmanifest")
+ || uri.startsWith("/fonts")
+ || uri.startsWith("/pdfjs"))) {
- counter.increment();
- // System.out.println("Counted");
- }
+ Counter counter =
+ Counter.builder("http.requests")
+ .tag("uri", uri)
+ .tag("method", request.getMethod())
+ .register(meterRegistry);
- filterChain.doFilter(request, response);
- }
+ counter.increment();
+ // System.out.println("Counted");
+ }
+ filterChain.doFilter(request, response);
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java
index 5dba40d0..a852bb1a 100644
--- a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java
+++ b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java
@@ -9,34 +9,45 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
+
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
public class OpenApiConfig {
- @Autowired
- ApplicationProperties applicationProperties;
+ @Autowired ApplicationProperties applicationProperties;
- @Bean
- public OpenAPI customOpenAPI() {
- String version = getClass().getPackage().getImplementationVersion();
- if (version == null) {
- version = "1.0.0"; // default version if all else fails
- }
+ @Bean
+ public OpenAPI customOpenAPI() {
+ String version = getClass().getPackage().getImplementationVersion();
+ if (version == null) {
+ version = "1.0.0"; // default version if all else fails
+ }
- SecurityScheme apiKeyScheme = new SecurityScheme().type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER)
- .name("X-API-KEY");
- if (!applicationProperties.getSecurity().getEnableLogin()) {
- return new OpenAPI().components(new Components())
- .info(new Info().title("Stirling PDF API").version(version).description(
- "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
- } else {
- return new OpenAPI().components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
- .info(new Info().title("Stirling PDF API").version(version).description(
- "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."))
- .addSecurityItem(new SecurityRequirement().addList("apiKey"));
- }
-
- }
-
-}
\ No newline at end of file
+ SecurityScheme apiKeyScheme =
+ new SecurityScheme()
+ .type(SecurityScheme.Type.APIKEY)
+ .in(SecurityScheme.In.HEADER)
+ .name("X-API-KEY");
+ if (!applicationProperties.getSecurity().getEnableLogin()) {
+ return new OpenAPI()
+ .components(new Components())
+ .info(
+ new Info()
+ .title("Stirling PDF API")
+ .version(version)
+ .description(
+ "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
+ } else {
+ return new OpenAPI()
+ .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
+ .info(
+ new Info()
+ .title("Stirling PDF API")
+ .version(version)
+ .description(
+ "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."))
+ .addSecurityItem(new SecurityRequirement().addList("apiKey"));
+ }
+ }
+}
diff --git a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java
index 77b69c88..07644b30 100644
--- a/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java
+++ b/src/main/java/stirling/software/SPDF/config/StartupApplicationListener.java
@@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
-
import java.time.LocalDateTime;
import org.springframework.context.ApplicationListener;
@@ -17,4 +16,3 @@ public class StartupApplicationListener implements ApplicationListener createPropertySource(String name, EncodedResource encodedResource)
- throws IOException {
+ public PropertySource> createPropertySource(String name, EncodedResource encodedResource)
+ throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
- return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
+ return new PropertiesPropertySource(
+ encodedResource.getResource().getFilename(), properties);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java
index 397a8a70..6c2a05d3 100644
--- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java
+++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationFailureHandler.java
@@ -12,36 +12,38 @@ import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+
@Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
-
- @Autowired
- private final LoginAttemptService loginAttemptService;
+
+ @Autowired private final LoginAttemptService loginAttemptService;
@Autowired
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService;
}
-
+
@Override
- public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
- throws IOException, ServletException {
- String ip = request.getRemoteAddr();
+ public void onAuthenticationFailure(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ AuthenticationException exception)
+ throws IOException, ServletException {
+ String ip = request.getRemoteAddr();
logger.error("Failed login attempt from IP: " + ip);
-
+
String username = request.getParameter("username");
- if(loginAttemptService.loginAttemptCheck(username)) {
- setDefaultFailureUrl("/login?error=locked");
-
+ if (loginAttemptService.loginAttemptCheck(username)) {
+ setDefaultFailureUrl("/login?error=locked");
+
} else {
- if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
- setDefaultFailureUrl("/login?error=badcredentials");
- } else if (exception.getClass().isAssignableFrom(LockedException.class)) {
- setDefaultFailureUrl("/login?error=locked");
- }
+ if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
+ setDefaultFailureUrl("/login?error=badcredentials");
+ } else if (exception.getClass().isAssignableFrom(LockedException.class)) {
+ setDefaultFailureUrl("/login?error=locked");
+ }
}
-
-
+
super.onAuthenticationFailure(request, response, exception);
}
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java
index cd2217e1..d14466ea 100644
--- a/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java
+++ b/src/main/java/stirling/software/SPDF/config/security/CustomAuthenticationSuccessHandler.java
@@ -15,30 +15,33 @@ import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.utils.RequestUriUtils;
@Component
-public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
+public class CustomAuthenticationSuccessHandler
+ extends SavedRequestAwareAuthenticationSuccessHandler {
- @Autowired
- private LoginAttemptService loginAttemptService;
+ @Autowired private LoginAttemptService loginAttemptService;
@Override
- public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
- String username = request.getParameter("username");
+ public void onAuthenticationSuccess(
+ HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+ throws ServletException, IOException {
+ String username = request.getParameter("username");
loginAttemptService.loginSucceeded(username);
-
-
+
// Get the saved request
HttpSession session = request.getSession(false);
- SavedRequest savedRequest = session != null ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") : null;
- if (savedRequest != null && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
+ SavedRequest savedRequest =
+ session != null
+ ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
+ : null;
+ if (savedRequest != null
+ && !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
// Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication);
} else {
// Redirect to the root URL (considering context path)
getRedirectStrategy().sendRedirect(request, response, "/");
}
-
- //super.onAuthenticationSuccess(request, response, authentication);
- }
-
+ // super.onAuthenticationSuccess(request, response, authentication);
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java
index 77db2cd4..021cdc31 100644
--- a/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java
+++ b/src/main/java/stirling/software/SPDF/config/security/CustomUserDetailsService.java
@@ -20,33 +20,38 @@ import stirling.software.SPDF.repository.UserRepository;
@Service
public class CustomUserDetailsService implements UserDetailsService {
- @Autowired
- private UserRepository userRepository;
+ @Autowired private UserRepository userRepository;
+
+ @Autowired private LoginAttemptService loginAttemptService;
- @Autowired
- private LoginAttemptService loginAttemptService;
-
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- User user = userRepository.findByUsername(username)
- .orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username));
+ User user =
+ userRepository
+ .findByUsername(username)
+ .orElseThrow(
+ () ->
+ new UsernameNotFoundException(
+ "No user found with username: " + username));
if (loginAttemptService.isBlocked(username)) {
- throw new LockedException("Your account has been locked due to too many failed login attempts.");
+ throw new LockedException(
+ "Your account has been locked due to too many failed login attempts.");
}
-
+
return new org.springframework.security.core.userdetails.User(
- user.getUsername(),
- user.getPassword(),
- user.isEnabled(),
- true, true, true,
- getAuthorities(user.getAuthorities())
- );
+ user.getUsername(),
+ user.getPassword(),
+ user.isEnabled(),
+ true,
+ true,
+ true,
+ getAuthorities(user.getAuthorities()));
}
private Collection extends GrantedAuthority> getAuthorities(Set authorities) {
return authorities.stream()
- .map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
- .collect(Collectors.toList());
+ .map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
+ .collect(Collectors.toList());
}
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java
index 65acf148..b272327a 100644
--- a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java
+++ b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java
@@ -19,16 +19,16 @@ import stirling.software.SPDF.utils.RequestUriUtils;
@Component
public class FirstLoginFilter extends OncePerRequestFilter {
-
- @Autowired
- @Lazy
- private UserService userService;
-
+
+ @Autowired @Lazy private UserService userService;
+
@Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- String method = request.getMethod();
- String requestURI = request.getRequestURI();
- // Check if the request is for static resources
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ String method = request.getMethod();
+ String requestURI = request.getRequestURI();
+ // Check if the request is for static resources
boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI);
// If it's a static resource, just continue the filter chain and skip the logic below
@@ -36,11 +36,14 @@ public class FirstLoginFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
return;
}
-
+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
Optional user = userService.findByUsername(authentication.getName());
- if ("GET".equalsIgnoreCase(method) && user.isPresent() && user.get().isFirstLogin() && !"/change-creds".equals(requestURI)) {
+ if ("GET".equalsIgnoreCase(method)
+ && user.isPresent()
+ && user.get().isFirstLogin()
+ && !"/change-creds".equals(requestURI)) {
response.sendRedirect("/change-creds");
return;
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java
index 03e34b57..b79cd586 100644
--- a/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java
+++ b/src/main/java/stirling/software/SPDF/config/security/IPRateLimitingFilter.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.config.security;
+
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -13,51 +14,53 @@ import stirling.software.SPDF.utils.RequestUriUtils;
public class IPRateLimitingFilter implements Filter {
- private final ConcurrentHashMap requestCounts = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap requestCounts =
+ new ConcurrentHashMap<>();
private final ConcurrentHashMap getCounts = new ConcurrentHashMap<>();
private final int maxRequests;
private final int maxGetRequests;
-
+
public IPRateLimitingFilter(int maxRequests, int maxGetRequests) {
this.maxRequests = maxRequests;
this.maxGetRequests = maxGetRequests;
}
@Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- if (request instanceof HttpServletRequest) {
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- String method = httpRequest.getMethod();
- String requestURI = httpRequest.getRequestURI();
- // Check if the request is for static resources
- boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI);
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ if (request instanceof HttpServletRequest) {
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ String method = httpRequest.getMethod();
+ String requestURI = httpRequest.getRequestURI();
+ // Check if the request is for static resources
+ boolean isStaticResource = RequestUriUtils.isStaticResource(requestURI);
- // If it's a static resource, just continue the filter chain and skip the logic below
- if (isStaticResource) {
- chain.doFilter(request, response);
- return;
- }
-
- String clientIp = request.getRemoteAddr();
- requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0));
- if (!"GET".equalsIgnoreCase(method)) {
-
- if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) {
- // Handle limit exceeded (e.g., send error response)
- response.getWriter().write("Rate limit exceeded");
- return;
- }
- } else {
- if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) {
- // Handle limit exceeded (e.g., send error response)
- response.getWriter().write("GET Rate limit exceeded");
- return;
- }
- }
- }
- chain.doFilter(request, response);
+ // If it's a static resource, just continue the filter chain and skip the logic below
+ if (isStaticResource) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ String clientIp = request.getRemoteAddr();
+ requestCounts.computeIfAbsent(clientIp, k -> new AtomicInteger(0));
+ if (!"GET".equalsIgnoreCase(method)) {
+
+ if (requestCounts.get(clientIp).incrementAndGet() > maxRequests) {
+ // Handle limit exceeded (e.g., send error response)
+ response.getWriter().write("Rate limit exceeded");
+ return;
+ }
+ } else {
+ if (requestCounts.get(clientIp).incrementAndGet() > maxGetRequests) {
+ // Handle limit exceeded (e.g., send error response)
+ response.getWriter().write("GET Rate limit exceeded");
+ return;
+ }
+ }
+ }
+ chain.doFilter(request, response);
}
-
+
public void resetRequestCounts() {
requestCounts.clear();
getCounts.clear();
diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java
index 5d100dd8..3b396b15 100644
--- a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java
+++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java
@@ -13,75 +13,76 @@ import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Role;
+
@Component
public class InitialSecuritySetup {
- @Autowired
- private UserService userService;
+ @Autowired private UserService userService;
+ @Autowired ApplicationProperties applicationProperties;
- @Autowired
- ApplicationProperties applicationProperties;
-
- @PostConstruct
- public void init() {
- if (!userService.hasUsers()) {
-
-
- String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername();
- String initialPassword = applicationProperties.getSecurity().getInitialLogin().getPassword();
- if (initialUsername != null && initialPassword != null) {
- userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
- } else {
- initialUsername = "admin";
- initialPassword = "stirling";
- userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
- }
- }
- if(!userService.usernameExists(Role.INTERNAL_API_USER.getRoleId())) {
- userService.saveUser(Role.INTERNAL_API_USER.getRoleId(), UUID.randomUUID().toString(), Role.INTERNAL_API_USER.getRoleId());
- userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
- }
- }
+ @PostConstruct
+ public void init() {
+ if (!userService.hasUsers()) {
+ String initialUsername =
+ applicationProperties.getSecurity().getInitialLogin().getUsername();
+ String initialPassword =
+ applicationProperties.getSecurity().getInitialLogin().getPassword();
+ if (initialUsername != null && initialPassword != null) {
+ userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
+ } else {
+ initialUsername = "admin";
+ initialPassword = "stirling";
+ userService.saveUser(
+ initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
+ }
+ }
+ if (!userService.usernameExists(Role.INTERNAL_API_USER.getRoleId())) {
+ userService.saveUser(
+ Role.INTERNAL_API_USER.getRoleId(),
+ UUID.randomUUID().toString(),
+ Role.INTERNAL_API_USER.getRoleId());
+ userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
+ }
+ }
+ @PostConstruct
+ public void initSecretKey() throws IOException {
+ String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
+ if (secretKey == null || secretKey.isEmpty()) {
+ secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
+ saveKeyToConfig(secretKey);
+ }
+ }
- @PostConstruct
- public void initSecretKey() throws IOException {
- String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
- if (secretKey == null || secretKey.isEmpty()) {
- secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
- saveKeyToConfig(secretKey);
- }
- }
+ private void saveKeyToConfig(String key) throws IOException {
+ Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
+ List lines = Files.readAllLines(path);
+ boolean keyFound = false;
- private void saveKeyToConfig(String key) throws IOException {
- Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
- List lines = Files.readAllLines(path);
- boolean keyFound = false;
+ // Search for the existing key to replace it or place to add it
+ for (int i = 0; i < lines.size(); i++) {
+ if (lines.get(i).startsWith("AutomaticallyGenerated:")) {
+ keyFound = true;
+ if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) {
+ lines.set(i + 1, " key: " + key);
+ break;
+ } else {
+ lines.add(i + 1, " key: " + key);
+ break;
+ }
+ }
+ }
- // Search for the existing key to replace it or place to add it
- for (int i = 0; i < lines.size(); i++) {
- if (lines.get(i).startsWith("AutomaticallyGenerated:")) {
- keyFound = true;
- if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) {
- lines.set(i + 1, " key: " + key);
- break;
- } else {
- lines.add(i + 1, " key: " + key);
- break;
- }
- }
- }
+ // If the section doesn't exist, append it
+ if (!keyFound) {
+ lines.add("# Automatically Generated Settings (Do Not Edit Directly)");
+ lines.add("AutomaticallyGenerated:");
+ lines.add(" key: " + key);
+ }
- // If the section doesn't exist, append it
- if (!keyFound) {
- lines.add("# Automatically Generated Settings (Do Not Edit Directly)");
- lines.add("AutomaticallyGenerated:");
- lines.add(" key: " + key);
- }
-
- // Write back to the file
- Files.write(path, lines);
- }
-}
\ No newline at end of file
+ // Write back to the file
+ Files.write(path, lines);
+ }
+}
diff --git a/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java b/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java
index 55a84449..40a54ecc 100644
--- a/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java
+++ b/src/main/java/stirling/software/SPDF/config/security/LoginAttemptService.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.config.security;
+
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -12,39 +13,41 @@ import stirling.software.SPDF.model.AttemptCounter;
@Service
public class LoginAttemptService {
-
- @Autowired
- ApplicationProperties applicationProperties;
-
+ @Autowired ApplicationProperties applicationProperties;
+
private int MAX_ATTEMPTS;
private long ATTEMPT_INCREMENT_TIME;
-
-
+
@PostConstruct
public void init() {
- MAX_ATTEMPTS = applicationProperties.getSecurity().getLoginAttemptCount();
- ATTEMPT_INCREMENT_TIME = TimeUnit.MINUTES.toMillis(applicationProperties.getSecurity().getLoginResetTimeMinutes());
+ MAX_ATTEMPTS = applicationProperties.getSecurity().getLoginAttemptCount();
+ ATTEMPT_INCREMENT_TIME =
+ TimeUnit.MINUTES.toMillis(
+ applicationProperties.getSecurity().getLoginResetTimeMinutes());
}
-
- private final ConcurrentHashMap attemptsCache = new ConcurrentHashMap<>();
+
+ private final ConcurrentHashMap attemptsCache =
+ new ConcurrentHashMap<>();
public void loginSucceeded(String key) {
attemptsCache.remove(key);
}
public boolean loginAttemptCheck(String key) {
- attemptsCache.compute(key, (k, attemptCounter) -> {
- if (attemptCounter == null || attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
- return new AttemptCounter();
- } else {
- attemptCounter.increment();
- return attemptCounter;
- }
- });
+ attemptsCache.compute(
+ key,
+ (k, attemptCounter) -> {
+ if (attemptCounter == null
+ || attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {
+ return new AttemptCounter();
+ } else {
+ attemptCounter.increment();
+ return attemptCounter;
+ }
+ });
return attemptsCache.get(key).getAttemptCount() >= MAX_ATTEMPTS;
}
-
public boolean isBlocked(String key) {
AttemptCounter attemptCounter = attemptsCache.get(key);
if (attemptCounter != null) {
@@ -52,5 +55,4 @@ public class LoginAttemptService {
}
return false;
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java b/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java
index 3ef8ef31..a3641a70 100644
--- a/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java
+++ b/src/main/java/stirling/software/SPDF/config/security/RateLimitResetScheduler.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.config.security;
+
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -11,7 +12,7 @@ public class RateLimitResetScheduler {
this.rateLimitingFilter = rateLimitingFilter;
}
- @Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday TODO: configurable
+ @Scheduled(cron = "0 0 0 * * MON") // At 00:00 every Monday TODO: configurable
public void resetRateLimit() {
rateLimitingFilter.resetRequestCounts();
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java
index e0b439db..bf1e3661 100644
--- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java
+++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java
@@ -19,104 +19,112 @@ import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
+
@Configuration
@EnableWebSecurity()
@EnableMethodSecurity
public class SecurityConfiguration {
- @Autowired
- private UserDetailsService userDetailsService;
+ @Autowired private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- @Autowired
- @Lazy
- private UserService userService;
-
+
+ @Autowired @Lazy private UserService userService;
+
@Autowired
@Qualifier("loginEnabled")
public boolean loginEnabledValue;
-
- @Autowired
- private UserAuthenticationFilter userAuthenticationFilter;
+ @Autowired private UserAuthenticationFilter userAuthenticationFilter;
+ @Autowired private LoginAttemptService loginAttemptService;
+
+ @Autowired private FirstLoginFilter firstLoginFilter;
- @Autowired
- private LoginAttemptService loginAttemptService;
-
- @Autowired
- private FirstLoginFilter firstLoginFilter;
-
@Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
-
- if(loginEnabledValue) {
-
- http.csrf(csrf -> csrf.disable());
- http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
- http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
- http
- .formLogin(formLogin -> formLogin
- .loginPage("/login")
- .successHandler(new CustomAuthenticationSuccessHandler())
- .defaultSuccessUrl("/")
- .failureHandler(new CustomAuthenticationFailureHandler(loginAttemptService))
- .permitAll()
- ).requestCache(requestCache -> requestCache
- .requestCache(new NullRequestCache())
- )
- .logout(logout -> logout
- .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
- .logoutSuccessUrl("/login?logout=true")
- .invalidateHttpSession(true) // Invalidate session
- .deleteCookies("JSESSIONID", "remember-me")
- ).rememberMe(rememberMeConfigurer -> rememberMeConfigurer // Use the configurator directly
- .key("uniqueAndSecret")
- .tokenRepository(persistentTokenRepository())
- .tokenValiditySeconds(1209600) // 2 weeks
- )
- .authorizeHttpRequests(authz -> authz
- .requestMatchers(req -> {
- String uri = req.getRequestURI();
- String contextPath = req.getContextPath();
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
- // Remove the context path from the URI
- String trimmedUri = uri.startsWith(contextPath) ? uri.substring(contextPath.length()) : uri;
+ if (loginEnabledValue) {
+
+ http.csrf(csrf -> csrf.disable());
+ http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
+ http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
+ http.formLogin(
+ formLogin ->
+ formLogin
+ .loginPage("/login")
+ .successHandler(
+ new CustomAuthenticationSuccessHandler())
+ .defaultSuccessUrl("/")
+ .failureHandler(
+ new CustomAuthenticationFailureHandler(
+ loginAttemptService))
+ .permitAll())
+ .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
+ .logout(
+ logout ->
+ logout.logoutRequestMatcher(
+ new AntPathRequestMatcher("/logout"))
+ .logoutSuccessUrl("/login?logout=true")
+ .invalidateHttpSession(true) // Invalidate session
+ .deleteCookies("JSESSIONID", "remember-me"))
+ .rememberMe(
+ rememberMeConfigurer ->
+ rememberMeConfigurer // Use the configurator directly
+ .key("uniqueAndSecret")
+ .tokenRepository(persistentTokenRepository())
+ .tokenValiditySeconds(1209600) // 2 weeks
+ )
+ .authorizeHttpRequests(
+ authz ->
+ authz.requestMatchers(
+ req -> {
+ String uri = req.getRequestURI();
+ String contextPath = req.getContextPath();
+
+ // Remove the context path from the URI
+ String trimmedUri =
+ uri.startsWith(contextPath)
+ ? uri.substring(
+ contextPath
+ .length())
+ : uri;
+
+ return trimmedUri.startsWith("/login")
+ || trimmedUri.endsWith(".svg")
+ || trimmedUri.startsWith(
+ "/register")
+ || trimmedUri.startsWith("/error")
+ || trimmedUri.startsWith("/images/")
+ || trimmedUri.startsWith("/public/")
+ || trimmedUri.startsWith("/css/")
+ || trimmedUri.startsWith("/js/")
+ || trimmedUri.startsWith(
+ "/api/v1/info/status");
+ })
+ .permitAll()
+ .anyRequest()
+ .authenticated())
+ .userDetailsService(userDetailsService)
+ .authenticationProvider(authenticationProvider());
+ } else {
+ http.csrf(csrf -> csrf.disable())
+ .authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
+ }
- return trimmedUri.startsWith("/login") || trimmedUri.endsWith(".svg") ||
- trimmedUri.startsWith("/register") || trimmedUri.startsWith("/error") ||
- trimmedUri.startsWith("/images/") || trimmedUri.startsWith("/public/") ||
- trimmedUri.startsWith("/css/") || trimmedUri.startsWith("/js/");
- }
- ).permitAll()
- .anyRequest().authenticated()
- )
- .userDetailsService(userDetailsService)
- .authenticationProvider(authenticationProvider());
- } else {
- http.csrf(csrf -> csrf.disable())
- .authorizeHttpRequests(authz -> authz
- .anyRequest().permitAll()
- );
- }
return http.build();
}
-
-
-
@Bean
public IPRateLimitingFilter rateLimitingFilter() {
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
}
-
-
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
@@ -124,13 +132,9 @@ public class SecurityConfiguration {
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
-
+
@Bean
public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl();
}
-
-
-
}
-
diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java
index ce77e5a4..d12fc72b 100644
--- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java
+++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java
@@ -19,32 +19,29 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
+
@Component
public class UserAuthenticationFilter extends OncePerRequestFilter {
- @Autowired
- private UserDetailsService userDetailsService;
+ @Autowired private UserDetailsService userDetailsService;
+
+ @Autowired @Lazy private UserService userService;
- @Autowired
- @Lazy
- private UserService userService;
-
-
@Autowired
@Qualifier("loginEnabled")
public boolean loginEnabledValue;
@Override
- protected void doFilterInternal(HttpServletRequest request,
- HttpServletResponse response,
- FilterChain filterChain) throws ServletException, IOException {
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
if (!loginEnabledValue) {
// If login is not enabled, just pass all requests without authentication
filterChain.doFilter(request, response);
return;
}
- String requestURI = request.getRequestURI();
+ String requestURI = request.getRequestURI();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// Check for API key in the request headers if no authentication exists
@@ -52,15 +49,17 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
String apiKey = request.getHeader("X-API-Key");
if (apiKey != null && !apiKey.trim().isEmpty()) {
try {
- // Use API key to authenticate. This requires you to have an authentication provider for API keys.
- UserDetails userDetails = userService.loadUserByApiKey(apiKey);
- if(userDetails == null)
- {
- response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ // Use API key to authenticate. This requires you to have an authentication
+ // provider for API keys.
+ UserDetails userDetails = userService.loadUserByApiKey(apiKey);
+ if (userDetails == null) {
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid API Key.");
return;
- }
- authentication = new ApiKeyAuthenticationToken(userDetails, apiKey, userDetails.getAuthorities());
+ }
+ authentication =
+ new ApiKeyAuthenticationToken(
+ userDetails, apiKey, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) {
// If API key authentication fails, deny the request
@@ -73,36 +72,39 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// If we still don't have any authentication, deny the request
if (authentication == null || !authentication.isAuthenticated()) {
- String method = request.getMethod();
- String contextPath = request.getContextPath();
-
- if ("GET".equalsIgnoreCase(method) && ! (contextPath + "/login").equals(requestURI)) {
- response.sendRedirect(contextPath + "/login"); // redirect to the login page
- return;
+ String method = request.getMethod();
+ String contextPath = request.getContextPath();
+
+ if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) {
+ response.sendRedirect(contextPath + "/login"); // redirect to the login page
+ return;
} else {
- response.setStatus(HttpStatus.UNAUTHORIZED.value());
- response.getWriter().write("Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected");
- return;
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ response.getWriter()
+ .write(
+ "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected");
+ return;
}
- }
+ }
filterChain.doFilter(request, response);
}
-
+
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String uri = request.getRequestURI();
String contextPath = request.getContextPath();
String[] permitAllPatterns = {
- contextPath + "/login",
- contextPath + "/register",
- contextPath + "/error",
- contextPath + "/images/",
- contextPath + "/public/",
- contextPath + "/css/",
- contextPath + "/js/",
- contextPath + "/pdfjs/",
- contextPath + "/site.webmanifest"
+ contextPath + "/login",
+ contextPath + "/register",
+ contextPath + "/error",
+ contextPath + "/images/",
+ contextPath + "/public/",
+ contextPath + "/css/",
+ contextPath + "/js/",
+ contextPath + "/pdfjs/",
+ contextPath + "/api/v1/info/status",
+ contextPath + "/site.webmanifest"
};
for (String pattern : permitAllPatterns) {
@@ -113,5 +115,4 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
return false;
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java
index f23e5ce3..6c315971 100644
--- a/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java
+++ b/src/main/java/stirling/software/SPDF/config/security/UserBasedRateLimitingFilter.java
@@ -20,28 +20,29 @@ import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import io.github.bucket4j.Refill;
+
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.Role;
+
@Component
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
- private final Map apiBuckets = new ConcurrentHashMap<>();
+ private final Map apiBuckets = new ConcurrentHashMap<>();
private final Map webBuckets = new ConcurrentHashMap<>();
- @Autowired
- private UserDetailsService userDetailsService;
+ @Autowired private UserDetailsService userDetailsService;
@Autowired
@Qualifier("rateLimit")
public boolean rateLimit;
@Override
- protected void doFilterInternal(HttpServletRequest request,
- HttpServletResponse response,
- FilterChain filterChain) throws ServletException, IOException {
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
if (!rateLimit) {
// If rateLimit is not enabled, just pass all requests without rate limiting
filterChain.doFilter(request, response);
@@ -60,7 +61,8 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
// Check for API key in the request headers
String apiKey = request.getHeader("X-API-Key");
if (apiKey != null && !apiKey.trim().isEmpty()) {
- identifier = "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
+ identifier =
+ "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
} else {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
@@ -74,14 +76,27 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
identifier = request.getRemoteAddr();
}
- Role userRole = getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
+ Role userRole =
+ getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (request.getHeader("X-API-Key") != null) {
// It's an API call
- processRequest(userRole.getApiCallsPerDay(), identifier, apiBuckets, request, response, filterChain);
+ processRequest(
+ userRole.getApiCallsPerDay(),
+ identifier,
+ apiBuckets,
+ request,
+ response,
+ filterChain);
} else {
// It's a Web UI call
- processRequest(userRole.getWebCallsPerDay(), identifier, webBuckets, request, response, filterChain);
+ processRequest(
+ userRole.getWebCallsPerDay(),
+ identifier,
+ webBuckets,
+ request,
+ response,
+ filterChain);
}
}
@@ -98,8 +113,13 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
throw new IllegalStateException("User does not have a valid role.");
}
- private void processRequest(int limitPerDay, String identifier, Map buckets,
- HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ private void processRequest(
+ int limitPerDay,
+ String identifier,
+ Map buckets,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain)
throws IOException, ServletException {
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
@@ -116,10 +136,8 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
}
private Bucket createUserBucket(int limitPerDay) {
- Bandwidth limit = Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
+ Bandwidth limit =
+ Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
return Bucket.builder().addLimit(limit).build();
}
}
-
-
-
diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java
index 45794d92..986bb16f 100644
--- a/src/main/java/stirling/software/SPDF/config/security/UserService.java
+++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.config.security;
+
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -21,38 +22,35 @@ import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.UserRepository;
-@Service
-public class UserService implements UserServiceInterface{
-
- @Autowired
- private UserRepository userRepository;
- @Autowired
- private PasswordEncoder passwordEncoder;
+@Service
+public class UserService implements UserServiceInterface {
+
+ @Autowired private UserRepository userRepository;
+
+ @Autowired private PasswordEncoder passwordEncoder;
public Authentication getAuthentication(String apiKey) {
User user = getUserByApiKey(apiKey);
if (user == null) {
throw new UsernameNotFoundException("API key is not valid");
}
-
+
// Convert the user into an Authentication object
return new UsernamePasswordAuthenticationToken(
- user, // principal (typically the user)
- null, // credentials (we don't expose the password or API key here)
- getAuthorities(user) // user's authorities (roles/permissions)
- );
+ user, // principal (typically the user)
+ null, // credentials (we don't expose the password or API key here)
+ getAuthorities(user) // user's authorities (roles/permissions)
+ );
}
-
+
private Collection extends GrantedAuthority> getAuthorities(User user) {
// Convert each Authority object into a SimpleGrantedAuthority object.
- return user.getAuthorities().stream()
- .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
- .collect(Collectors.toList());
-
-
+ return user.getAuthorities().stream()
+ .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
+ .collect(Collectors.toList());
}
-
+
private String generateApiKey() {
String apiKey;
do {
@@ -62,9 +60,11 @@ public class UserService implements UserServiceInterface{
}
public User addApiKeyToUser(String username) {
- User user = userRepository.findByUsername(username)
- .orElseThrow(() -> new UsernameNotFoundException("User not found"));
-
+ User user =
+ userRepository
+ .findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+
user.setApiKey(generateApiKey());
return userRepository.save(user);
}
@@ -74,8 +74,10 @@ public class UserService implements UserServiceInterface{
}
public String getApiKeyForUser(String username) {
- User user = userRepository.findByUsername(username)
- .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+ User user =
+ userRepository
+ .findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
return user.getApiKey();
}
@@ -86,27 +88,25 @@ public class UserService implements UserServiceInterface{
public User getUserByApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey);
}
-
+
public UserDetails loadUserByApiKey(String apiKey) {
User userOptional = userRepository.findByApiKey(apiKey);
if (userOptional != null) {
User user = userOptional;
// Convert your User entity to a UserDetails object with authorities
return new org.springframework.security.core.userdetails.User(
- user.getUsername(),
- user.getPassword(), // you might not need this for API key auth
- getAuthorities(user)
- );
+ user.getUsername(),
+ user.getPassword(), // you might not need this for API key auth
+ getAuthorities(user));
}
- return null; // or throw an exception
+ return null; // or throw an exception
}
-
public boolean validateApiKeyForUser(String username, String apiKey) {
Optional userOpt = userRepository.findByUsername(username);
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
}
-
+
public void saveUser(String username, String password) {
User user = new User();
user.setUsername(username);
@@ -124,7 +124,7 @@ public class UserService implements UserServiceInterface{
user.setFirstLogin(firstLogin);
userRepository.save(user);
}
-
+
public void saveUser(String username, String password, String role) {
User user = new User();
user.setUsername(username);
@@ -134,42 +134,42 @@ public class UserService implements UserServiceInterface{
user.setFirstLogin(false);
userRepository.save(user);
}
-
+
public void deleteUser(String username) {
- Optional userOpt = userRepository.findByUsername(username);
- if (userOpt.isPresent()) {
- for (Authority authority : userOpt.get().getAuthorities()) {
- if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
- return;
- }
- }
- userRepository.delete(userOpt.get());
- }
+ Optional userOpt = userRepository.findByUsername(username);
+ if (userOpt.isPresent()) {
+ for (Authority authority : userOpt.get().getAuthorities()) {
+ if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
+ return;
+ }
+ }
+ userRepository.delete(userOpt.get());
+ }
}
-
+
public boolean usernameExists(String username) {
return userRepository.findByUsername(username).isPresent();
}
-
+
public boolean hasUsers() {
return userRepository.count() > 0;
}
-
+
public void updateUserSettings(String username, Map updates) {
Optional userOpt = userRepository.findByUsername(username);
if (userOpt.isPresent()) {
User user = userOpt.get();
Map settingsMap = user.getSettings();
- if(settingsMap == null) {
- settingsMap = new HashMap();
- }
+ if (settingsMap == null) {
+ settingsMap = new HashMap();
+ }
settingsMap.clear();
settingsMap.putAll(updates);
user.setSettings(settingsMap);
userRepository.save(user);
- }
+ }
}
public Optional findByUsername(String username) {
@@ -185,13 +185,12 @@ public class UserService implements UserServiceInterface{
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
-
+
public void changeFirstUse(User user, boolean firstUse) {
user.setFirstLogin(firstUse);
userRepository.save(user);
}
-
-
+
public boolean isPasswordCorrect(User user, String currentPassword) {
return passwordEncoder.matches(currentPassword, user.getPassword());
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/CropController.java b/src/main/java/stirling/software/SPDF/controller/api/CropController.java
index d2723cce..a547d89f 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/CropController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/CropController.java
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -28,59 +29,62 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class CropController {
- private static final Logger logger = LoggerFactory.getLogger(CropController.class);
+ 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 cropPdf(@ModelAttribute CropPdfForm form)
- throws IOException {
+ @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 cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
+ PDDocument sourceDocument =
+ PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()));
-
+ PDDocument newDocument = new PDDocument();
-PDDocument sourceDocument = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()));
+ int totalPages = sourceDocument.getNumberOfPages();
-PDDocument newDocument = new PDDocument();
+ LayerUtility layerUtility = new LayerUtility(newDocument);
-int totalPages = sourceDocument.getNumberOfPages();
+ for (int i = 0; i < totalPages; i++) {
+ PDPage sourcePage = sourceDocument.getPage(i);
-LayerUtility layerUtility = new LayerUtility(newDocument);
+ // Create a new page with the size of the source page
+ PDPage newPage = new PDPage(sourcePage.getMediaBox());
+ newDocument.addPage(newPage);
+ PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
-for (int i = 0; i < totalPages; i++) {
- PDPage sourcePage = sourceDocument.getPage(i);
-
- // Create a new page with the size of the source page
- PDPage newPage = new PDPage(sourcePage.getMediaBox());
- newDocument.addPage(newPage);
- PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
+ // Import the source page as a form XObject
+ PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
- // Import the source page as a form XObject
- PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
+ contentStream.saveGraphicsState();
- contentStream.saveGraphicsState();
-
- // Define the crop area
- contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight());
- contentStream.clip();
+ // Define the crop area
+ contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight());
+ contentStream.clip();
- // Draw the entire formXObject
- contentStream.drawForm(formXObject);
+ // Draw the entire formXObject
+ contentStream.drawForm(formXObject);
- contentStream.restoreGraphicsState();
-
- contentStream.close();
-
- // Now, set the new page's media box to the cropped size
- newPage.setMediaBox(new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight()));
-}
-
-ByteArrayOutputStream baos = new ByteArrayOutputStream();
-newDocument.save(baos);
-newDocument.close();
-sourceDocument.close();
-
-byte[] pdfContent = baos.toByteArray();
-return WebResponseUtils.bytesToWebResponse(pdfContent, form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf");
- }
+ contentStream.restoreGraphicsState();
+ contentStream.close();
+
+ // Now, set the new page's media box to the cropped size
+ newPage.setMediaBox(
+ new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight()));
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ newDocument.save(baos);
+ newDocument.close();
+ sourceDocument.close();
+
+ byte[] pdfContent = baos.toByteArray();
+ return WebResponseUtils.bytesToWebResponse(
+ pdfContent,
+ form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ + "_cropped.pdf");
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java
index 7db00a31..70b79191 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java
@@ -1,7 +1,15 @@
package stirling.software.SPDF.controller.api;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -14,19 +22,13 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@@ -34,7 +36,6 @@ public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
-
private PDDocument mergeDocuments(List documents) throws IOException {
PDDocument mergedDoc = new PDDocument();
for (PDDocument doc : documents) {
@@ -52,27 +53,39 @@ public class MergeController {
case "byDateModified":
return (file1, file2) -> {
try {
- BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class);
- BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class);
+ BasicFileAttributes attr1 =
+ Files.readAttributes(
+ Paths.get(file1.getOriginalFilename()),
+ BasicFileAttributes.class);
+ BasicFileAttributes attr2 =
+ Files.readAttributes(
+ Paths.get(file2.getOriginalFilename()),
+ BasicFileAttributes.class);
return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime());
} catch (IOException e) {
- return 0; // If there's an error, treat them as equal
+ return 0; // If there's an error, treat them as equal
}
};
case "byDateCreated":
return (file1, file2) -> {
try {
- BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class);
- BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class);
+ BasicFileAttributes attr1 =
+ Files.readAttributes(
+ Paths.get(file1.getOriginalFilename()),
+ BasicFileAttributes.class);
+ BasicFileAttributes attr2 =
+ Files.readAttributes(
+ Paths.get(file2.getOriginalFilename()),
+ BasicFileAttributes.class);
return attr1.creationTime().compareTo(attr2.creationTime());
} catch (IOException e) {
- return 0; // If there's an error, treat them as equal
+ return 0; // If there's an error, treat them as equal
}
};
case "byPDFTitle":
return (file1, file2) -> {
try (PDDocument doc1 = PDDocument.load(file1.getInputStream());
- PDDocument doc2 = PDDocument.load(file2.getInputStream())) {
+ PDDocument doc2 = PDDocument.load(file2.getInputStream())) {
String title1 = doc1.getDocumentInformation().getTitle();
String title2 = doc2.getDocumentInformation().getTitle();
return title1.compareTo(title2);
@@ -82,14 +95,17 @@ public class MergeController {
};
case "orderProvided":
default:
- return (file1, file2) -> 0; // Default is the order provided
+ return (file1, file2) -> 0; // Default is the order provided
}
}
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
- @Operation(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. Input:PDF Output:PDF Type:MISO")
- public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest form) throws IOException {
+ @Operation(
+ 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. Input:PDF Output:PDF Type:MISO")
+ public ResponseEntity mergePdfs(@ModelAttribute MergePdfsRequest form)
+ throws IOException {
try {
MultipartFile[] files = form.getFileInput();
Arrays.sort(files, getSortComparator(form.getSortType()));
@@ -101,14 +117,16 @@ public class MergeController {
mergedDoc.addSource(new ByteArrayInputStream(file.getBytes()));
}
- mergedDoc.setDestinationFileName(files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
+ mergedDoc.setDestinationFileName(
+ files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
mergedDoc.setDestinationStream(docOutputstream);
mergedDoc.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
- return WebResponseUtils.bytesToWebResponse(docOutputstream.toByteArray(), mergedDoc.getDestinationFileName());
+ return WebResponseUtils.bytesToWebResponse(
+ docOutputstream.toByteArray(), mergedDoc.getDestinationFileName());
} catch (Exception ex) {
logger.error("Error in merge pdf process", ex);
- throw ex;
+ throw ex;
}
}
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java
index ebe81ffd..52127571 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/MultiPageLayoutController.java
@@ -1,6 +1,5 @@
package stirling.software.SPDF.controller.api;
-
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -23,6 +22,7 @@ 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.model.api.general.MergeMultiplePagesRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -31,94 +31,110 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
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")
- @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 mergeMultiplePagesIntoOne(@ModelAttribute MergeMultiplePagesRequest request)
- throws IOException {
+ @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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity mergeMultiplePagesIntoOne(
+ @ModelAttribute MergeMultiplePagesRequest request) throws IOException {
- int pagesPerSheet = request.getPagesPerSheet();
- MultipartFile file = request.getFileInput();
- boolean addBorder = request.isAddBorder();
-
- if (pagesPerSheet != 2 && pagesPerSheet != 3 && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {
- throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square");
- }
+ int pagesPerSheet = request.getPagesPerSheet();
+ MultipartFile file = request.getFileInput();
+ boolean addBorder = request.isAddBorder();
- int cols = pagesPerSheet == 2 || pagesPerSheet == 3 ? pagesPerSheet : (int) Math.sqrt(pagesPerSheet);
- int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
+ if (pagesPerSheet != 2
+ && pagesPerSheet != 3
+ && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {
+ throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square");
+ }
- PDDocument sourceDocument = PDDocument.load(file.getInputStream());
- PDDocument newDocument = new PDDocument();
- PDPage newPage = new PDPage(PDRectangle.A4);
- newDocument.addPage(newPage);
+ int cols =
+ pagesPerSheet == 2 || pagesPerSheet == 3
+ ? pagesPerSheet
+ : (int) Math.sqrt(pagesPerSheet);
+ int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
- int totalPages = sourceDocument.getNumberOfPages();
- float cellWidth = newPage.getMediaBox().getWidth() / cols;
- float cellHeight = newPage.getMediaBox().getHeight() / rows;
+ PDDocument sourceDocument = PDDocument.load(file.getInputStream());
+ PDDocument newDocument = new PDDocument();
+ PDPage newPage = new PDPage(PDRectangle.A4);
+ newDocument.addPage(newPage);
- PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
- LayerUtility layerUtility = new LayerUtility(newDocument);
+ int totalPages = sourceDocument.getNumberOfPages();
+ float cellWidth = newPage.getMediaBox().getWidth() / cols;
+ float cellHeight = newPage.getMediaBox().getHeight() / rows;
- float borderThickness = 1.5f; // Specify border thickness as required
- contentStream.setLineWidth(borderThickness);
- contentStream.setStrokingColor(Color.BLACK);
-
- for (int i = 0; i < totalPages; i++) {
- if (i != 0 && i % pagesPerSheet == 0) {
- // Close the current content stream and create a new page and content stream
- contentStream.close();
- newPage = new PDPage(PDRectangle.A4);
- newDocument.addPage(newPage);
- contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
- }
+ PDPageContentStream contentStream =
+ new PDPageContentStream(
+ newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
+ LayerUtility layerUtility = new LayerUtility(newDocument);
- PDPage sourcePage = sourceDocument.getPage(i);
- PDRectangle rect = sourcePage.getMediaBox();
- float scaleWidth = cellWidth / rect.getWidth();
- float scaleHeight = cellHeight / rect.getHeight();
- float scale = Math.min(scaleWidth, scaleHeight);
+ float borderThickness = 1.5f; // Specify border thickness as required
+ contentStream.setLineWidth(borderThickness);
+ contentStream.setStrokingColor(Color.BLACK);
- int adjustedPageIndex = i % pagesPerSheet; // This will reset the index for every new page
- int rowIndex = adjustedPageIndex / cols;
- int colIndex = adjustedPageIndex % cols;
+ for (int i = 0; i < totalPages; i++) {
+ if (i != 0 && i % pagesPerSheet == 0) {
+ // Close the current content stream and create a new page and content stream
+ contentStream.close();
+ newPage = new PDPage(PDRectangle.A4);
+ newDocument.addPage(newPage);
+ contentStream =
+ new PDPageContentStream(
+ newDocument,
+ newPage,
+ PDPageContentStream.AppendMode.APPEND,
+ true,
+ true);
+ }
- float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
- float y = newPage.getMediaBox().getHeight() - ((rowIndex + 1) * cellHeight - (cellHeight - rect.getHeight() * scale) / 2);
+ PDPage sourcePage = sourceDocument.getPage(i);
+ PDRectangle rect = sourcePage.getMediaBox();
+ float scaleWidth = cellWidth / rect.getWidth();
+ float scaleHeight = cellHeight / rect.getHeight();
+ float scale = Math.min(scaleWidth, scaleHeight);
- contentStream.saveGraphicsState();
- contentStream.transform(Matrix.getTranslateInstance(x, y));
- contentStream.transform(Matrix.getScaleInstance(scale, scale));
+ int adjustedPageIndex =
+ i % pagesPerSheet; // This will reset the index for every new page
+ int rowIndex = adjustedPageIndex / cols;
+ int colIndex = adjustedPageIndex % cols;
- PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
- contentStream.drawForm(formXObject);
+ float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
+ float y =
+ newPage.getMediaBox().getHeight()
+ - ((rowIndex + 1) * cellHeight
+ - (cellHeight - rect.getHeight() * scale) / 2);
- contentStream.restoreGraphicsState();
-
- if(addBorder) {
- // Draw border around each page
- float borderX = colIndex * cellWidth;
- float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight;
- contentStream.addRect(borderX, borderY, cellWidth, cellHeight);
- contentStream.stroke();
- }
- }
+ contentStream.saveGraphicsState();
+ contentStream.transform(Matrix.getTranslateInstance(x, y));
+ contentStream.transform(Matrix.getScaleInstance(scale, scale));
+ PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
+ contentStream.drawForm(formXObject);
- contentStream.close(); // Close the final content stream
- sourceDocument.close();
+ contentStream.restoreGraphicsState();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- newDocument.save(baos);
- newDocument.close();
+ if (addBorder) {
+ // Draw border around each page
+ float borderX = colIndex * cellWidth;
+ float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight;
+ contentStream.addRect(borderX, borderY, cellWidth, cellHeight);
+ contentStream.stroke();
+ }
+ }
- byte[] result = baos.toByteArray();
- return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf");
- }
+ contentStream.close(); // Close the final content stream
+ sourceDocument.close();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ newDocument.save(baos);
+ newDocument.close();
+ byte[] result = baos.toByteArray();
+ return WebResponseUtils.bytesToWebResponse(
+ result,
+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf");
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java
index 9551754a..f6099c3a 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java
@@ -1,11 +1,13 @@
package stirling.software.SPDF.controller.api;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.ArrayList;
+
import org.apache.pdfbox.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType;
@@ -18,36 +20,49 @@ 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.model.api.general.OverlayPdfsRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
public class PdfOverlayController {
- @PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
- @Operation(summary = "Overlay PDF files in various modes", description = "Overlay PDF files onto a base PDF with different modes: Sequential, Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO")
- public ResponseEntity overlayPdfs(@ModelAttribute OverlayPdfsRequest request) throws IOException {
+ @PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
+ @Operation(
+ summary = "Overlay PDF files in various modes",
+ description =
+ "Overlay PDF files onto a base PDF with different modes: Sequential, Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO")
+ public ResponseEntity overlayPdfs(@ModelAttribute OverlayPdfsRequest request)
+ throws IOException {
MultipartFile baseFile = request.getFileInput();
int overlayPos = request.getOverlayPosition();
-
+
MultipartFile[] overlayFiles = request.getOverlayFiles();
File[] overlayPdfFiles = new File[overlayFiles.length];
List tempFiles = new ArrayList<>(); // List to keep track of temporary files
- try {
+ try {
for (int i = 0; i < overlayFiles.length; i++) {
overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]);
}
-
- String mode = request.getOverlayMode(); // "SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay"
+
+ String mode = request.getOverlayMode(); // "SequentialOverlay", "InterleavedOverlay",
+ // "FixedRepeatOverlay"
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream());
Overlay overlay = new Overlay()) {
- Map overlayGuide = prepareOverlayGuide(basePdf.getNumberOfPages(), overlayPdfFiles, mode, counts, tempFiles);
-
+ Map overlayGuide =
+ prepareOverlayGuide(
+ basePdf.getNumberOfPages(),
+ overlayPdfFiles,
+ mode,
+ counts,
+ tempFiles);
+
overlay.setInputPDF(basePdf);
if (overlayPos == 0) {
overlay.setOverlayPosition(Overlay.Position.FOREGROUND);
@@ -58,10 +73,13 @@ public class PdfOverlayController {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
overlay.overlay(overlayGuide).save(outputStream);
byte[] data = outputStream.toByteArray();
- String outputFilename = baseFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf"; // Remove file extension and append .pdf
-
- return WebResponseUtils.bytesToWebResponse(data, outputFilename, MediaType.APPLICATION_PDF);
- }
+ String outputFilename =
+ baseFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ + "_overlayed.pdf"; // Remove file extension and append .pdf
+
+ return WebResponseUtils.bytesToWebResponse(
+ data, outputFilename, MediaType.APPLICATION_PDF);
+ }
} finally {
for (File overlayPdfFile : overlayPdfFiles) {
if (overlayPdfFile != null) {
@@ -76,7 +94,9 @@ public class PdfOverlayController {
}
}
- private Map prepareOverlayGuide(int basePageCount, File[] overlayFiles, String mode, int[] counts, List tempFiles) throws IOException {
+ private Map prepareOverlayGuide(
+ int basePageCount, File[] overlayFiles, String mode, int[] counts, List tempFiles)
+ throws IOException {
Map overlayGuide = new HashMap<>();
switch (mode) {
case "SequentialOverlay":
@@ -94,12 +114,19 @@ public class PdfOverlayController {
return overlayGuide;
}
- private void sequentialOverlay(Map overlayGuide, File[] overlayFiles, int basePageCount, List tempFiles) throws IOException {
+ private void sequentialOverlay(
+ Map overlayGuide,
+ File[] overlayFiles,
+ int basePageCount,
+ List tempFiles)
+ throws IOException {
int overlayFileIndex = 0;
int pageCountInCurrentOverlay = 0;
for (int basePageIndex = 1; basePageIndex <= basePageCount; basePageIndex++) {
- if (pageCountInCurrentOverlay == 0 || pageCountInCurrentOverlay >= getNumberOfPages(overlayFiles[overlayFileIndex])) {
+ if (pageCountInCurrentOverlay == 0
+ || pageCountInCurrentOverlay
+ >= getNumberOfPages(overlayFiles[overlayFileIndex])) {
pageCountInCurrentOverlay = 0;
overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;
}
@@ -125,13 +152,9 @@ public class PdfOverlayController {
}
}
-
-
-
-
-
-
- private void interleavedOverlay(Map overlayGuide, File[] overlayFiles, int basePageCount) throws IOException {
+ private void interleavedOverlay(
+ Map overlayGuide, File[] overlayFiles, int basePageCount)
+ throws IOException {
for (int basePageIndex = 1; basePageIndex <= basePageCount; basePageIndex++) {
File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];
@@ -145,10 +168,12 @@ public class PdfOverlayController {
}
}
-
- private void fixedRepeatOverlay(Map overlayGuide, File[] overlayFiles, int[] counts, int basePageCount) throws IOException {
+ private void fixedRepeatOverlay(
+ Map overlayGuide, File[] overlayFiles, int[] counts, int basePageCount)
+ throws IOException {
if (overlayFiles.length != counts.length) {
- throw new IllegalArgumentException("Counts array length must match the number of overlay files");
+ throw new IllegalArgumentException(
+ "Counts array length must match the number of overlay files");
}
int currentPage = 1;
for (int i = 0; i < overlayFiles.length; i++) {
@@ -167,7 +192,7 @@ public class PdfOverlayController {
}
}
}
-
}
-// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined elsewhere.
+// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined
+// elsewhere.
diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java
index b20bd99b..8c31bf3c 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java
@@ -17,200 +17,204 @@ 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.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
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")
- @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 deletePages(@ModelAttribute PDFWithPageNums request )
- throws IOException {
+ @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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity deletePages(@ModelAttribute PDFWithPageNums request)
+ throws IOException {
- MultipartFile pdfFile = request.getFileInput();
- String pagesToDelete = request.getPageNumbers();
-
- PDDocument document = PDDocument.load(pdfFile.getBytes());
+ MultipartFile pdfFile = request.getFileInput();
+ String pagesToDelete = request.getPageNumbers();
- // Split the page order string into an array of page numbers or range of numbers
- String[] pageOrderArr = pagesToDelete.split(",");
+ PDDocument document = PDDocument.load(pdfFile.getBytes());
- List pagesToRemove = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
+ // Split the page order string into an array of page numbers or range of numbers
+ String[] pageOrderArr = pagesToDelete.split(",");
- for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
- int pageIndex = pagesToRemove.get(i);
- document.removePage(pageIndex);
- }
- return WebResponseUtils.pdfDocToWebResponse(document,
- pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
+ List pagesToRemove =
+ GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
- }
-
-
-
- private List removeFirst(int totalPages) {
- if (totalPages <= 1)
- return new ArrayList<>();
- List newPageOrder = new ArrayList<>();
- for (int i = 2; i <= totalPages; i++) {
- newPageOrder.add(i - 1);
- }
- return newPageOrder;
- }
-
- private List removeLast(int totalPages) {
- if (totalPages <= 1)
- return new ArrayList<>();
- List newPageOrder = new ArrayList<>();
- for (int i = 1; i < totalPages; i++) {
- newPageOrder.add(i - 1);
- }
- return newPageOrder;
- }
-
- private List removeFirstAndLast(int totalPages) {
- if (totalPages <= 2)
- return new ArrayList<>();
- List newPageOrder = new ArrayList<>();
- for (int i = 2; i < totalPages; i++) {
- newPageOrder.add(i - 1);
- }
- return newPageOrder;
- }
-
- private List reverseOrder(int totalPages) {
- List newPageOrder = new ArrayList<>();
- for (int i = totalPages; i >= 1; i--) {
- newPageOrder.add(i - 1);
- }
- return newPageOrder;
- }
-
- private List duplexSort(int totalPages) {
- List newPageOrder = new ArrayList<>();
- int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages
- for (int i = 1; i <= half; i++) {
- newPageOrder.add(i - 1);
- if (i <= totalPages - half) { // Avoid going out of bounds
- newPageOrder.add(totalPages - i);
- }
- }
- return newPageOrder;
- }
-
- private List bookletSort(int totalPages) {
- List newPageOrder = new ArrayList<>();
- for (int i = 0; i < totalPages / 2; i++) {
- newPageOrder.add(i);
- newPageOrder.add(totalPages - i - 1);
- }
- return newPageOrder;
- }
-
- private List sideStitchBooklet(int totalPages) {
- List newPageOrder = new ArrayList<>();
- for (int i = 0; i < (totalPages + 3) / 4; i++) {
- int begin = i * 4;
- newPageOrder.add(Math.min(begin + 3, totalPages - 1));
- newPageOrder.add(Math.min(begin, totalPages - 1));
- newPageOrder.add(Math.min(begin + 1, totalPages - 1));
- newPageOrder.add(Math.min(begin + 2, totalPages - 1));
- }
- return newPageOrder;
+ for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
+ int pageIndex = pagesToRemove.get(i);
+ document.removePage(pageIndex);
+ }
+ return WebResponseUtils.pdfDocToWebResponse(
+ document,
+ pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
}
- private List oddEvenSplit(int totalPages) {
- List newPageOrder = new ArrayList<>();
- for (int i = 1; i <= totalPages; i += 2) {
- newPageOrder.add(i - 1);
- }
- for (int i = 2; i <= totalPages; i += 2) {
- newPageOrder.add(i - 1);
- }
- return newPageOrder;
- }
+ private List removeFirst(int totalPages) {
+ if (totalPages <= 1) return new ArrayList<>();
+ List newPageOrder = new ArrayList<>();
+ for (int i = 2; i <= totalPages; i++) {
+ newPageOrder.add(i - 1);
+ }
+ return newPageOrder;
+ }
- private List processSortTypes(String sortTypes, int totalPages) {
- try {
- SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
- switch (mode) {
- case REVERSE_ORDER:
- return reverseOrder(totalPages);
- case DUPLEX_SORT:
- return duplexSort(totalPages);
- case BOOKLET_SORT:
- return bookletSort(totalPages);
- case SIDE_STITCH_BOOKLET_SORT:
- return sideStitchBooklet(totalPages);
- case ODD_EVEN_SPLIT:
- return oddEvenSplit(totalPages);
- case REMOVE_FIRST:
- return removeFirst(totalPages);
- case REMOVE_LAST:
- return removeLast(totalPages);
- case REMOVE_FIRST_AND_LAST:
- return removeFirstAndLast(totalPages);
- default:
- throw new IllegalArgumentException("Unsupported custom mode");
- }
- } catch (IllegalArgumentException e) {
- logger.error("Unsupported custom mode", e);
- return null;
- }
- }
+ private List removeLast(int totalPages) {
+ if (totalPages <= 1) return new ArrayList<>();
+ List newPageOrder = new ArrayList<>();
+ for (int i = 1; i < totalPages; i++) {
+ newPageOrder.add(i - 1);
+ }
+ return newPageOrder;
+ }
- @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. Input:PDF Output:PDF")
- public ResponseEntity rearrangePages(@ModelAttribute RearrangePagesRequest request) throws IOException {
- MultipartFile pdfFile = request.getFileInput();
- String pageOrder = request.getPageNumbers();
- String sortType = request.getCustomMode();
- try {
- // Load the input PDF
- PDDocument document = PDDocument.load(pdfFile.getInputStream());
+ private List removeFirstAndLast(int totalPages) {
+ if (totalPages <= 2) return new ArrayList<>();
+ List newPageOrder = new ArrayList<>();
+ for (int i = 2; i < totalPages; i++) {
+ newPageOrder.add(i - 1);
+ }
+ return newPageOrder;
+ }
- // Split the page order string into an array of page numbers or range of numbers
- String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
- int totalPages = document.getNumberOfPages();
- List newPageOrder;
- if (sortType != null && sortType.length() > 0) {
- newPageOrder = processSortTypes(sortType, totalPages);
- } else {
- newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
- }
- logger.info("newPageOrder = " +newPageOrder);
- logger.info("totalPages = " +totalPages);
- // Create a new list to hold the pages in the new order
- List newPages = new ArrayList<>();
- for (int i = 0; i < newPageOrder.size(); i++) {
- newPages.add(document.getPage(newPageOrder.get(i)));
- }
+ private List reverseOrder(int totalPages) {
+ List newPageOrder = new ArrayList<>();
+ for (int i = totalPages; i >= 1; i--) {
+ newPageOrder.add(i - 1);
+ }
+ return newPageOrder;
+ }
- // Remove all the pages from the original document
- for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
- document.removePage(i);
- }
+ private List duplexSort(int totalPages) {
+ List newPageOrder = new ArrayList<>();
+ int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages
+ for (int i = 1; i <= half; i++) {
+ newPageOrder.add(i - 1);
+ if (i <= totalPages - half) { // Avoid going out of bounds
+ newPageOrder.add(totalPages - i);
+ }
+ }
+ return newPageOrder;
+ }
- // Add the pages in the new order
- for (PDPage page : newPages) {
- document.addPage(page);
- }
+ private List bookletSort(int totalPages) {
+ List newPageOrder = new ArrayList<>();
+ for (int i = 0; i < totalPages / 2; i++) {
+ newPageOrder.add(i);
+ newPageOrder.add(totalPages - i - 1);
+ }
+ return newPageOrder;
+ }
- return WebResponseUtils.pdfDocToWebResponse(document,
- pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
- } catch (IOException e) {
- logger.error("Failed rearranging documents", e);
- return null;
- }
- }
+ private List sideStitchBooklet(int totalPages) {
+ List newPageOrder = new ArrayList<>();
+ for (int i = 0; i < (totalPages + 3) / 4; i++) {
+ int begin = i * 4;
+ newPageOrder.add(Math.min(begin + 3, totalPages - 1));
+ newPageOrder.add(Math.min(begin, totalPages - 1));
+ newPageOrder.add(Math.min(begin + 1, totalPages - 1));
+ newPageOrder.add(Math.min(begin + 2, totalPages - 1));
+ }
+ return newPageOrder;
+ }
-
+ private List oddEvenSplit(int totalPages) {
+ List newPageOrder = new ArrayList<>();
+ for (int i = 1; i <= totalPages; i += 2) {
+ newPageOrder.add(i - 1);
+ }
+ for (int i = 2; i <= totalPages; i += 2) {
+ newPageOrder.add(i - 1);
+ }
+ return newPageOrder;
+ }
+ private List processSortTypes(String sortTypes, int totalPages) {
+ try {
+ SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
+ switch (mode) {
+ case REVERSE_ORDER:
+ return reverseOrder(totalPages);
+ case DUPLEX_SORT:
+ return duplexSort(totalPages);
+ case BOOKLET_SORT:
+ return bookletSort(totalPages);
+ case SIDE_STITCH_BOOKLET_SORT:
+ return sideStitchBooklet(totalPages);
+ case ODD_EVEN_SPLIT:
+ return oddEvenSplit(totalPages);
+ case REMOVE_FIRST:
+ return removeFirst(totalPages);
+ case REMOVE_LAST:
+ return removeLast(totalPages);
+ case REMOVE_FIRST_AND_LAST:
+ return removeFirstAndLast(totalPages);
+ default:
+ throw new IllegalArgumentException("Unsupported custom mode");
+ }
+ } catch (IllegalArgumentException e) {
+ logger.error("Unsupported custom mode", e);
+ return null;
+ }
+ }
+
+ @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. Input:PDF Output:PDF")
+ public ResponseEntity rearrangePages(@ModelAttribute RearrangePagesRequest request)
+ throws IOException {
+ MultipartFile pdfFile = request.getFileInput();
+ String pageOrder = request.getPageNumbers();
+ String sortType = request.getCustomMode();
+ try {
+ // Load the input PDF
+ PDDocument document = PDDocument.load(pdfFile.getInputStream());
+
+ // Split the page order string into an array of page numbers or range of numbers
+ String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
+ int totalPages = document.getNumberOfPages();
+ List newPageOrder;
+ if (sortType != null && sortType.length() > 0) {
+ newPageOrder = processSortTypes(sortType, totalPages);
+ } else {
+ newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
+ }
+ logger.info("newPageOrder = " + newPageOrder);
+ logger.info("totalPages = " + totalPages);
+ // Create a new list to hold the pages in the new order
+ List newPages = new ArrayList<>();
+ for (int i = 0; i < newPageOrder.size(); i++) {
+ newPages.add(document.getPage(newPageOrder.get(i)));
+ }
+
+ // Remove all the pages from the original document
+ for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
+ document.removePage(i);
+ }
+
+ // Add the pages in the new order
+ for (PDPage page : newPages) {
+ document.addPage(page);
+ }
+
+ return WebResponseUtils.pdfDocToWebResponse(
+ document,
+ pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ + "_rearranged.pdf");
+ } catch (IOException e) {
+ logger.error("Failed rearranging documents", e);
+ return null;
+ }
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java
index ed527549..883beb5d 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/RotationController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/RotationController.java
@@ -16,6 +16,7 @@ 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.model.api.general.RotatePDFRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -28,11 +29,11 @@ public class RotationController {
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@Operation(
- 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. Input:PDF Output:PDF Type:SISO"
- )
- public ResponseEntity rotatePDF(
- @ModelAttribute RotatePDFRequest request) throws IOException {
+ 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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity rotatePDF(@ModelAttribute RotatePDFRequest request)
+ throws IOException {
MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle();
// Load the PDF document
@@ -45,8 +46,8 @@ public class RotationController {
page.setRotation(page.getRotation() + angle);
}
- return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
-
+ return WebResponseUtils.pdfDocToWebResponse(
+ document,
+ pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java
index 743ea6b1..0dcec05c 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java
@@ -23,88 +23,90 @@ 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.model.api.general.ScalePagesRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
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")
- @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 scalePages(@ModelAttribute ScalePagesRequest request) throws IOException {
- MultipartFile file = request.getFileInput();
- String targetPDRectangle = request.getPageSize();
- float scaleFactor = request.getScaleFactor();
+ @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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest request)
+ throws IOException {
+ MultipartFile file = request.getFileInput();
+ String targetPDRectangle = request.getPageSize();
+ float scaleFactor = request.getScaleFactor();
- Map sizeMap = new HashMap<>();
- // Add A0 - A10
- sizeMap.put("A0", PDRectangle.A0);
- sizeMap.put("A1", PDRectangle.A1);
- sizeMap.put("A2", PDRectangle.A2);
- sizeMap.put("A3", PDRectangle.A3);
- sizeMap.put("A4", PDRectangle.A4);
- sizeMap.put("A5", PDRectangle.A5);
- sizeMap.put("A6", PDRectangle.A6);
+ Map sizeMap = new HashMap<>();
+ // Add A0 - A10
+ sizeMap.put("A0", PDRectangle.A0);
+ sizeMap.put("A1", PDRectangle.A1);
+ sizeMap.put("A2", PDRectangle.A2);
+ sizeMap.put("A3", PDRectangle.A3);
+ sizeMap.put("A4", PDRectangle.A4);
+ sizeMap.put("A5", PDRectangle.A5);
+ sizeMap.put("A6", PDRectangle.A6);
- // Add other sizes
- sizeMap.put("LETTER", PDRectangle.LETTER);
- sizeMap.put("LEGAL", PDRectangle.LEGAL);
+ // Add other sizes
+ sizeMap.put("LETTER", PDRectangle.LETTER);
+ sizeMap.put("LEGAL", PDRectangle.LEGAL);
- if (!sizeMap.containsKey(targetPDRectangle)) {
- throw new IllegalArgumentException(
- "Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
- }
+ if (!sizeMap.containsKey(targetPDRectangle)) {
+ throw new IllegalArgumentException(
+ "Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
+ }
- PDRectangle targetSize = sizeMap.get(targetPDRectangle);
+ PDRectangle targetSize = sizeMap.get(targetPDRectangle);
- PDDocument sourceDocument = PDDocument.load(file.getBytes());
- PDDocument outputDocument = new PDDocument();
+ PDDocument sourceDocument = PDDocument.load(file.getBytes());
+ PDDocument outputDocument = new PDDocument();
- int totalPages = sourceDocument.getNumberOfPages();
- for (int i = 0; i < totalPages; i++) {
- PDPage sourcePage = sourceDocument.getPage(i);
- PDRectangle sourceSize = sourcePage.getMediaBox();
-
- float scaleWidth = targetSize.getWidth() / sourceSize.getWidth();
- float scaleHeight = targetSize.getHeight() / sourceSize.getHeight();
- float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;
-
- PDPage newPage = new PDPage(targetSize);
- outputDocument.addPage(newPage);
-
- PDPageContentStream contentStream = new PDPageContentStream(outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true);
-
- float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
- float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
-
- contentStream.saveGraphicsState();
- contentStream.transform(Matrix.getTranslateInstance(x, y));
- contentStream.transform(Matrix.getScaleInstance(scale, scale));
-
- LayerUtility layerUtility = new LayerUtility(outputDocument);
- PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i);
- contentStream.drawForm(form);
+ int totalPages = sourceDocument.getNumberOfPages();
+ for (int i = 0; i < totalPages; i++) {
+ PDPage sourcePage = sourceDocument.getPage(i);
+ PDRectangle sourceSize = sourcePage.getMediaBox();
- contentStream.restoreGraphicsState();
- contentStream.close();
- }
+ float scaleWidth = targetSize.getWidth() / sourceSize.getWidth();
+ float scaleHeight = targetSize.getHeight() / sourceSize.getHeight();
+ float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;
+ PDPage newPage = new PDPage(targetSize);
+ outputDocument.addPage(newPage);
+ PDPageContentStream contentStream =
+ new PDPageContentStream(
+ outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true);
+ float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
+ float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
+ contentStream.saveGraphicsState();
+ contentStream.transform(Matrix.getTranslateInstance(x, y));
+ contentStream.transform(Matrix.getScaleInstance(scale, scale));
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- outputDocument.save(baos);
- outputDocument.close();
- sourceDocument.close();
-
-
- return WebResponseUtils.bytesToWebResponse(baos.toByteArray(),
- file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
- }
+ LayerUtility layerUtility = new LayerUtility(outputDocument);
+ PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i);
+ contentStream.drawForm(form);
+ contentStream.restoreGraphicsState();
+ contentStream.close();
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ outputDocument.save(baos);
+ outputDocument.close();
+ sourceDocument.close();
+
+ return WebResponseUtils.bytesToWebResponse(
+ baos.toByteArray(),
+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java
index 0651949e..a521769e 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java
@@ -25,6 +25,7 @@ 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.model.api.PDFWithPageNums;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -36,19 +37,24 @@ public class SplitPDFController {
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
- @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. Input:PDF Output:PDF Type:SIMO")
- public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) throws IOException {
- MultipartFile file = request.getFileInput();
+ @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. Input:PDF Output:PDF Type:SIMO")
+ public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request)
+ throws IOException {
+ MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream);
List pageNumbers = request.getPageNumbersList(document);
- if(!pageNumbers.contains(document.getNumberOfPages() - 1))
- pageNumbers.add(document.getNumberOfPages()- 1);
- logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
+ if (!pageNumbers.contains(document.getNumberOfPages() - 1))
+ pageNumbers.add(document.getNumberOfPages() - 1);
+ logger.info(
+ "Splitting PDF into pages: {}",
+ pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List splitDocumentsBoas = new ArrayList<>();
@@ -72,7 +78,6 @@ public class SplitPDFController {
}
}
-
// closing the original document
document.close();
@@ -104,8 +109,7 @@ public class SplitPDFController {
Files.delete(zipFile);
// return the Resource in the response
- return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
-
+ return WebResponseUtils.bytesToWebResponse(
+ data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
}
-
-}
\ No newline at end of file
+}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java
index f36f64da..bc4e2cce 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.controller.api;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
@@ -25,17 +26,22 @@ 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.model.api.SplitPdfBySectionsRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySectionsController {
-
- @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
- @Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO")
- public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception {
+ @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
+ @Operation(
+ summary = "Split PDF pages into smaller sections",
+ description =
+ "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input:PDF Output:ZIP-PDF Type:SISO")
+ public ResponseEntity splitPdf(@ModelAttribute SplitPdfBySectionsRequest request)
+ throws Exception {
List splitDocumentsBoas = new ArrayList<>();
MultipartFile file = request.getFileInput();
@@ -59,8 +65,6 @@ public class SplitPdfBySectionsController {
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
byte[] data;
-
-
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
int pageNum = 1;
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
@@ -82,10 +86,13 @@ public class SplitPdfBySectionsController {
Files.delete(zipFile);
}
- return WebResponseUtils.bytesToWebResponse(data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
+ return WebResponseUtils.bytesToWebResponse(
+ data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
}
-
- public List splitPdfPages(PDDocument document, int horizontalDivisions, int verticalDivisions) throws IOException {
+
+ public List splitPdfPages(
+ PDDocument document, int horizontalDivisions, int verticalDivisions)
+ throws IOException {
List splitDocuments = new ArrayList<>();
for (PDPage originalPage : document.getPages()) {
@@ -103,13 +110,20 @@ public class SplitPdfBySectionsController {
PDPage subPage = new PDPage(new PDRectangle(subPageWidth, subPageHeight));
subDoc.addPage(subPage);
- PDFormXObject form = layerUtility.importPageAsForm(document, document.getPages().indexOf(originalPage));
+ PDFormXObject form =
+ layerUtility.importPageAsForm(
+ document, document.getPages().indexOf(originalPage));
- try (PDPageContentStream contentStream = new PDPageContentStream(subDoc, subPage)) {
+ try (PDPageContentStream contentStream =
+ new PDPageContentStream(subDoc, subPage)) {
// Set clipping area and position
float translateX = -subPageWidth * i;
float translateY = height - subPageHeight * (verticalDivisions - j);
-
+
+
+ //Code for google Docs pdfs..
+ //float translateY = -subPageHeight * (verticalDivisions - 1 - j);
+
contentStream.saveGraphicsState();
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
contentStream.clip();
@@ -127,9 +141,4 @@ public class SplitPdfBySectionsController {
return splitDocuments;
}
-
-
-
-
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java
index 9b25e8be..28ac4673 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySizeController.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.controller.api;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
@@ -20,6 +21,7 @@ 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.model.api.general.SplitPdfBySizeOrCountRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -29,22 +31,23 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySizeController {
-
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
- @Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n"
- + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO")
- public ResponseEntity autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
- List splitDocumentsBoas = new ArrayList();
-
-
-
+ @Operation(
+ summary = "Auto split PDF pages into separate documents based on size or count",
+ description =
+ "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n"
+ + " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO")
+ public ResponseEntity autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request)
+ throws Exception {
+ List splitDocumentsBoas = new ArrayList();
+
MultipartFile file = request.getFileInput();
PDDocument sourceDocument = PDDocument.load(file.getInputStream());
-
- //0 = size, 1 = page count, 2 = doc count
+
+ // 0 = size, 1 = page count, 2 = doc count
int type = request.getSplitType();
String value = request.getSplitValue();
-
+
if (type == 0) { // Split by size
long maxBytes = GeneralUtils.convertSizeToBytes(value);
long currentSize = 0;
@@ -93,7 +96,7 @@ public class SplitPdfBySizeController {
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
}
} else if (type == 2) { // Split by doc count
- int documentCount = Integer.parseInt(value);
+ int documentCount = Integer.parseInt(value);
int totalPageCount = sourceDocument.getNumberOfPages();
int pagesPerDocument = totalPageCount / documentCount;
int extraPages = totalPageCount % documentCount;
@@ -114,9 +117,7 @@ public class SplitPdfBySizeController {
}
sourceDocument.close();
-
-
-
+
Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
byte[] data;
@@ -135,19 +136,18 @@ public class SplitPdfBySizeController {
} catch (Exception e) {
e.printStackTrace();
} finally {
- data = Files.readAllBytes(zipFile);
+ data = Files.readAllBytes(zipFile);
Files.delete(zipFile);
}
- return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
+ return WebResponseUtils.bytesToWebResponse(
+ data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
}
-
+
private ByteArrayOutputStream currentDocToByteArray(PDDocument document) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos);
document.close();
return baos;
}
-
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java
index 22bf1d70..cd971b55 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java
@@ -20,8 +20,10 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
@@ -29,58 +31,61 @@ 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 pdfToSinglePage(@ModelAttribute PDFFile request) throws IOException {
+ 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 pdfToSinglePage(@ModelAttribute PDFFile request)
+ throws IOException {
- // Load the source document
- PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream());
+ // Load the source document
+ PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream());
- // Calculate total height and max width
- float totalHeight = 0;
- float maxWidth = 0;
- for (PDPage page : sourceDocument.getPages()) {
- PDRectangle pageSize = page.getMediaBox();
- totalHeight += pageSize.getHeight();
- maxWidth = Math.max(maxWidth, pageSize.getWidth());
- }
+ // Calculate total height and max width
+ float totalHeight = 0;
+ float maxWidth = 0;
+ for (PDPage page : sourceDocument.getPages()) {
+ PDRectangle pageSize = page.getMediaBox();
+ totalHeight += pageSize.getHeight();
+ maxWidth = Math.max(maxWidth, pageSize.getWidth());
+ }
- // Create new document and page with calculated dimensions
- PDDocument newDocument = new PDDocument();
- PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
- newDocument.addPage(newPage);
+ // Create new document and page with calculated dimensions
+ PDDocument newDocument = new PDDocument();
+ PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
+ newDocument.addPage(newPage);
- // Initialize the content stream of the new page
- PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
- contentStream.close();
-
- LayerUtility layerUtility = new LayerUtility(newDocument);
- float yOffset = totalHeight;
+ // Initialize the content stream of the new page
+ PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
+ contentStream.close();
- // For each page, copy its content to the new page at the correct offset
- for (PDPage page : sourceDocument.getPages()) {
- PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, sourceDocument.getPages().indexOf(page));
- AffineTransform af = AffineTransform.getTranslateInstance(0, yOffset - page.getMediaBox().getHeight());
- layerUtility.wrapInSaveRestore(newPage);
- String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page);
- layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName);
- yOffset -= page.getMediaBox().getHeight();
- }
+ LayerUtility layerUtility = new LayerUtility(newDocument);
+ float yOffset = totalHeight;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- newDocument.save(baos);
- newDocument.close();
- sourceDocument.close();
+ // For each page, copy its content to the new page at the correct offset
+ for (PDPage page : sourceDocument.getPages()) {
+ PDFormXObject form =
+ layerUtility.importPageAsForm(
+ sourceDocument, sourceDocument.getPages().indexOf(page));
+ AffineTransform af =
+ AffineTransform.getTranslateInstance(
+ 0, yOffset - page.getMediaBox().getHeight());
+ layerUtility.wrapInSaveRestore(newPage);
+ String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page);
+ layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName);
+ yOffset -= page.getMediaBox().getHeight();
+ }
- byte[] result = baos.toByteArray();
- return WebResponseUtils.bytesToWebResponse(result, request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ newDocument.save(baos);
+ newDocument.close();
+ sourceDocument.close();
-
-
-
+ byte[] result = baos.toByteArray();
+ return WebResponseUtils.bytesToWebResponse(
+ result,
+ request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ + "_singlePage.pdf");
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java
index 01a50a3b..89e81c99 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java
@@ -29,14 +29,14 @@ import stirling.software.SPDF.model.User;
@Controller
@RequestMapping("/api/v1/user")
public class UserController {
-
- @Autowired
- private UserService userService;
-
+
+ @Autowired private UserService userService;
+
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/register")
- public String register(@RequestParam String username, @RequestParam String password, Model model) {
- if(userService.usernameExists(username)) {
+ public String register(
+ @RequestParam String username, @RequestParam String password, Model model) {
+ if (userService.usernameExists(username)) {
model.addAttribute("error", "Username already exists");
return "register";
}
@@ -44,39 +44,41 @@ public class UserController {
userService.saveUser(username, password);
return "redirect:/login?registered=true";
}
-
+
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-username-and-password")
- public RedirectView changeUsernameAndPassword(Principal principal,
- @RequestParam String currentPassword,
- @RequestParam String newUsername,
- @RequestParam String newPassword,
- HttpServletRequest request,
- HttpServletResponse response,
- RedirectAttributes redirectAttributes) {
- if (principal == null) {
- return new RedirectView("/change-creds?messageType=notAuthenticated");
- }
+ public RedirectView changeUsernameAndPassword(
+ Principal principal,
+ @RequestParam String currentPassword,
+ @RequestParam String newUsername,
+ @RequestParam String newPassword,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ RedirectAttributes redirectAttributes) {
+ if (principal == null) {
+ return new RedirectView("/change-creds?messageType=notAuthenticated");
+ }
- Optional userOpt = userService.findByUsername(principal.getName());
+ Optional userOpt = userService.findByUsername(principal.getName());
- if (userOpt == null || userOpt.isEmpty()) {
- return new RedirectView("/change-creds?messageType=userNotFound");
- }
+ if (userOpt == null || userOpt.isEmpty()) {
+ return new RedirectView("/change-creds?messageType=userNotFound");
+ }
- User user = userOpt.get();
+ User user = userOpt.get();
- if (!userService.isPasswordCorrect(user, currentPassword)) {
- return new RedirectView("/change-creds?messageType=incorrectPassword");
- }
-
- if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
- return new RedirectView("/change-creds?messageType=usernameExists");
- }
+ if (!userService.isPasswordCorrect(user, currentPassword)) {
+ return new RedirectView("/change-creds?messageType=incorrectPassword");
+ }
+ if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
+ return new RedirectView("/change-creds?messageType=usernameExists");
+ }
userService.changePassword(user, newPassword);
- if(newUsername != null && newUsername.length() > 0 && !user.getUsername().equals(newUsername)) {
+ if (newUsername != null
+ && newUsername.length() > 0
+ && !user.getUsername().equals(newUsername)) {
userService.changeUsername(user, newUsername);
}
userService.changeFirstUse(user, false);
@@ -87,36 +89,36 @@ public class UserController {
return new RedirectView("/login?messageType=credsUpdated");
}
-
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-username")
- public RedirectView changeUsername(Principal principal,
- @RequestParam String currentPassword,
- @RequestParam String newUsername,
- HttpServletRequest request,
- HttpServletResponse response,
- RedirectAttributes redirectAttributes) {
- if (principal == null) {
- return new RedirectView("/account?messageType=notAuthenticated");
- }
+ public RedirectView changeUsername(
+ Principal principal,
+ @RequestParam String currentPassword,
+ @RequestParam String newUsername,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ RedirectAttributes redirectAttributes) {
+ if (principal == null) {
+ return new RedirectView("/account?messageType=notAuthenticated");
+ }
- Optional userOpt = userService.findByUsername(principal.getName());
+ Optional userOpt = userService.findByUsername(principal.getName());
- if (userOpt == null || userOpt.isEmpty()) {
- return new RedirectView("/account?messageType=userNotFound");
- }
+ if (userOpt == null || userOpt.isEmpty()) {
+ return new RedirectView("/account?messageType=userNotFound");
+ }
- User user = userOpt.get();
+ User user = userOpt.get();
- if (!userService.isPasswordCorrect(user, currentPassword)) {
- return new RedirectView("/account?messageType=incorrectPassword");
- }
+ if (!userService.isPasswordCorrect(user, currentPassword)) {
+ return new RedirectView("/account?messageType=incorrectPassword");
+ }
- if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
- return new RedirectView("/account?messageType=usernameExists");
- }
+ if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
+ return new RedirectView("/account?messageType=usernameExists");
+ }
- if(newUsername != null && newUsername.length() > 0) {
+ if (newUsername != null && newUsername.length() > 0) {
userService.changeUsername(user, newUsername);
}
@@ -125,30 +127,31 @@ public class UserController {
return new RedirectView("/login?messageType=credsUpdated");
}
-
+
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/change-password")
- public RedirectView changePassword(Principal principal,
- @RequestParam String currentPassword,
- @RequestParam String newPassword,
- HttpServletRequest request,
- HttpServletResponse response,
- RedirectAttributes redirectAttributes) {
- if (principal == null) {
- return new RedirectView("/account?messageType=notAuthenticated");
- }
+ public RedirectView changePassword(
+ Principal principal,
+ @RequestParam String currentPassword,
+ @RequestParam String newPassword,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ RedirectAttributes redirectAttributes) {
+ if (principal == null) {
+ return new RedirectView("/account?messageType=notAuthenticated");
+ }
- Optional userOpt = userService.findByUsername(principal.getName());
+ Optional userOpt = userService.findByUsername(principal.getName());
- if (userOpt == null || userOpt.isEmpty()) {
- return new RedirectView("/account?messageType=userNotFound");
- }
+ if (userOpt == null || userOpt.isEmpty()) {
+ return new RedirectView("/account?messageType=userNotFound");
+ }
- User user = userOpt.get();
+ User user = userOpt.get();
- if (!userService.isPasswordCorrect(user, currentPassword)) {
- return new RedirectView("/account?messageType=incorrectPassword");
- }
+ if (!userService.isPasswordCorrect(user, currentPassword)) {
+ return new RedirectView("/account?messageType=incorrectPassword");
+ }
userService.changePassword(user, newPassword);
@@ -160,33 +163,37 @@ public class UserController {
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/updateUserSettings")
- public String updateUserSettings(HttpServletRequest request, Principal principal) {
- Map paramMap = request.getParameterMap();
- Map updates = new HashMap<>();
+ public String updateUserSettings(HttpServletRequest request, Principal principal) {
+ Map paramMap = request.getParameterMap();
+ Map updates = new HashMap<>();
- System.out.println("Received parameter map: " + paramMap);
+ System.out.println("Received parameter map: " + paramMap);
- for (Map.Entry entry : paramMap.entrySet()) {
- updates.put(entry.getKey(), entry.getValue()[0]);
- }
+ for (Map.Entry entry : paramMap.entrySet()) {
+ updates.put(entry.getKey(), entry.getValue()[0]);
+ }
- System.out.println("Processed updates: " + updates);
+ System.out.println("Processed updates: " + updates);
- // Assuming you have a method in userService to update the settings for a user
- userService.updateUserSettings(principal.getName(), updates);
+ // Assuming you have a method in userService to update the settings for a user
+ userService.updateUserSettings(principal.getName(), updates);
- return "redirect:/account"; // Redirect to a page of your choice after updating
- }
+ return "redirect:/account"; // Redirect to a page of your choice after updating
+ }
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/saveUser")
- public RedirectView saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String role,
- @RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) {
-
- if(userService.usernameExists(username)) {
- return new RedirectView("/addUsers?messageType=usernameExists");
- }
- try {
+ public RedirectView saveUser(
+ @RequestParam String username,
+ @RequestParam String password,
+ @RequestParam String role,
+ @RequestParam(name = "forceChange", required = false, defaultValue = "false")
+ boolean forceChange) {
+
+ if (userService.usernameExists(username)) {
+ return new RedirectView("/addUsers?messageType=usernameExists");
+ }
+ try {
// Validate the role
Role roleEnum = Role.fromString(role);
if (roleEnum == Role.INTERNAL_API_USER) {
@@ -197,28 +204,27 @@ public class UserController {
// If the role ID is not valid, redirect with an error message
return new RedirectView("/addUsers?messageType=invalidRole");
}
-
+
userService.saveUser(username, password, role, forceChange);
- return new RedirectView("/addUsers"); // Redirect to account page after adding the user
+ return new RedirectView("/addUsers"); // Redirect to account page after adding the user
}
-
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/deleteUser/{username}")
- public String deleteUser(@PathVariable String username, Authentication authentication) {
-
- // Get the currently authenticated username
+ public String deleteUser(@PathVariable String username, Authentication authentication) {
+
+ // Get the currently authenticated username
String currentUsername = authentication.getName();
// Check if the provided username matches the current session's username
if (currentUsername.equals(username)) {
throw new IllegalArgumentException("Cannot delete currently logined in user.");
}
-
- userService.deleteUser(username);
+
+ userService.deleteUser(username);
return "redirect:/addUsers";
}
-
+
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/get-api-key")
public ResponseEntity getApiKey(Principal principal) {
@@ -247,6 +253,4 @@ public class UserController {
}
return ResponseEntity.ok(apiKey);
}
-
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java
index 5839dd2d..bec09040 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java
@@ -9,6 +9,7 @@ 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.model.api.GeneralFile;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -18,35 +19,30 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF {
+ @PostMapping(consumes = "multipart/form-data", value = "/html/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 HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
+ MultipartFile fileInput = request.getFileInput();
- @PostMapping(consumes = "multipart/form-data", value = "/html/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 HtmlToPdf(
- @ModelAttribute GeneralFile request)
- throws Exception {
- MultipartFile fileInput = request.getFileInput();
+ if (fileInput == null) {
+ throw new IllegalArgumentException(
+ "Please provide an HTML or ZIP file for conversion.");
+ }
- 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 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);
- }
-
-
-
-
-
-
+ String outputFilename =
+ originalFilename.replaceFirst("[.][^.]+$", "")
+ + ".pdf"; // Remove file extension and append .pdf
+ return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java
index f0c60b69..a3ea2841 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java
@@ -20,6 +20,7 @@ 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.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.utils.PdfUtils;
@@ -33,15 +34,18 @@ public class ConvertImgPDFController {
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
- @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. Input:PDF Output:Image Type:SI-Conditional")
- public ResponseEntity convertToImage(@ModelAttribute ConvertToImageRequest request) throws IOException {
+ @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. Input:PDF Output:Image Type:SI-Conditional")
+ public ResponseEntity convertToImage(@ModelAttribute ConvertToImageRequest request)
+ throws IOException {
MultipartFile file = request.getFileInput();
String imageFormat = request.getImageFormat();
String singleOrMultiple = request.getSingleOrMultiple();
String colorType = request.getColorType();
String dpi = request.getDpi();
-
+
byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) {
@@ -54,7 +58,14 @@ public class ConvertImgPDFController {
byte[] result = null;
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
try {
- result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi), filename);
+ result =
+ PdfUtils.convertFromPdf(
+ pdfBytes,
+ imageFormat.toUpperCase(),
+ colorTypeResult,
+ singleImage,
+ Integer.valueOf(dpi),
+ filename);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
@@ -65,29 +76,39 @@ public class ConvertImgPDFController {
if (singleImage) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
- ResponseEntity response = new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
+ ResponseEntity response =
+ new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response;
} else {
ByteArrayResource resource = new ByteArrayResource(result);
// return the Resource in the response
return ResponseEntity.ok()
- .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename + "_convertedToImages.zip")
- .contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(resource.contentLength()).body(resource);
+ .header(
+ HttpHeaders.CONTENT_DISPOSITION,
+ "attachment; filename=" + filename + "_convertedToImages.zip")
+ .contentType(MediaType.APPLICATION_OCTET_STREAM)
+ .contentLength(resource.contentLength())
+ .body(resource);
}
}
@PostMapping(consumes = "multipart/form-data", value = "/img/pdf")
- @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. Input:Image Output:PDF Type:SISO?")
- public ResponseEntity convertToPdf(@ModelAttribute ConvertToPdfRequest request) throws IOException {
+ @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. Input:Image Output:PDF Type:SISO?")
+ public ResponseEntity convertToPdf(@ModelAttribute ConvertToPdfRequest request)
+ throws IOException {
MultipartFile[] file = request.getFileInput();
String fitOption = request.getFitOption();
String colorType = request.getColorType();
boolean autoRotate = request.isAutoRotate();
-
+
// Convert the file to PDF and get the resulting bytes
byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType);
- return WebResponseUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
+ return WebResponseUtils.bytesToWebResponse(
+ bytes,
+ file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
}
private String getMediaType(String imageFormat) {
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java
index 4191ecdf..8bdc5049 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java
@@ -12,6 +12,7 @@ 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.model.api.GeneralFile;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -20,17 +21,16 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf {
-
- @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
+
+ @PostMapping(consumes = "multipart/form-data", value = "/markdown/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 markdownToPdf(
- @ModelAttribute GeneralFile request)
- throws Exception {
- MultipartFile fileInput = request.getFileInput();
-
+ 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 markdownToPdf(@ModelAttribute GeneralFile request)
+ throws Exception {
+ MultipartFile fileInput = request.getFileInput();
+
if (fileInput == null) {
throw new IllegalArgumentException("Please provide a Markdown file for conversion.");
}
@@ -45,10 +45,12 @@ public class ConvertMarkdownToPdf {
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
+ byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html");
+
+ String outputFilename =
+ originalFilename.replaceFirst("[.][^.]+$", "")
+ + ".pdf"; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java
index e1c18a49..ebc9f4f5 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java
@@ -18,6 +18,7 @@ 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.model.api.GeneralFile;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -31,20 +32,33 @@ public class ConvertOfficeController {
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension
String originalFilename = inputFile.getOriginalFilename();
- if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
+ if (originalFilename == null
+ || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
throw new IllegalArgumentException("Invalid file extension");
}
// Save the uploaded file to a temporary location
- Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
+ Path tempInputFile =
+ Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
// Run the LibreOffice command
- List command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString()));
- ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command);
+ List command =
+ new ArrayList<>(
+ Arrays.asList(
+ "unoconv",
+ "-vvv",
+ "-f",
+ "pdf",
+ "-o",
+ tempOutputFile.toString(),
+ tempInputFile.toString()));
+ ProcessExecutorResult returnCode =
+ ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
+ .runCommandWithOutputHandling(command);
// Read the converted PDF file
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
@@ -55,6 +69,7 @@ public class ConvertOfficeController {
return pdfBytes;
}
+
private boolean isValidFileExtension(String fileExtension) {
String extensionPattern = "^(?i)[a-z0-9]{2,4}$";
return fileExtension.matches(extensionPattern);
@@ -62,17 +77,19 @@ public class ConvertOfficeController {
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
@Operation(
- summary = "Convert a file to a PDF using LibreOffice",
- description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO"
- )
- public ResponseEntity processFileToPDF(@ModelAttribute GeneralFile request)
- throws Exception {
- MultipartFile inputFile = request.getFileInput();
+ summary = "Convert a file to a PDF using LibreOffice",
+ description =
+ "This endpoint converts a given file to a PDF using LibreOffice API Input:ANY Output:PDF Type:SISO")
+ public ResponseEntity processFileToPDF(@ModelAttribute GeneralFile request)
+ throws Exception {
+ MultipartFile inputFile = request.getFileInput();
// unused but can start server instance if startup time is to long
// LibreOfficeListener.getInstance().start();
byte[] pdfByteArray = convertToPdf(inputFile);
- return WebResponseUtils.bytesToWebResponse(pdfByteArray, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf");
+ return WebResponseUtils.bytesToWebResponse(
+ pdfByteArray,
+ inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ + "_convertedToPDF.pdf");
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java
index 11279a27..74b292b5 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToOffice.java
@@ -11,6 +11,7 @@ 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.model.api.PDFFile;
import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
@@ -22,51 +23,70 @@ import stirling.software.SPDF.utils.PDFToFile;
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertPDFToOffice {
- @PostMapping(consumes = "multipart/form-data", value = "/pdf/html")
- @Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
- public ResponseEntity processPdfToHTML(@ModelAttribute PDFFile request)
- throws Exception {
- MultipartFile inputFile = request.getFileInput();
- PDFToFile pdfToFile = new PDFToFile();
- return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
- }
+ @PostMapping(consumes = "multipart/form-data", value = "/pdf/html")
+ @Operation(
+ summary = "Convert PDF to HTML",
+ description =
+ "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
+ public ResponseEntity processPdfToHTML(@ModelAttribute PDFFile request)
+ throws Exception {
+ MultipartFile inputFile = request.getFileInput();
+ PDFToFile pdfToFile = new PDFToFile();
+ return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
+ }
- @PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
- @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 processPdfToPresentation(@ModelAttribute PdfToPresentationRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String outputFormat = request.getOutputFormat();
- PDFToFile pdfToFile = new PDFToFile();
- return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
- }
+ @PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
+ @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 processPdfToPresentation(
+ @ModelAttribute PdfToPresentationRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String outputFormat = request.getOutputFormat();
+ PDFToFile pdfToFile = new PDFToFile();
+ return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
+ }
- @PostMapping(consumes = "multipart/form-data", value = "/pdf/text")
- @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 processPdfToRTForTXT(@ModelAttribute PdfToTextOrRTFRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String outputFormat = request.getOutputFormat();
+ @PostMapping(consumes = "multipart/form-data", value = "/pdf/text")
+ @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 processPdfToRTForTXT(
+ @ModelAttribute PdfToTextOrRTFRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String outputFormat = request.getOutputFormat();
- PDFToFile pdfToFile = new PDFToFile();
- return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
- }
+ PDFToFile pdfToFile = new PDFToFile();
+ return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
+ }
- @PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
- @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 processPdfToWord(@ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String outputFormat = request.getOutputFormat();
- PDFToFile pdfToFile = new PDFToFile();
- return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
- }
+ @PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
+ @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 processPdfToWord(@ModelAttribute PdfToWordRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String outputFormat = request.getOutputFormat();
+ PDFToFile pdfToFile = new PDFToFile();
+ return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
+ }
- @PostMapping(consumes = "multipart/form-data", value = "/pdf/xml")
- @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 processPdfToXML(@ModelAttribute PDFFile request)
- throws Exception {
- MultipartFile inputFile = request.getFileInput();
-
- PDFToFile pdfToFile = new PDFToFile();
- return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");
- }
+ @PostMapping(consumes = "multipart/form-data", value = "/pdf/xml")
+ @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 processPdfToXML(@ModelAttribute PDFFile request)
+ throws Exception {
+ MultipartFile inputFile = request.getFileInput();
+ PDFToFile pdfToFile = new PDFToFile();
+ return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java
index 32ccb84a..ac8ce031 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java
@@ -14,6 +14,7 @@ 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.model.api.PDFFile;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -24,14 +25,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertPDFToPDFA {
- @PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
- @Operation(
- 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. Input:PDF Output:PDF Type:SISO"
- )
- public ResponseEntity pdfToPdfA(@ModelAttribute PDFFile request)
- throws Exception {
- MultipartFile inputFile = request.getFileInput();
+ @PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
+ @Operation(
+ 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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity pdfToPdfA(@ModelAttribute PDFFile request) throws Exception {
+ MultipartFile inputFile = request.getFileInput();
// Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf");
@@ -50,7 +50,9 @@ public class ConvertPDFToPDFA {
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
- ProcessExecutorResult 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
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
@@ -60,8 +62,8 @@ public class ConvertPDFToPDFA {
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
- String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
+ String outputFilename =
+ inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java
index d3f5c307..bf631c87 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java
@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@@ -25,52 +26,52 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF {
- @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
- @Operation(
- summary = "Convert a URL to a PDF",
- description = "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO"
- )
- public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request) throws IOException, InterruptedException {
- String URL = request.getUrlInput();
+ @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
+ @Operation(
+ summary = "Convert a URL to a PDF",
+ description =
+ "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO")
+ public ResponseEntity urlToPdf(@ModelAttribute UrlToPdfRequest request)
+ throws IOException, InterruptedException {
+ String URL = request.getUrlInput();
- // 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 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);
- }
+ // 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");
- 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";
- }
+ // Prepare the OCRmyPDF command
+ List 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";
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java
index 6398e8b9..c5b9ea8d 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractController.java
@@ -22,6 +22,7 @@ import com.opencsv.CSVWriter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.controller.api.CropController;
import stirling.software.SPDF.controller.api.strippers.PDFTableStripper;
import stirling.software.SPDF.model.api.extract.PDFFilePage;
@@ -34,21 +35,24 @@ public class ExtractController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
- @Operation(summary = "Extracts a PDF document to csv", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
- public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form)
- throws Exception {
+ @Operation(
+ summary = "Extracts a PDF document to csv",
+ description =
+ "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
+ public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
ArrayList tableData = new ArrayList<>();
int columnsCount = 0;
- try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
+ try (PDDocument document =
+ PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
final double res = 72; // PDF units are at 72 DPI
PDFTableStripper stripper = new PDFTableStripper();
PDPage pdPage = document.getPage(form.getPageId() - 1);
stripper.extractTable(pdPage);
columnsCount = stripper.getColumns();
for (int c = 0; c < columnsCount; ++c) {
- for(int r=0; r notEmptyColumns = new ArrayList<>();
- for (String item: tableData) {
- if(!item.trim().isEmpty()){
+ for (String item : tableData) {
+ if (!item.trim().isEmpty()) {
notEmptyColumns.add(item);
- }else{
+ } else {
columnsCount--;
}
}
- List fullTable = notEmptyColumns.stream().map((entity)->
- entity.replace('\n',' ').replace('\r',' ').trim().replaceAll("\\s{2,}", "|")).toList();
+ List fullTable =
+ notEmptyColumns.stream()
+ .map(
+ (entity) ->
+ entity.replace('\n', ' ')
+ .replace('\r', ' ')
+ .trim()
+ .replaceAll("\\s{2,}", "|"))
+ .toList();
int rowsCount = fullTable.get(0).split("\\|").length;
- ArrayList headersList = getTableHeaders(columnsCount,fullTable);
- ArrayList recordList = getRecordsList(rowsCount,fullTable);
-
- if(headersList.size() == 0 && recordList.size() == 0) {
- throw new Exception("No table detected, no headers or records found");
+ ArrayList headersList = getTableHeaders(columnsCount, fullTable);
+ ArrayList recordList = getRecordsList(rowsCount, fullTable);
+
+ if (headersList.size() == 0 && recordList.size() == 0) {
+ throw new Exception("No table detected, no headers or records found");
}
-
+
StringWriter writer = new StringWriter();
try (CSVWriter csvWriter = new CSVWriter(writer)) {
csvWriter.writeNext(headersList.toArray(new String[0]));
@@ -85,35 +96,41 @@ public class ExtractController {
}
HttpHeaders headers = new HttpHeaders();
- headers.setContentDisposition(ContentDisposition.builder("attachment").filename(form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted.csv").build());
+ headers.setContentDisposition(
+ ContentDisposition.builder("attachment")
+ .filename(
+ form.getFileInput()
+ .getOriginalFilename()
+ .replaceFirst("[.][^.]+$", "")
+ + "_extracted.csv")
+ .build());
headers.setContentType(MediaType.parseMediaType("text/csv"));
- return ResponseEntity.ok()
- .headers(headers)
- .body(writer.toString());
+ return ResponseEntity.ok().headers(headers).body(writer.toString());
}
- private ArrayList getRecordsList( int rowsCounts ,List items){
+ private ArrayList getRecordsList(int rowsCounts, List items) {
ArrayList recordsList = new ArrayList<>();
- for (int b=1; b getTableHeaders(int columnsCount, List items){
+
+ private ArrayList getTableHeaders(int columnsCount, List items) {
ArrayList resultList = new ArrayList<>();
- for (int i=0;i containsText(@ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String text = request.getText();
- String pageNumber = request.getPageNumbers();
-
- PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
- if (PdfUtils.hasText(pdfDocument, pageNumber, text))
- return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
- return null;
- }
+ @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 containsText(@ModelAttribute ContainsTextRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String text = request.getText();
+ String pageNumber = request.getPageNumbers();
- // 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 containsImage(@ModelAttribute PDFWithPageNums request)
- throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String pageNumber = request.getPageNumbers();
-
- PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
- if (PdfUtils.hasImages(pdfDocument, pageNumber))
- return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
- return null;
- }
+ PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
+ if (PdfUtils.hasText(pdfDocument, pageNumber, text))
+ 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 pageCount(@ModelAttribute PDFComparisonAndCount request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String pageCount = request.getPageCount();
- String comparator = request.getComparator();
- // Load the PDF
- PDDocument document = PDDocument.load(inputFile.getInputStream());
- int actualPageCount = document.getNumberOfPages();
+ // 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 containsImage(@ModelAttribute PDFWithPageNums request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String pageNumber = request.getPageNumbers();
- 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);
- }
+ PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
+ if (PdfUtils.hasImages(pdfDocument, pageNumber))
+ return WebResponseUtils.pdfDocToWebResponse(
+ pdfDocument, inputFile.getOriginalFilename());
+ return null;
+ }
- if (valid)
- return WebResponseUtils.multiPartFileToWebResponse(inputFile);
- 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 pageCount(@ModelAttribute PDFComparisonAndCount request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String pageCount = request.getPageCount();
+ String comparator = request.getComparator();
+ // Load the PDF
+ PDDocument document = PDDocument.load(inputFile.getInputStream());
+ int actualPageCount = document.getNumberOfPages();
- @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 pageSize(@ModelAttribute PageSizeRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String standardPageSize = request.getStandardPageSize();
- String comparator = request.getComparator();
+ 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);
+ }
- // Load the PDF
- PDDocument document = PDDocument.load(inputFile.getInputStream());
+ if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
+ return null;
+ }
- PDPage firstPage = document.getPage(0);
- PDRectangle actualPageSize = firstPage.getMediaBox();
+ @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 pageSize(@ModelAttribute PageSizeRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String standardPageSize = request.getStandardPageSize();
+ String comparator = request.getComparator();
- // Calculate the area of the actual page size
- float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
+ // Load the PDF
+ PDDocument document = PDDocument.load(inputFile.getInputStream());
- // Get the standard size and calculate its area
- PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
- float standardArea = standardSize.getWidth() * standardSize.getHeight();
+ PDPage firstPage = document.getPage(0);
+ PDRectangle actualPageSize = firstPage.getMediaBox();
- 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);
- }
+ // Calculate the area of the actual page size
+ float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
- if (valid)
- return WebResponseUtils.multiPartFileToWebResponse(inputFile);
- return null;
- }
+ // Get the standard size and calculate its area
+ PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
+ float standardArea = standardSize.getWidth() * standardSize.getHeight();
- @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 fileSize(@ModelAttribute FileSizeRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- String fileSize = request.getFileSize();
- String comparator = request.getComparator();
+ 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);
+ }
- // Get the file size
- long actualFileSize = inputFile.getSize();
+ if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
+ return null;
+ }
- 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);
- }
+ @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 fileSize(@ModelAttribute FileSizeRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ String fileSize = request.getFileSize();
+ String comparator = request.getComparator();
- if (valid)
- return WebResponseUtils.multiPartFileToWebResponse(inputFile);
- return null;
- }
+ // Get the file size
+ long actualFileSize = inputFile.getSize();
- @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 pageRotation(@ModelAttribute PageRotationRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- int rotation = request.getRotation();
- String comparator = request.getComparator();
+ 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);
+ }
- // Load the PDF
- PDDocument document = PDDocument.load(inputFile.getInputStream());
+ if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
+ return null;
+ }
- // 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);
- }
+ @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 pageRotation(@ModelAttribute PageRotationRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ int rotation = request.getRotation();
+ String comparator = request.getComparator();
- if (valid)
- return WebResponseUtils.multiPartFileToWebResponse(inputFile);
- return null;
+ // 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;
+ }
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java
index fe8337d2..e81ef1e1 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoRenameController.java
@@ -19,8 +19,10 @@ 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.model.api.misc.ExtractHeaderRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@@ -32,97 +34,105 @@ public class AutoRenameController {
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 extractHeader(@ModelAttribute ExtractHeaderRequest request) throws Exception {
+ @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 extractHeader(@ModelAttribute ExtractHeaderRequest request)
+ throws Exception {
MultipartFile file = request.getFileInput();
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
- PDDocument document = PDDocument.load(file.getInputStream());
- PDFTextStripper reader = new PDFTextStripper() {
- class LineInfo {
- String text;
- float fontSize;
+ 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;
- }
- }
+ LineInfo(String text, float fontSize) {
+ this.text = text;
+ this.fontSize = fontSize;
+ }
+ }
- List lineInfos = new ArrayList<>();
- StringBuilder lineBuilder = new StringBuilder();
- float lastY = -1;
- float maxFontSizeInLine = 0.0f;
- int lineCount = 0;
+ List 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();
- }
- }
- }
+ @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));
- }
- }
+ 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
+ @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 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));
- }
+ // Merge lines with same font size
+ List 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;
+ // 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);
- }
+ return title != null
+ ? title
+ : (useFirstTextAsFallback
+ ? (mergedLineInfos.isEmpty()
+ ? null
+ : mergedLineInfos.get(mergedLineInfos.size() - 1)
+ .text)
+ : null);
+ }
+ };
- };
+ String header = reader.getText(document);
- 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());
+ logger.info("File has no good title to be found");
+ return WebResponseUtils.pdfDocToWebResponse(document, file.getOriginalFilename());
}
}
-
-
-
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java
index e62fc35f..9b447fcb 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.controller.api.misc;
+
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
@@ -32,6 +33,7 @@ import com.google.zxing.common.HybridBinarizer;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -40,11 +42,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class AutoSplitPdfController {
- private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
+ private static final String QR_CONTENT = "https://github.com/Stirling-Tools/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-PDF Type:SISO")
- public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException {
+ @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-PDF Type:SISO")
+ public ResponseEntity autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
+ throws IOException {
MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode();
@@ -107,29 +113,48 @@ public class AutoSplitPdfController {
} catch (Exception e) {
e.printStackTrace();
} finally {
- data = Files.readAllBytes(zipFile);
+ data = Files.readAllBytes(zipFile);
Files.delete(zipFile);
}
- return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
+ 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);
+ 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);
+ 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");
+ 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));
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java
index c52ff61a..036d6a66 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/BlankPageController.java
@@ -28,6 +28,7 @@ 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.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@@ -39,17 +40,18 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class BlankPageController {
- @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
- @Operation(
- 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. Input:PDF Output:PDF Type:SISO"
- )
- public ResponseEntity removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request) throws IOException, InterruptedException {
- MultipartFile inputFile = request.getFileInput();
- int threshold = request.getThreshold();
- float whitePercent = request.getWhitePercent();
-
- PDDocument document = null;
+ @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
+ @Operation(
+ 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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request)
+ throws IOException, InterruptedException {
+ MultipartFile inputFile = request.getFileInput();
+ int threshold = request.getThreshold();
+ float whitePercent = request.getWhitePercent();
+
+ PDDocument document = null;
try {
document = PDDocument.load(inputFile.getInputStream());
PDPageTree pages = document.getDocumentCatalog().getPages();
@@ -72,21 +74,34 @@ public class BlankPageController {
boolean hasImages = PdfUtils.hasImagesOnPage(page);
if (hasImages) {
System.out.println("page " + pageIndex + " has image");
-
+
Path tempFile = Files.createTempFile("image_", ".png");
-
+
// Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300);
ImageIO.write(image, "png", tempFile.toFile());
-
- List 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 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
- ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command);
-
+ ProcessExecutorResult returnCode =
+ ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
+ .runCommandWithOutputHandling(command);
+
// does contain data
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);
} else {
System.out.println("Skipping, Image was blank for page #" + pageIndex);
@@ -94,12 +109,12 @@ public class BlankPageController {
}
}
pageIndex++;
-
}
System.out.print("pagesToKeep=" + pagesToKeepIndex.size());
// Remove pages not present in pagesToKeepIndex
- List pageIndices = IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
+ List pageIndices =
+ IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
Collections.reverse(pageIndices); // Reverse to prevent index shifting during removal
for (Integer i : pageIndices) {
if (!pagesToKeepIndex.contains(i)) {
@@ -107,16 +122,15 @@ public class BlankPageController {
}
}
- return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_blanksRemoved.pdf");
+ return WebResponseUtils.pdfDocToWebResponse(
+ document,
+ inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ + "_blanksRemoved.pdf");
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
- if (document != null)
- document.close();
+ if (document != null) document.close();
}
}
-
-
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java
index dd864bc1..fd9a0460 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java
@@ -30,6 +30,7 @@ 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.model.api.misc.OptimizePdfRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
@@ -44,20 +45,23 @@ public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
@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. Input:PDF Output:PDF Type:SISO")
- public ResponseEntity optimizePdf(@ModelAttribute OptimizePdfRequest request) throws Exception {
+ @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 optimizePdf(@ModelAttribute OptimizePdfRequest request)
+ throws Exception {
MultipartFile inputFile = request.getFileInput();
Integer optimizeLevel = request.getOptimizeLevel();
String expectedOutputSizeString = request.getExpectedOutputSize();
-
- if(expectedOutputSizeString == null && optimizeLevel == null) {
+ if (expectedOutputSizeString == null && optimizeLevel == null) {
throw new Exception("Both expected output size and optimize level are not specified");
}
Long expectedOutputSize = 0L;
boolean autoMode = false;
- if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) {
+ if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1) {
expectedOutputSize = GeneralUtils.convertSizeToBytes(expectedOutputSizeString);
autoMode = true;
}
@@ -71,8 +75,9 @@ public class CompressController {
// Prepare the output file path
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
- // Determine initial optimization level based on expected size reduction, only if in autoMode
- if(autoMode) {
+ // Determine initial optimization level based on expected size reduction, only if in
+ // autoMode
+ if (autoMode) {
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
if (sizeReductionRatio > 0.7) {
optimizeLevel = 1;
@@ -94,20 +99,20 @@ public class CompressController {
command.add("-dCompatibilityLevel=1.4");
switch (optimizeLevel) {
- case 1:
- command.add("-dPDFSETTINGS=/prepress");
- break;
- case 2:
- command.add("-dPDFSETTINGS=/printer");
- break;
- case 3:
- command.add("-dPDFSETTINGS=/ebook");
- break;
- case 4:
- command.add("-dPDFSETTINGS=/screen");
- break;
- default:
- command.add("-dPDFSETTINGS=/default");
+ case 1:
+ command.add("-dPDFSETTINGS=/prepress");
+ break;
+ case 2:
+ command.add("-dPDFSETTINGS=/printer");
+ break;
+ case 3:
+ command.add("-dPDFSETTINGS=/ebook");
+ break;
+ case 4:
+ command.add("-dPDFSETTINGS=/screen");
+ break;
+ default:
+ command.add("-dPDFSETTINGS=/default");
}
command.add("-dNOPAUSE");
@@ -116,7 +121,9 @@ public class CompressController {
command.add("-sOutputFile=" + tempOutputFile.toString());
command.add(tempInputFile.toString());
- ProcessExecutorResult 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
long outputFileSize = Files.size(tempOutputFile);
@@ -125,19 +132,18 @@ public class CompressController {
} else {
// Increase optimization level for next iteration
optimizeLevel++;
- if(autoMode && optimizeLevel > 3) {
+ if (autoMode && optimizeLevel > 3) {
System.out.println("Skipping level 4 due to bad results in auto mode");
sizeMet = true;
- } else if(optimizeLevel == 5) {
-
+ } else if (optimizeLevel == 5) {
+
} else {
- System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel);
+ System.out.println(
+ "Increasing ghostscript optimisation level to " + optimizeLevel);
}
}
}
-
-
if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize > expectedOutputSize) {
@@ -157,8 +163,8 @@ public class CompressController {
BufferedImage bufferedImage = image.getImage();
// Calculate the new dimensions
- int newWidth = (int)(bufferedImage.getWidth() * scaleFactor);
- int newHeight = (int)(bufferedImage.getHeight() * scaleFactor);
+ int newWidth = (int) (bufferedImage.getWidth() * scaleFactor);
+ int newHeight = (int) (bufferedImage.getHeight() * scaleFactor);
// If the new dimensions are zero, skip this iteration
if (newWidth == 0 || newHeight == 0) {
@@ -166,23 +172,39 @@ public class CompressController {
}
// Otherwise, proceed with the scaling
- Image scaledImage = bufferedImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
+ Image scaledImage =
+ bufferedImage.getScaledInstance(
+ newWidth, newHeight, Image.SCALE_SMOOTH);
// Convert the scaled image back to a BufferedImage
- BufferedImage scaledBufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
- scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
+ BufferedImage scaledBufferedImage =
+ new BufferedImage(
+ newWidth,
+ newHeight,
+ BufferedImage.TYPE_INT_RGB);
+ scaledBufferedImage
+ .getGraphics()
+ .drawImage(scaledImage, 0, 0, null);
// Compress the scaled image
- ByteArrayOutputStream compressedImageStream = new ByteArrayOutputStream();
- ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream);
+ ByteArrayOutputStream compressedImageStream =
+ new ByteArrayOutputStream();
+ ImageIO.write(
+ scaledBufferedImage, "jpeg", compressedImageStream);
byte[] imageBytes = compressedImageStream.toByteArray();
compressedImageStream.close();
// Convert compressed image back to PDImageXObject
- ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
- PDImageXObject compressedImage = PDImageXObject.createFromByteArray(doc, imageBytes, image.getCOSObject().toString());
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream(imageBytes);
+ PDImageXObject compressedImage =
+ PDImageXObject.createFromByteArray(
+ doc,
+ imageBytes,
+ image.getCOSObject().toString());
- // Replace the image in the resources with the compressed version
+ // Replace the image in the resources with the compressed
+ // version
res.put(name, compressedImage);
}
}
@@ -194,16 +216,23 @@ public class CompressController {
long currentSize = Files.size(tempOutputFile);
// Check if the overall PDF size is still larger than expectedOutputSize
if (currentSize > expectedOutputSize) {
- // Log the current file size and scaleFactor
-
- System.out.println("Current file size: " + FileUtils.byteCountToDisplaySize(currentSize));
+ // Log the current file size and scaleFactor
+
+ System.out.println(
+ "Current file size: "
+ + FileUtils.byteCountToDisplaySize(currentSize));
System.out.println("Current scale factor: " + scaleFactor);
// The file is still too large, reduce scaleFactor and try again
scaleFactor *= 0.9; // reduce scaleFactor by 10%
// Avoid scaleFactor being too small, causing the image to shrink to 0
- if(scaleFactor < 0.2 || previousFileSize == currentSize){
- throw new RuntimeException("Could not reach the desired size without excessively degrading image quality, lowest size recommended is " + FileUtils.byteCountToDisplaySize(currentSize) + ", " + currentSize + " bytes");
+ if (scaleFactor < 0.2 || previousFileSize == currentSize) {
+ throw new RuntimeException(
+ "Could not reach the desired size without excessively degrading image quality, lowest size recommended is "
+ + FileUtils.byteCountToDisplaySize(currentSize)
+ + ", "
+ + currentSize
+ + " bytes");
}
previousFileSize = currentSize;
} else {
@@ -211,10 +240,7 @@ public class CompressController {
break;
}
}
-
}
-
-
}
}
@@ -222,9 +248,10 @@ public class CompressController {
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
// Check if optimized file is larger than the original
- if(pdfBytes.length > inputFileSize) {
+ if (pdfBytes.length > inputFileSize) {
// Log the occurrence
- logger.warn("Optimized file is larger than the original. Returning the original file instead.");
+ logger.warn(
+ "Optimized file is larger than the original. Returning the original file instead.");
// Read the original file again
pdfBytes = Files.readAllBytes(tempInputFile);
@@ -235,8 +262,8 @@ public class CompressController {
Files.delete(tempOutputFile);
// Return the optimized PDF as a response
- String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf";
+ String outputFilename =
+ inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java
index d5906970..257f4d52 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java
@@ -32,10 +32,12 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@@ -44,18 +46,28 @@ public class ExtractImageScansController {
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
- @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. Input:PDF Output:IMAGE/ZIP Type:SIMO")
+ @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. Input:PDF Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity extractImageScans(
- @RequestBody(
- description = "Form data containing file and extraction parameters",
- required = true,
- content = @Content(
- mediaType = "multipart/form-data",
- schema = @Schema(implementation = ExtractImageScansRequest.class) // This should represent your form's structure
- )
- )
- ExtractImageScansRequest form) throws IOException, InterruptedException {
+ @RequestBody(
+ description = "Form data containing file and extraction parameters",
+ required = true,
+ content =
+ @Content(
+ mediaType = "multipart/form-data",
+ schema =
+ @Schema(
+ implementation =
+ ExtractImageScansRequest
+ .class) // This should
+ // represent
+ // your form's
+ // structure
+ ))
+ ExtractImageScansRequest form)
+ throws IOException, InterruptedException {
String fileName = form.getFileInput().getOriginalFilename();
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
@@ -64,7 +76,8 @@ public class ExtractImageScansController {
// Check if input file is a PDF
if (extension.equalsIgnoreCase("pdf")) {
// Load PDF document
- try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
+ try (PDDocument document =
+ PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages();
images = new ArrayList<>();
@@ -84,7 +97,10 @@ public class ExtractImageScansController {
}
} else {
Path tempInputFile = Files.createTempFile("input_", "." + extension);
- Files.copy(form.getFileInput().getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(
+ form.getFileInput().getInputStream(),
+ tempInputFile,
+ StandardCopyOption.REPLACE_EXISTING);
// Add input file path to images list
images.add(tempInputFile.toString());
}
@@ -95,21 +111,28 @@ public class ExtractImageScansController {
for (int i = 0; i < images.size(); i++) {
Path tempDir = Files.createTempDirectory("openCV_output");
- List command = new ArrayList<>(Arrays.asList(
- "python3",
- "./scripts/split_photos.py",
- images.get(i),
- tempDir.toString(),
- "--angle_threshold", String.valueOf(form.getAngleThreshold()),
- "--tolerance", String.valueOf(form.getTolerance()),
- "--min_area", String.valueOf(form.getMinArea()),
- "--min_contour_area", String.valueOf(form.getMinContourArea()),
- "--border_size", String.valueOf(form.getBorderSize())
- ));
-
+ List command =
+ new ArrayList<>(
+ Arrays.asList(
+ "python3",
+ "./scripts/split_photos.py",
+ images.get(i),
+ tempDir.toString(),
+ "--angle_threshold",
+ String.valueOf(form.getAngleThreshold()),
+ "--tolerance",
+ String.valueOf(form.getTolerance()),
+ "--min_area",
+ String.valueOf(form.getMinArea()),
+ "--min_contour_area",
+ String.valueOf(form.getMinContourArea()),
+ "--border_size",
+ String.valueOf(form.getBorderSize())));
// Run CLI command
- ProcessExecutorResult 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
List tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList());
@@ -126,10 +149,16 @@ public class ExtractImageScansController {
String outputZipFilename = fileName.replaceFirst("[.][^.]+$", "") + "_processed.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip");
- try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
+ try (ZipOutputStream zipOut =
+ new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
// Add processed images to the zip
for (int i = 0; i < processedImageBytes.size(); i++) {
- ZipEntry entry = new ZipEntry(fileName.replaceFirst("[.][^.]+$", "") + "_" + (i + 1) + ".png");
+ ZipEntry entry =
+ new ZipEntry(
+ fileName.replaceFirst("[.][^.]+$", "")
+ + "_"
+ + (i + 1)
+ + ".png");
zipOut.putNextEntry(entry);
zipOut.write(processedImageBytes.get(i));
zipOut.closeEntry();
@@ -141,13 +170,15 @@ public class ExtractImageScansController {
// Clean up the temporary zip file
Files.delete(tempZipFile);
- return WebResponseUtils.bytesToWebResponse(zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
+ return WebResponseUtils.bytesToWebResponse(
+ zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
} else {
// Return the processed image as a response
byte[] imageBytes = processedImageBytes.get(0);
- return WebResponseUtils.bytesToWebResponse(imageBytes, fileName.replaceFirst("[.][^.]+$", "") + ".png", MediaType.IMAGE_PNG);
+ return WebResponseUtils.bytesToWebResponse(
+ imageBytes,
+ fileName.replaceFirst("[.][^.]+$", "") + ".png",
+ MediaType.IMAGE_PNG);
}
-
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java
index 6e18f1f2..f436d9f6 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java
@@ -1,4 +1,5 @@
package stirling.software.SPDF.controller.api.misc;
+
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
@@ -29,8 +30,10 @@ 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.model.api.PDFWithImageFormatRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
+
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
@@ -39,13 +42,17 @@ public class ExtractImagesController {
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
- @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. Input:PDF Output:IMAGE/ZIP Type:SIMO")
- public ResponseEntity extractImages(@ModelAttribute PDFWithImageFormatRequest request) throws IOException {
+ @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. Input:PDF Output:IMAGE/ZIP Type:SIMO")
+ public ResponseEntity extractImages(@ModelAttribute PDFWithImageFormatRequest request)
+ throws IOException {
MultipartFile file = request.getFileInput();
String format = request.getFormat();
- System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
+ System.out.println(
+ System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
PDDocument document = PDDocument.load(file.getBytes());
// Create ByteArrayOutputStream to write zip file to byte array
@@ -69,24 +76,37 @@ public class ExtractImagesController {
if (page.getResources().isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
int imageHash = image.hashCode();
- if(processedImages.contains(imageHash)) {
+ if (processedImages.contains(imageHash)) {
continue; // Skip already processed images
}
processedImages.add(imageHash);
-
+
// Convert image to desired format
RenderedImage renderedImage = image.getImage();
BufferedImage bufferedImage = null;
if (format.equalsIgnoreCase("png")) {
- bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ bufferedImage =
+ new BufferedImage(
+ renderedImage.getWidth(),
+ renderedImage.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
} else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) {
- bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_INT_RGB);
+ bufferedImage =
+ new BufferedImage(
+ renderedImage.getWidth(),
+ renderedImage.getHeight(),
+ BufferedImage.TYPE_INT_RGB);
} else if (format.equalsIgnoreCase("gif")) {
- bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
+ bufferedImage =
+ new BufferedImage(
+ renderedImage.getWidth(),
+ renderedImage.getHeight(),
+ BufferedImage.TYPE_BYTE_INDEXED);
}
// Write image to zip file
- String imageName = filename + "_" + imageIndex + " (Page " + pageNum + ")." + format;
+ String imageName =
+ filename + "_" + imageIndex + " (Page " + pageNum + ")." + format;
ZipEntry zipEntry = new ZipEntry(imageName);
zos.putNextEntry(zipEntry);
@@ -111,7 +131,7 @@ public class ExtractImagesController {
// Create ByteArrayResource from byte array
byte[] zipContents = baos.toByteArray();
- return WebResponseUtils.boasToWebResponse(baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
+ return WebResponseUtils.boasToWebResponse(
+ baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java b/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java
index 099e8411..e9885f1e 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/FakeScanControllerWIP.java
@@ -3,21 +3,17 @@ package stirling.software.SPDF.controller.api.misc;
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;
import java.io.ByteArrayOutputStream;
-//Required for file input/output
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
-//Other required classes
import java.util.Random;
-//Required for image input/output
import javax.imageio.ImageIO;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -40,6 +36,7 @@ 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.model.api.PDFFile;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -50,102 +47,101 @@ public class FakeScanControllerWIP {
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
- //TODO
+ // TODO
@Hidden
@PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
@Operation(
- 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."
- )
+ 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.")
public ResponseEntity repairPdf(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput();
- PDDocument document = PDDocument.load(inputFile.getBytes());
- PDFRenderer pdfRenderer = new PDFRenderer(document);
- for (int page = 0; page < document.getNumberOfPages(); ++page)
- {
- BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
- ImageIO.write(image, "png", new File("scanned-" + (page+1) + ".png"));
- }
- document.close();
+ PDDocument document = PDDocument.load(inputFile.getBytes());
+ PDFRenderer pdfRenderer = new PDFRenderer(document);
+ for (int page = 0; page < document.getNumberOfPages(); ++page) {
+ BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
+ ImageIO.write(image, "png", new File("scanned-" + (page + 1) + ".png"));
+ }
+ document.close();
- // Constants
- int scannedness = 90; // Value between 0 and 100
- int dirtiness = 0; // Value between 0 and 100
+ // Constants
+ int scannedness = 90; // Value between 0 and 100
+ int dirtiness = 0; // Value between 0 and 100
- // Load the source image
- BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png"));
+ // Load the source image
+ BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png"));
- // Create the destination image
- BufferedImage destinationImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType());
+ // Create the destination image
+ BufferedImage destinationImage =
+ new BufferedImage(
+ sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType());
- // Apply a brightness and contrast effect based on the "scanned-ness"
- float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5
- float offset = scannedness * 1.5f; // Between 0 and 150
- BufferedImageOp op = new RescaleOp(scaleFactor, offset, null);
- op.filter(sourceImage, destinationImage);
+ // Apply a brightness and contrast effect based on the "scanned-ness"
+ float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5
+ float offset = scannedness * 1.5f; // Between 0 and 150
+ BufferedImageOp op = new RescaleOp(scaleFactor, offset, null);
+ op.filter(sourceImage, destinationImage);
- // Apply a rotation effect
- double rotationRequired = Math.toRadians((new SecureRandom().nextInt(3 - 1) + 1)); // Random angle between 1 and 3 degrees
- double locationX = destinationImage.getWidth() / 2;
- double locationY = destinationImage.getHeight() / 2;
- AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
- AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
- destinationImage = rotateOp.filter(destinationImage, null);
+ // Apply a rotation effect
+ double rotationRequired =
+ Math.toRadians(
+ (new SecureRandom().nextInt(3 - 1)
+ + 1)); // Random angle between 1 and 3 degrees
+ double locationX = destinationImage.getWidth() / 2;
+ double locationY = destinationImage.getHeight() / 2;
+ AffineTransform tx =
+ AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
+ AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
+ destinationImage = rotateOp.filter(destinationImage, null);
- // Apply a blur effect based on the "scanned-ness"
- float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2
- float[] matrix = {
- blurIntensity, blurIntensity, blurIntensity,
- blurIntensity, blurIntensity, blurIntensity,
- blurIntensity, blurIntensity, blurIntensity
- };
- BufferedImageOp blurOp = new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null);
- destinationImage = blurOp.filter(destinationImage, null);
+ // Apply a blur effect based on the "scanned-ness"
+ float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2
+ float[] matrix = {
+ blurIntensity, blurIntensity, blurIntensity,
+ blurIntensity, blurIntensity, blurIntensity,
+ blurIntensity, blurIntensity, blurIntensity
+ };
+ BufferedImageOp blurOp =
+ new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null);
+ destinationImage = blurOp.filter(destinationImage, null);
- // Add noise to the image based on the "dirtiness"
- Random random = new SecureRandom();
- for (int y = 0; y < destinationImage.getHeight(); y++) {
- for (int x = 0; x < destinationImage.getWidth(); x++) {
- if (random.nextInt(100) < dirtiness) {
- // Change the pixel color to black randomly based on the "dirtiness"
- destinationImage.setRGB(x, y, Color.BLACK.getRGB());
- }
- }
- }
+ // Add noise to the image based on the "dirtiness"
+ Random random = new SecureRandom();
+ for (int y = 0; y < destinationImage.getHeight(); y++) {
+ for (int x = 0; x < destinationImage.getWidth(); x++) {
+ if (random.nextInt(100) < dirtiness) {
+ // Change the pixel color to black randomly based on the "dirtiness"
+ destinationImage.setRGB(x, y, Color.BLACK.getRGB());
+ }
+ }
+ }
- // Save the image
- ImageIO.write(destinationImage, "PNG", new File("scanned-1.png"));
+ // Save the image
+ ImageIO.write(destinationImage, "PNG", new File("scanned-1.png"));
+ PDDocument documentOut = new PDDocument();
+ for (int page = 1; page <= document.getNumberOfPages(); ++page) {
+ BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png"));
-
-
-
-
+ // Adjust the dimensions of the page
+ PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1));
+ documentOut.addPage(pdPage);
- PDDocument documentOut = new PDDocument();
- for (int page = 1; page <= document.getNumberOfPages(); ++page)
- {
- BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png"));
-
- // Adjust the dimensions of the page
- PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1));
- documentOut.addPage(pdPage);
-
- PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim);
- PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage);
-
- // Draw the image with a slight offset and enlarged dimensions
- contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2);
- contentStream.close();
- }
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- documentOut.save(baos);
- documentOut.close();
+ PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim);
+ PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage);
+
+ // Draw the image with a slight offset and enlarged dimensions
+ contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2);
+ contentStream.close();
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ documentOut.save(baos);
+ documentOut.close();
// Return the optimized PDF as a response
- String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf";
+ String outputFilename =
+ inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf";
return WebResponseUtils.boasToWebResponse(baos, outputFilename);
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java
index 027c6240..62783dc4 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java
@@ -19,6 +19,7 @@ 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.model.api.misc.MetadataRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -27,7 +28,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class MetadataController {
-
private String checkUndefined(String entry) {
// Check if the string is "undefined"
if ("undefined".equals(entry)) {
@@ -36,14 +36,16 @@ public class MetadataController {
}
// Return the original string if it's not "undefined"
return entry;
-
}
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
- @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. Input:PDF Output:PDF Type:SISO")
- public ResponseEntity metadata(@ModelAttribute MetadataRequest request) throws IOException {
-
+ @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. Input:PDF Output:PDF Type:SISO")
+ public ResponseEntity metadata(@ModelAttribute MetadataRequest request)
+ throws IOException {
+
// Extract PDF file from the request object
MultipartFile pdfFile = request.getFileInput();
@@ -61,8 +63,8 @@ public class MetadataController {
// Extract additional custom parameters
Map allRequestParams = request.getAllRequestParams();
- if(allRequestParams == null) {
- allRequestParams = new java.util.HashMap();
+ if (allRequestParams == null) {
+ allRequestParams = new java.util.HashMap();
}
// Load the PDF file into a PDDocument
PDDocument document = PDDocument.load(pdfFile.getBytes());
@@ -89,7 +91,9 @@ public class MetadataController {
}
// Remove metadata from the PDF history
document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName("Metadata"));
- document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName("PieceInfo"));
+ document.getDocumentCatalog()
+ .getCOSObject()
+ .removeItem(COSName.getPDFName("PieceInfo"));
author = null;
creationDate = null;
creator = null;
@@ -104,9 +108,17 @@ public class MetadataController {
for (Entry entry : allRequestParams.entrySet()) {
String key = entry.getKey();
// Check if the key is a standard metadata key
- if (!key.equalsIgnoreCase("Author") && !key.equalsIgnoreCase("CreationDate") && !key.equalsIgnoreCase("Creator") && !key.equalsIgnoreCase("Keywords")
- && !key.equalsIgnoreCase("modificationDate") && !key.equalsIgnoreCase("Producer") && !key.equalsIgnoreCase("Subject") && !key.equalsIgnoreCase("Title")
- && !key.equalsIgnoreCase("Trapped") && !key.contains("customKey") && !key.contains("customValue")) {
+ if (!key.equalsIgnoreCase("Author")
+ && !key.equalsIgnoreCase("CreationDate")
+ && !key.equalsIgnoreCase("Creator")
+ && !key.equalsIgnoreCase("Keywords")
+ && !key.equalsIgnoreCase("modificationDate")
+ && !key.equalsIgnoreCase("Producer")
+ && !key.equalsIgnoreCase("Subject")
+ && !key.equalsIgnoreCase("Title")
+ && !key.equalsIgnoreCase("Trapped")
+ && !key.contains("customKey")
+ && !key.contains("customValue")) {
info.setCustomMetadataValue(key, entry.getValue());
} else if (key.contains("customKey")) {
int number = Integer.parseInt(key.replaceAll("\\D", ""));
@@ -119,7 +131,8 @@ public class MetadataController {
if (creationDate != null && creationDate.length() > 0) {
Calendar creationDateCal = Calendar.getInstance();
try {
- creationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
+ creationDateCal.setTime(
+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
} catch (ParseException e) {
e.printStackTrace();
}
@@ -130,7 +143,8 @@ public class MetadataController {
if (modificationDate != null && modificationDate.length() > 0) {
Calendar modificationDateCal = Calendar.getInstance();
try {
- modificationDateCal.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
+ modificationDateCal.setTime(
+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
} catch (ParseException e) {
e.printStackTrace();
}
@@ -147,7 +161,8 @@ public class MetadataController {
info.setTrapped(trapped);
document.setDocumentInformation(info);
- return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf");
+ return WebResponseUtils.pdfDocToWebResponse(
+ document,
+ pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf");
}
-
}
diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java
index 5ea1818e..21cf2b1c 100644
--- a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java
+++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java
@@ -26,6 +26,7 @@ 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.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -44,14 +45,21 @@ public class OCRController {
if (files == null) {
return Collections.emptyList();
}
- return Arrays.stream(files).filter(file -> file.getName().endsWith(".traineddata")).map(file -> file.getName().replace(".traineddata", ""))
- .filter(lang -> !lang.equalsIgnoreCase("osd")).collect(Collectors.toList());
+ return Arrays.stream(files)
+ .filter(file -> file.getName().endsWith(".traineddata"))
+ .map(file -> file.getName().replace(".traineddata", ""))
+ .filter(lang -> !lang.equalsIgnoreCase("osd"))
+ .collect(Collectors.toList());
}
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
- @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. Input:PDF Output:PDF Type:SI-Conditional")
- public ResponseEntity processPdfWithOCR(@ModelAttribute ProcessPdfWithOcrRequest request) throws IOException, InterruptedException {
+ @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. Input:PDF Output:PDF Type:SI-Conditional")
+ public ResponseEntity processPdfWithOCR(
+ @ModelAttribute ProcessPdfWithOcrRequest request)
+ throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput();
List selectedLanguages = request.getLanguages();
Boolean sidecar = request.isSidecar();
@@ -65,16 +73,17 @@ public class OCRController {
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
throw new IOException("Please select at least one language.");
}
-
- if(!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) {
+
+ if (!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) {
throw new IOException("ocrRenderType wrong");
}
-
+
// Get available Tesseract languages
List availableLanguages = getAvailableTesseractLanguages();
// Validate selected languages
- selectedLanguages = selectedLanguages.stream().filter(availableLanguages::contains).toList();
+ selectedLanguages =
+ selectedLanguages.stream().filter(availableLanguages::contains).toList();
if (selectedLanguages.isEmpty()) {
throw new IOException("None of the selected languages are valid.");
@@ -92,8 +101,16 @@ public class OCRController {
// Run OCR Command
String languageOption = String.join("+", selectedLanguages);
-
- List command = new ArrayList<>(Arrays.asList("ocrmypdf", "--verbose", "2", "--output-type", "pdf", "--pdf-renderer" , ocrRenderType));
+ List command =
+ new ArrayList<>(
+ Arrays.asList(
+ "ocrmypdf",
+ "--verbose",
+ "2",
+ "--output-type",
+ "pdf",
+ "--pdf-renderer",
+ ocrRenderType));
if (sidecar != null && sidecar) {
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
@@ -120,42 +137,61 @@ 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
- 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);
+ 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);
}
-
-
-
// Remove images from the OCR processed PDF if the flag is set to true
if (removeImagesAfter != null && removeImagesAfter) {
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
- List