Merge branch 'Stirling-Tools:main' into main

This commit is contained in:
leo-jmateo 2025-03-16 12:26:26 +01:00 committed by GitHub
commit cc3bff2f52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
114 changed files with 1146 additions and 782 deletions

View File

@ -72,7 +72,8 @@ Devtools:
Test:
- changed-files:
- any-glob-to-any-file: 'cucumber/**/*'
- any-glob-to-any-file: 'src/test**/*'
- any-glob-to-any-file: 'src/test/**/*'
- any-glob-to-any-file: 'src/testing/**/*'
- any-glob-to-any-file: '.pre-commit-config'
- any-glob-to-any-file: '.github/workflows/pre_commit.yml'
- any-glob-to-any-file: '.github/workflows/scorecards.yml'

View File

@ -35,6 +35,7 @@ jobs:
run: ./gradlew swaggerhubUpload
env:
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
SWAGGERHUB_USER: "Frooodle"
- name: Get version number
id: versionNumber
@ -42,6 +43,7 @@ jobs:
- name: Set API version as published and default on SwaggerHub
run: |
curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}"
curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/${SWAGGERHUB_USER}/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}"
env:
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
SWAGGERHUB_USER: "Frooodle"

6
.gitignore vendored
View File

@ -27,6 +27,8 @@ clientWebUI/
!cucumber/exampleFiles/example_html.zip
exampleYmlFiles/stirling/
/testing/file_snapshots
SwaggerDoc.json
# Gradle
.gradle
.lock
@ -188,3 +190,7 @@ id_ed25519.pub
.ipynb_checkpoints
**/jcef-bundle/
# node_modules
node_modules/
*.mjs

View File

@ -1,5 +1,5 @@
# Build the application
FROM gradle:8.12-jdk21 AS build
FROM gradle:8.13-jdk21 AS build
COPY build.gradle .
COPY settings.gradle .

View File

@ -120,17 +120,17 @@ Stirling-PDF currently supports 39 languages!
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![87%](https://geps.dev/progress/87) |
| Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) |
| Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) |
| Catalan (Català) (ca_CA) | ![95%](https://geps.dev/progress/95) |
| Catalan (Català) (ca_CA) | ![94%](https://geps.dev/progress/94) |
| Croatian (Hrvatski) (hr_HR) | ![85%](https://geps.dev/progress/85) |
| Czech (Česky) (cs_CZ) | ![96%](https://geps.dev/progress/96) |
| Danish (Dansk) (da_DK) | ![84%](https://geps.dev/progress/84) |
| Dutch (Nederlands) (nl_NL) | ![84%](https://geps.dev/progress/84) |
| Dutch (Nederlands) (nl_NL) | ![83%](https://geps.dev/progress/83) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![96%](https://geps.dev/progress/96) |
| Hindi (हिंदी) (hi_IN) | ![97%](https://geps.dev/progress/97) |
| Hindi (हिंदी) (hi_IN) | ![96%](https://geps.dev/progress/96) |
| Hungarian (Magyar) (hu_HU) | ![94%](https://geps.dev/progress/94) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![85%](https://geps.dev/progress/85) |
| Irish (Gaeilge) (ga_IE) | ![96%](https://geps.dev/progress/96) |
@ -138,7 +138,7 @@ Stirling-PDF currently supports 39 languages!
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![97%](https://geps.dev/progress/97) |
| Norwegian (Norsk) (no_NB) | ![77%](https://geps.dev/progress/77) |
| Persian (فارسی) (fa_IR) | ![93%](https://geps.dev/progress/93) |
| Persian (فارسی) (fa_IR) | ![92%](https://geps.dev/progress/92) |
| Polish (Polski) (pl_PL) | ![84%](https://geps.dev/progress/84) |
| Portuguese (Português) (pt_PT) | ![96%](https://geps.dev/progress/96) |
| Portuguese Brazilian (Português) (pt_BR) | ![97%](https://geps.dev/progress/97) |
@ -146,7 +146,7 @@ Stirling-PDF currently supports 39 languages!
| Russian (Русский) (ru_RU) | ![96%](https://geps.dev/progress/96) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![63%](https://geps.dev/progress/63) |
| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) |
| Slovakian (Slovensky) (sk_SK) | ![73%](https://geps.dev/progress/73) |
| Slovakian (Slovensky) (sk_SK) | ![72%](https://geps.dev/progress/72) |
| Slovenian (Slovenščina) (sl_SI) | ![95%](https://geps.dev/progress/95) |
| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![92%](https://geps.dev/progress/92) |
@ -154,7 +154,7 @@ Stirling-PDF currently supports 39 languages!
| Tibetan (བོད་ཡིག་) (zh_BO) | ![93%](https://geps.dev/progress/93) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![81%](https://geps.dev/progress/81) |
| Ukrainian (Українська) (uk_UA) | ![98%](https://geps.dev/progress/98) |
| Ukrainian (Українська) (uk_UA) | ![97%](https://geps.dev/progress/97) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![78%](https://geps.dev/progress/78) |

View File

@ -2,7 +2,7 @@ plugins {
id "java"
id "org.springframework.boot" version "3.4.3"
id "io.spring.dependency-management" version "1.1.7"
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6"
id "com.diffplug.spotless" version "7.0.2"
@ -25,7 +25,7 @@ ext {
}
group = "stirling.software"
version = "0.44.1"
version = "0.44.2"
java {
// 17 is lowest but we support and recommend 21
@ -98,6 +98,7 @@ openApi {
apiDocsUrl = "http://localhost:8080/v1/api-docs"
outputDir = file("$projectDir")
outputFileName = "SwaggerDoc.json"
waitTimeInSeconds = 60 // Increase the wait time to 60 seconds
}
//0.11.5 to 2024.11.5
@ -256,7 +257,7 @@ launch4j {
spotless {
java {
target project.fileTree('src/main/java')
target project.fileTree('src').include('**/*.java')
googleJavaFormat("1.25.2").aosp().reorderImports(false)
@ -284,17 +285,21 @@ sonar {
// }
tasks.wrapper {
gradleVersion = "8.12"
distributionType = Wrapper.DistributionType.ALL
}
//tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation"
//}
configurations.all {
// Remove all commons-logging dependencies so that only spring-jcl is used
exclude group: 'commons-logging', module: 'commons-logging'
// Exclude Tomcat
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
dependencies {
//tmp for security bumps
implementation 'ch.qos.logback:logback-core:1.5.17'
implementation 'ch.qos.logback:logback-core:1.5.17'
implementation 'ch.qos.logback:logback-classic:1.5.17'
@ -381,19 +386,13 @@ dependencies {
//general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv
implementation ("com.opencsv:opencsv:5.10") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation ("com.opencsv:opencsv:5.10")
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion")
implementation "org.apache.pdfbox:preflight:$pdfboxVersion"
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion")
// https://mvnrepository.com/artifact/technology.tabula/tabula
implementation ('technology.tabula:tabula:1.0.5') {
@ -407,7 +406,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.4"
implementation "io.micrometer:micrometer-core:1.14.5"
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0"
@ -443,9 +442,9 @@ task writeVersion {
swaggerhubUpload {
// dependsOn = generateOpenApiDocs // Depends on your task generating Swagger docs
api = "Stirling-PDF" // The name of your API on SwaggerHub
owner = "Frooodle" // Your SwaggerHub username (or organization name)
owner = "${System.getenv().getOrDefault('SWAGGERHUB_USER', 'Frooodle')}" // Your SwaggerHub username (or organization name)
version = project.version // The version of your API
inputFile = "./SwaggerDoc.json" // The path to your Swagger docs
inputFile = file("SwaggerDoc.json") // The path to your Swagger docs
token = "${System.getenv("SWAGGERHUB_API_KEY")}" // Your SwaggerHub API key, passed as an environment variable
oas = "3.0.0" // The version of the OpenAPI Specification you"re using
}
@ -473,3 +472,7 @@ task printMacVersion {
println getMacVersion(project.version.toString())
}
}
tasks.named('generateOpenApiDocs') {
doNotTrackState("Tracking state is not supported for this task")
}

View File

@ -18,17 +18,17 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
@RestController
@RequestMapping("/api/v1/analysis")
@Tag(name = "Analysis", description = "Analysis APIs")
public class AnalysisController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public AnalysisController(CustomPDDocumentFactory pdfDocumentFactory) {
public AnalysisController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -21,7 +21,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -29,10 +29,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class CropController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public CropController(CustomPDDocumentFactory pdfDocumentFactory) {
public CropController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -40,7 +40,8 @@ public class CropController {
@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")
"This operation takes an input PDF file and crops it according to the given"
+ " coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = pdfDocumentFactory.load(form);

View File

@ -33,7 +33,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -43,10 +43,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class MergeController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public MergeController(CustomPDDocumentFactory pdfDocumentFactory) {
public MergeController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -119,7 +119,9 @@ public class MergeController {
@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")
"This endpoint merges multiple PDF files into a single PDF file. The merged"
+ " file will contain all pages from the input files in the order they were"
+ " provided. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
throws IOException {
List<File> filesToDelete = new ArrayList<>(); // List of temporary files to delete

View File

@ -24,7 +24,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -32,10 +32,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class MultiPageLayoutController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public MultiPageLayoutController(CustomPDDocumentFactory pdfDocumentFactory) {
public MultiPageLayoutController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -43,7 +43,8 @@ public class MultiPageLayoutController {
@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")
"This operation takes an input PDF file and the number of pages to merge into a"
+ " single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
@ModelAttribute MergeMultiplePagesRequest request) throws IOException {

View File

@ -15,7 +15,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -31,7 +31,7 @@ public class PdfImageRemovalController {
// Service for removing images from PDFs
private final PdfImageRemovalService pdfImageRemovalService;
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
/**
* Constructor for dependency injection of PdfImageRemovalService.
@ -41,7 +41,7 @@ public class PdfImageRemovalController {
@Autowired
public PdfImageRemovalController(
PdfImageRemovalService pdfImageRemovalService,
CustomPDDocumentFactory pdfDocumentFactory) {
CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfImageRemovalService = pdfImageRemovalService;
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -61,7 +61,8 @@ public class PdfImageRemovalController {
@Operation(
summary = "Remove images from file to reduce the file size.",
description =
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
"This endpoint remove images from file to reduce the file size.Input:PDF"
+ " Output:PDF Type:MISO")
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
// Load the PDF document
PDDocument document = pdfDocumentFactory.load(file);

View File

@ -26,7 +26,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -35,10 +35,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class PdfOverlayController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public PdfOverlayController(CustomPDDocumentFactory pdfDocumentFactory) {
public PdfOverlayController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -46,7 +46,8 @@ public class PdfOverlayController {
@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")
"Overlay PDF files onto a base PDF with different modes: Sequential,"
+ " Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO")
public ResponseEntity<byte[]> overlayPdfs(@ModelAttribute OverlayPdfsRequest request)
throws IOException {
MultipartFile baseFile = request.getFileInput();

View File

@ -24,7 +24,7 @@ import lombok.extern.slf4j.Slf4j;
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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -34,10 +34,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class RearrangePagesPDFController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public RearrangePagesPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
public RearrangePagesPDFController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -45,7 +45,9 @@ public class RearrangePagesPDFController {
@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")
"This endpoint removes specified pages from a given PDF file. Users can provide"
+ " a comma-separated list of page numbers or ranges to delete. Input:PDF"
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> deletePages(@ModelAttribute PDFWithPageNums request)
throws IOException {
@ -242,7 +244,10 @@ public class RearrangePagesPDFController {
@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")
"This endpoint rearranges pages in a given PDF file based on the specified page"
+ " order or custom mode. Users can provide a page order as a"
+ " comma-separated list of page numbers or page ranges, or a custom mode."
+ " Input:PDF Output:PDF")
public ResponseEntity<byte[]> rearrangePages(@ModelAttribute RearrangePagesRequest request)
throws IOException {
MultipartFile pdfFile = request.getFileInput();

View File

@ -18,7 +18,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -26,10 +26,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class RotationController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public RotationController(CustomPDDocumentFactory pdfDocumentFactory) {
public RotationController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -37,11 +37,18 @@ public class RotationController {
@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")
"This endpoint rotates a given PDF file by a specified angle. The angle must be"
+ " a multiple of 90. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> rotatePDF(@ModelAttribute RotatePDFRequest request)
throws IOException {
MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle();
// Validate the angle is a multiple of 90
if (angle % 90 != 0) {
throw new IllegalArgumentException("Angle must be a multiple of 90");
}
// Load the PDF document
PDDocument document = pdfDocumentFactory.load(request);

View File

@ -25,7 +25,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -33,10 +33,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class ScalePagesController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ScalePagesController(CustomPDDocumentFactory pdfDocumentFactory) {
public ScalePagesController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -44,7 +44,8 @@ public class ScalePagesController {
@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")
"This operation takes an input PDF file and the size to scale the pages to in"
+ " the output PDF file. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> scalePages(@ModelAttribute ScalePagesRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
@ -123,7 +124,8 @@ public class ScalePagesController {
}
throw new IllegalArgumentException(
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, LETTER, LEGAL, KEEP");
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6,"
+ " LETTER, LEGAL, KEEP");
}
private Map<String, PDRectangle> getSizeMap() {

View File

@ -28,7 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -37,10 +37,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class SplitPDFController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
public SplitPDFController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -48,7 +48,10 @@ public class SplitPDFController {
@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")
"This endpoint splits a given PDF file into separate documents based on the"
+ " specified page numbers or ranges. Users can specify pages using"
+ " individual numbers, ranges, or 'all' for every page. Input:PDF"
+ " Output:PDF Type:SIMO")
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
throws IOException {

View File

@ -33,7 +33,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.service.PdfMetadataService;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -45,11 +45,11 @@ public class SplitPdfByChaptersController {
private final PdfMetadataService pdfMetadataService;
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPdfByChaptersController(
PdfMetadataService pdfMetadataService, CustomPDDocumentFactory pdfDocumentFactory) {
PdfMetadataService pdfMetadataService, CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfMetadataService = pdfMetadataService;
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -31,7 +31,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -39,10 +39,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySectionsController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPdfBySectionsController(CustomPDDocumentFactory pdfDocumentFactory) {
public SplitPdfBySectionsController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -50,7 +50,9 @@ public class SplitPdfBySectionsController {
@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")
"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<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request)
throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();

View File

@ -25,7 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -35,22 +35,25 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySizeController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public SplitPdfBySizeController(CustomPDDocumentFactory pdfDocumentFactory) {
public SplitPdfBySizeController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
log.info(
"SplitPdfBySizeController initialized with pdfDocumentFactory: {}",
pdfDocumentFactory);
pdfDocumentFactory.getClass().getSimpleName());
}
@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")
"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<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request)
throws Exception {

View File

@ -21,7 +21,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -29,10 +29,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class ToSinglePageController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ToSinglePageController(CustomPDDocumentFactory pdfDocumentFactory) {
public ToSinglePageController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -40,7 +40,10 @@ public class ToSinglePageController {
@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")
"This endpoint converts a multi-page PDF document into a single paged PDF"
+ " document. The width of the single page will be same as the input's"
+ " width, but the height will be the sum of all the pages' heights."
+ " Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> pdfToSinglePage(@ModelAttribute PDFFile request)
throws IOException {

View File

@ -15,7 +15,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -24,7 +24,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final ApplicationProperties applicationProperties;
@ -32,7 +32,7 @@ public class ConvertHtmlToPDF {
@Autowired
public ConvertHtmlToPDF(
CustomPDDocumentFactory pdfDocumentFactory,
CustomPDFDocumentFactory pdfDocumentFactory,
ApplicationProperties applicationProperties,
RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
@ -45,7 +45,8 @@ public class ConvertHtmlToPDF {
@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. Input:HTML Output:PDF Type:SISO")
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format."
+ " Input:HTML Output:PDF Type:SISO")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
throws Exception {
MultipartFile fileInput = request.getFileInput();

View File

@ -33,7 +33,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.*;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@ -43,10 +43,10 @@ import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertImgPDFController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertImgPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
public ConvertImgPDFController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -54,7 +54,9 @@ public class ConvertImgPDFController {
@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")
"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<byte[]> convertToImage(@ModelAttribute ConvertToImageRequest request)
throws NumberFormatException, Exception {
MultipartFile file = request.getFileInput();
@ -208,7 +210,9 @@ public class ConvertImgPDFController {
@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:MISO")
"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:MISO")
public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request)
throws IOException {
MultipartFile[] file = request.getFileInput();

View File

@ -25,7 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -34,14 +34,14 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final ApplicationProperties applicationProperties;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertMarkdownToPdf(
CustomPDDocumentFactory pdfDocumentFactory,
CustomPDFDocumentFactory pdfDocumentFactory,
ApplicationProperties applicationProperties,
RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
@ -54,7 +54,8 @@ public class ConvertMarkdownToPdf {
@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. Input:MARKDOWN Output:PDF Type:SISO")
"This endpoint takes a Markdown file input, converts it to HTML, and then to"
+ " PDF format. Input:MARKDOWN Output:PDF Type:SISO")
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request)
throws Exception {
MultipartFile fileInput = request.getFileInput();

View File

@ -24,7 +24,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -34,12 +34,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertOfficeController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertOfficeController(
CustomPDDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
CustomPDFDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.runtimePathConfig = runtimePathConfig;
}
@ -93,7 +93,8 @@ public class ConvertOfficeController {
@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")
"This endpoint converts a given file to a PDF using LibreOffice API Input:ANY"
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile request)
throws Exception {
MultipartFile inputFile = request.getFileInput();

View File

@ -21,7 +21,7 @@ import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.PDFToFile;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -30,10 +30,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertPDFToOffice {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertPDFToOffice(CustomPDDocumentFactory pdfDocumentFactory) {
public ConvertPDFToOffice(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -41,7 +41,8 @@ public class ConvertPDFToOffice {
@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")
"This endpoint converts a given PDF file to a Presentation format. Input:PDF"
+ " Output:PPT Type:SISO")
public ResponseEntity<byte[]> processPdfToPresentation(
@ModelAttribute PdfToPresentationRequest request)
throws IOException, InterruptedException {
@ -55,7 +56,8 @@ public class ConvertPDFToOffice {
@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")
"This endpoint converts a given PDF file to Text or RTF format. Input:PDF"
+ " Output:TXT Type:SISO")
public ResponseEntity<byte[]> processPdfToRTForTXT(
@ModelAttribute PdfToTextOrRTFRequest request)
throws IOException, InterruptedException {
@ -82,7 +84,8 @@ public class ConvertPDFToOffice {
@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")
"This endpoint converts a given PDF file to a Word document format. Input:PDF"
+ " Output:WORD Type:SISO")
public ResponseEntity<byte[]> processPdfToWord(@ModelAttribute PdfToWordRequest request)
throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput();
@ -95,7 +98,8 @@ public class ConvertPDFToOffice {
@Operation(
summary = "Convert PDF to XML",
description =
"This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
"This endpoint converts a PDF file to an XML file. Input:PDF Output:XML"
+ " Type:SISO")
public ResponseEntity<byte[]> processPdfToXML(@ModelAttribute PDFFile request)
throws Exception {
MultipartFile inputFile = request.getFileInput();

View File

@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@ -33,12 +33,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertWebsiteToPDF(
CustomPDDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
CustomPDFDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.runtimePathConfig = runtimePathConfig;
}
@ -47,7 +47,8 @@ public class ConvertWebsiteToPDF {
@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")
"This endpoint fetches content from a URL and converts it to a PDF format."
+ " Input:N/A Output:PDF Type:SISO")
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request)
throws IOException, InterruptedException {
String URL = request.getUrlInput();

View File

@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import technology.tabula.ObjectExtractor;
import technology.tabula.Page;
@ -43,10 +43,10 @@ import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
@Slf4j
public class ExtractCSVController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ExtractCSVController(CustomPDDocumentFactory pdfDocumentFactory) {
public ExtractCSVController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -54,7 +54,8 @@ public class ExtractCSVController {
@Operation(
summary = "Extracts a CSV document from a PDF",
description =
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
"This operation takes an input PDF file and returns CSV file of whole page."
+ " Input:PDF Output:CSV Type:SISO")
public ResponseEntity<?> pdfToCsv(@ModelAttribute PDFWithPageNums form) throws Exception {
String baseName = getBaseName(form.getFileInput().getOriginalFilename());
List<CsvEntry> csvEntries = new ArrayList<>();

View File

@ -23,7 +23,7 @@ import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
import stirling.software.SPDF.model.api.filter.FileSizeRequest;
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -32,10 +32,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Filter", description = "Filter APIs")
public class FilterController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public FilterController(CustomPDDocumentFactory pdfDocumentFactory) {
public FilterController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -35,10 +35,10 @@ public class AutoRenameController {
private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
private static final int LINE_LIMIT = 200;
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public AutoRenameController(CustomPDDocumentFactory pdfDocumentFactory) {
public AutoRenameController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -46,7 +46,8 @@ public class AutoRenameController {
@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")
"This endpoint accepts a PDF file and attempts to extract its title or header"
+ " based on heuristics. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> extractHeader(@ModelAttribute ExtractHeaderRequest request)
throws Exception {
MultipartFile file = request.getFileInput();

View File

@ -35,7 +35,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -51,10 +51,10 @@ public class AutoSplitPdfController {
"https://github.com/Frooodle/Stirling-PDF",
"https://stirlingpdf.com"));
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public AutoSplitPdfController(CustomPDDocumentFactory pdfDocumentFactory) {
public AutoSplitPdfController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -30,7 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -40,10 +40,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class BlankPageController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public BlankPageController(CustomPDDocumentFactory pdfDocumentFactory) {
public BlankPageController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -77,7 +77,9 @@ public class BlankPageController {
@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")
"This endpoint removes blank pages from a given PDF file. Users can specify the"
+ " threshold and white percentage to tune the detection of blank pages."
+ " Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request)
throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput();

View File

@ -3,13 +3,19 @@ package stirling.software.SPDF.controller.api.misc;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.Map.Entry;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
@ -36,11 +42,15 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ImageProcessingUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -51,307 +61,373 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class CompressController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public CompressController(CustomPDDocumentFactory pdfDocumentFactory) {
public CompressController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
private void compressImagesInPDF(Path pdfFile, double scaleFactor, float jpegQuality)
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class ImageReference {
int pageNum; // Page number where the image appears
COSName name; // The name used to reference this image
}
public Path compressImagesInPDF(
Path pdfFile, double scaleFactor, float jpegQuality, boolean convertToGrayscale)
throws Exception {
byte[] fileBytes = Files.readAllBytes(pdfFile);
long originalFileSize = fileBytes.length;
Path newCompressedPDF = Files.createTempFile("compressedPDF", ".pdf");
long originalFileSize = Files.size(pdfFile);
log.info(
"Starting image compression with scale factor: {} and JPEG quality: {} on file size: {}",
"Starting image compression with scale factor: {}, JPEG quality: {}, grayscale: {} on file size: {}",
scaleFactor,
jpegQuality,
convertToGrayscale,
GeneralUtils.formatBytes(originalFileSize));
// Track processed images to avoid recompression
Set<String> processedImages = new HashSet<>();
try (PDDocument doc = pdfDocumentFactory.load(pdfFile)) {
// Collect all unique images by content hash
Map<String, List<ImageReference>> uniqueImages = new HashMap<>();
Map<String, PDImageXObject> compressedVersions = new HashMap<>();
try (PDDocument doc = pdfDocumentFactory.load(fileBytes)) {
int totalImages = 0;
for (int pageNum = 0; pageNum < doc.getNumberOfPages(); pageNum++) {
PDPage page = doc.getPage(pageNum);
PDResources res = page.getResources();
if (res == null || res.getXObjectNames() == null) continue;
for (COSName name : res.getXObjectNames()) {
PDXObject xobj = res.getXObject(name);
if (!(xobj instanceof PDImageXObject)) continue;
totalImages++;
PDImageXObject image = (PDImageXObject) xobj;
String imageHash = generateImageHash(image);
// Store only page number and name reference
ImageReference ref = new ImageReference();
ref.pageNum = pageNum;
ref.name = name;
uniqueImages.computeIfAbsent(imageHash, k -> new ArrayList<>()).add(ref);
}
}
int uniqueImagesCount = uniqueImages.size();
int duplicatedImages = totalImages - uniqueImagesCount;
log.info(
"Found {} unique images and {} duplicated instances across {} pages",
uniqueImagesCount,
duplicatedImages,
doc.getNumberOfPages());
// SECOND PASS: Process each unique image exactly once
int compressedImages = 0;
int skippedImages = 0;
long totalOriginalBytes = 0;
long totalCompressedBytes = 0;
// Minimum dimensions to preserve reasonable quality
int MIN_WIDTH = 400; // Higher minimum
int MIN_HEIGHT = 400; // Higher minimum
for (Entry<String, List<ImageReference>> entry : uniqueImages.entrySet()) {
String imageHash = entry.getKey();
List<ImageReference> references = entry.getValue();
log.info("PDF has {} pages", doc.getNumberOfPages());
if (references.isEmpty()) continue;
for (int pageNum = 0; pageNum < doc.getNumberOfPages(); pageNum++) {
PDPage page = doc.getPage(pageNum);
PDResources res = page.getResources();
// Get the first instance of this image
ImageReference firstRef = references.get(0);
PDPage firstPage = doc.getPage(firstRef.pageNum);
PDResources firstPageResources = firstPage.getResources();
PDImageXObject originalImage =
(PDImageXObject) firstPageResources.getXObject(firstRef.name);
if (res == null || res.getXObjectNames() == null) {
continue;
}
// Track original size
int originalSize = (int) originalImage.getCOSObject().getLength();
totalOriginalBytes += originalSize;
int pageImages = 0;
// Process this unique image once
BufferedImage processedImage =
processAndCompressImage(
originalImage, scaleFactor, jpegQuality, convertToGrayscale);
for (COSName name : res.getXObjectNames()) {
String imageName = name.getName();
if (processedImage != null) {
// Convert to bytes for storage
byte[] compressedData = convertToBytes(processedImage, jpegQuality);
// Skip already processed images
if (processedImages.contains(imageName)) {
skippedImages++;
continue;
}
// Check if compression is beneficial
if (compressedData.length < originalSize || convertToGrayscale) {
// Create a single compressed version
PDImageXObject compressedImage =
PDImageXObject.createFromByteArray(
doc,
compressedData,
originalImage.getCOSObject().toString());
PDXObject xobj = res.getXObject(name);
if (!(xobj instanceof PDImageXObject)) {
continue;
}
// Store the compressed version only once in our map
compressedVersions.put(imageHash, compressedImage);
totalImages++;
pageImages++;
PDImageXObject image = (PDImageXObject) xobj;
BufferedImage bufferedImage = image.getImage();
int originalWidth = bufferedImage.getWidth();
int originalHeight = bufferedImage.getHeight();
log.info(
"Page {}, Image {}: Original dimensions: {}x{}",
pageNum + 1,
imageName,
originalWidth,
originalHeight);
// Skip if already small enough
if (originalWidth <= MIN_WIDTH || originalHeight <= MIN_HEIGHT) {
// Report compression stats
double reductionPercentage =
100.0 - ((compressedData.length * 100.0) / originalSize);
log.info(
"Page {}, Image {}: Skipping - below minimum dimensions threshold",
pageNum + 1,
imageName);
skippedImages++;
processedImages.add(imageName);
continue;
}
"Image hash {}: Compressed from {} to {} (reduced by {}%)",
imageHash,
GeneralUtils.formatBytes(originalSize),
GeneralUtils.formatBytes(compressedData.length),
String.format("%.1f", reductionPercentage));
// Adjust scale factor for very large or very small images
double adjustedScaleFactor = scaleFactor;
if (originalWidth > 3000 || originalHeight > 3000) {
// More aggressive for very large images
adjustedScaleFactor = Math.min(scaleFactor, 0.75);
log.info(
"Page {}, Image {}: Very large image, using more aggressive scale: {}",
pageNum + 1,
imageName,
adjustedScaleFactor);
} else if (originalWidth < 1000 || originalHeight < 1000) {
// More conservative for smaller images
adjustedScaleFactor = Math.max(scaleFactor, 0.9);
log.info(
"Page {}, Image {}: Smaller image, using conservative scale: {}",
pageNum + 1,
imageName,
adjustedScaleFactor);
}
// Replace ALL instances with the compressed version
for (ImageReference ref : references) {
// Get the page and resources when needed
PDPage page = doc.getPage(ref.pageNum);
PDResources resources = page.getResources();
resources.put(ref.name, compressedImage);
int newWidth = (int) (originalWidth * adjustedScaleFactor);
int newHeight = (int) (originalHeight * adjustedScaleFactor);
// Ensure minimum dimensions
newWidth = Math.max(newWidth, MIN_WIDTH);
newHeight = Math.max(newHeight, MIN_HEIGHT);
// Skip if change is negligible
if ((double) newWidth / originalWidth > 0.95
&& (double) newHeight / originalHeight > 0.95) {
log.info(
"Page {}, Image {}: Change too small, skipping compression",
pageNum + 1,
imageName);
skippedImages++;
processedImages.add(imageName);
continue;
}
log.info(
"Page {}, Image {}: Resizing to {}x{} ({}% of original)",
pageNum + 1,
imageName,
newWidth,
newHeight,
Math.round((newWidth * 100.0) / originalWidth));
// Use high quality scaling
BufferedImage scaledImage =
new BufferedImage(
newWidth,
newHeight,
bufferedImage.getColorModel().hasAlpha()
? BufferedImage.TYPE_INT_ARGB
: BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = scaledImage.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(
RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
g2d.dispose();
// Choose appropriate format and compression
String format = bufferedImage.getColorModel().hasAlpha() ? "png" : "jpeg";
// First get the actual size of the original image by encoding it to the chosen
// format
ByteArrayOutputStream originalImageStream = new ByteArrayOutputStream();
if (format.equals("jpeg")) {
// Get the best available JPEG writer (prioritizes TwelveMonkeys if
// available)
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = null;
// Prefer TwelveMonkeys writer if available
while (writers.hasNext()) {
ImageWriter candidate = writers.next();
if (candidate.getClass().getName().contains("twelvemonkeys")) {
writer = candidate;
break;
}
}
if (writer == null) {
writer = ImageIO.getImageWritersByFormatName("jpeg").next();
log.info(
"Replaced image on page {} with compressed version",
ref.pageNum + 1);
}
JPEGImageWriteParam param =
(JPEGImageWriteParam) writer.getDefaultWriteParam();
// Set advanced compression parameters
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(jpegQuality);
param.setOptimizeHuffmanTables(true); // Better compression
param.setProgressiveMode(
ImageWriteParam.MODE_DEFAULT); // Progressive scanning
// Write compressed image
try (ImageOutputStream ios =
ImageIO.createImageOutputStream(originalImageStream)) {
writer.setOutput(ios);
writer.write(null, new IIOImage(scaledImage, null, null), param);
}
writer.dispose();
totalCompressedBytes += compressedData.length * references.size();
compressedImages++;
} else {
ImageIO.write(bufferedImage, format, originalImageStream);
}
int originalEncodedSize = (int) image.getCOSObject().getLength();
originalImageStream.close();
// Now compress the scaled image
ByteArrayOutputStream compressedImageStream = new ByteArrayOutputStream();
if (format.equals("jpeg")) {
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
if (writers.hasNext()) {
ImageWriter writer = writers.next();
ImageWriteParam param = writer.getDefaultWriteParam();
if (param.canWriteCompressed()) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(jpegQuality);
ImageOutputStream imageOut =
ImageIO.createImageOutputStream(compressedImageStream);
writer.setOutput(imageOut);
writer.write(null, new IIOImage(scaledImage, null, null), param);
writer.dispose();
imageOut.close();
} else {
ImageIO.write(scaledImage, format, compressedImageStream);
}
} else {
ImageIO.write(scaledImage, format, compressedImageStream);
}
} else {
ImageIO.write(scaledImage, format, compressedImageStream);
}
byte[] imageBytes = compressedImageStream.toByteArray();
compressedImageStream.close();
// Format sizes using our utility method
String originalSizeStr = GeneralUtils.formatBytes(originalEncodedSize);
String compressedSizeStr = GeneralUtils.formatBytes(imageBytes.length);
// Calculate reduction percentage (how much smaller the new file is)
double reductionPercentage =
100.0 - ((imageBytes.length * 100.0) / originalEncodedSize);
if (imageBytes.length >= originalEncodedSize) {
log.info(
"Page {}, Image {}: Compressed size {} not smaller than original {}, skipping replacement",
pageNum + 1,
imageName,
GeneralUtils.formatBytes(imageBytes.length),
GeneralUtils.formatBytes(originalEncodedSize));
// Accumulate original size for both counters (no change)
totalOriginalBytes += originalEncodedSize;
totalCompressedBytes += originalEncodedSize;
log.info("Image hash {}: Compression not beneficial, skipping", imageHash);
totalCompressedBytes += originalSize * references.size();
skippedImages++;
processedImages.add(imageName);
continue;
}
log.info(
"Page {}, Image {}: Compressed from {} to {} (reduced by {}%)",
pageNum + 1,
imageName,
originalSizeStr,
compressedSizeStr,
String.format("%.1f", reductionPercentage));
// Only replace if compressed size is smaller
PDImageXObject compressedImage =
PDImageXObject.createFromByteArray(
doc, imageBytes, image.getCOSObject().toString());
res.put(name, compressedImage);
// Update counters with compressed size
totalOriginalBytes += originalEncodedSize;
totalCompressedBytes += imageBytes.length;
compressedImages++;
processedImages.add(imageName);
} else {
log.info("Image hash {}: Not suitable for compression, skipping", imageHash);
totalCompressedBytes += originalSize * references.size();
skippedImages++;
}
}
// Log overall image compression statistics
// Log compression statistics
double overallImageReduction =
totalOriginalBytes > 0
? 100.0 - ((totalCompressedBytes * 100.0) / totalOriginalBytes)
: 0;
log.info(
"Image compression summary - Total: {}, Compressed: {}, Skipped: {}",
totalImages,
"Image compression summary - Total unique: {}, Compressed: {}, Skipped: {}, Duplicates: {}",
uniqueImagesCount,
compressedImages,
skippedImages);
skippedImages,
duplicatedImages);
log.info(
"Total original image size: {}, compressed: {} (reduced by {:.1f}%)",
"Total original image size: {}, compressed: {} (reduced by {}%)",
GeneralUtils.formatBytes(totalOriginalBytes),
GeneralUtils.formatBytes(totalCompressedBytes),
overallImageReduction);
String.format("%.1f", overallImageReduction));
// Free memory before saving
compressedVersions.clear();
uniqueImages.clear();
// Save the document
log.info("Saving compressed PDF to {}", pdfFile.toString());
doc.save(pdfFile.toString());
log.info("Saving compressed PDF to {}", newCompressedPDF.toString());
doc.save(newCompressedPDF.toString());
// Log overall file size reduction
long compressedFileSize = Files.size(pdfFile);
long compressedFileSize = Files.size(newCompressedPDF);
double overallReduction = 100.0 - ((compressedFileSize * 100.0) / originalFileSize);
log.info(
"Overall PDF compression: {} → {} (reduced by {:.1f}%)",
"Overall PDF compression: {} → {} (reduced by {}%)",
GeneralUtils.formatBytes(originalFileSize),
GeneralUtils.formatBytes(compressedFileSize),
overallReduction);
String.format("%.1f", overallReduction));
return newCompressedPDF;
}
}
private BufferedImage convertToGrayscale(BufferedImage image) {
BufferedImage grayImage =
new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = grayImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return grayImage;
}
/**
* Processes and compresses an image if beneficial. Returns the processed image if compression
* is worthwhile, null otherwise.
*/
private BufferedImage processAndCompressImage(
PDImageXObject image, double scaleFactor, float jpegQuality, boolean convertToGrayscale)
throws IOException {
BufferedImage bufferedImage = image.getImage();
int originalWidth = bufferedImage.getWidth();
int originalHeight = bufferedImage.getHeight();
// Minimum dimensions to preserve reasonable quality
int MIN_WIDTH = 400;
int MIN_HEIGHT = 400;
log.info("Original dimensions: {}x{}", originalWidth, originalHeight);
// Skip if already small enough
if ((originalWidth <= MIN_WIDTH || originalHeight <= MIN_HEIGHT) && !convertToGrayscale) {
log.info("Skipping - below minimum dimensions threshold");
return null;
}
// Convert to grayscale first if requested (before resizing for better quality)
if (convertToGrayscale) {
bufferedImage = convertToGrayscale(bufferedImage);
log.info("Converted image to grayscale");
}
// Adjust scale factor for very large or very small images
double adjustedScaleFactor = scaleFactor;
if (originalWidth > 3000 || originalHeight > 3000) {
// More aggressive for very large images
adjustedScaleFactor = Math.min(scaleFactor, 0.75);
log.info("Very large image, using more aggressive scale: {}", adjustedScaleFactor);
} else if (originalWidth < 1000 || originalHeight < 1000) {
// More conservative for smaller images
adjustedScaleFactor = Math.max(scaleFactor, 0.9);
log.info("Smaller image, using conservative scale: {}", adjustedScaleFactor);
}
int newWidth = (int) (originalWidth * adjustedScaleFactor);
int newHeight = (int) (originalHeight * adjustedScaleFactor);
// Ensure minimum dimensions
newWidth = Math.max(newWidth, MIN_WIDTH);
newHeight = Math.max(newHeight, MIN_HEIGHT);
// Skip if change is negligible
if ((double) newWidth / originalWidth > 0.95
&& (double) newHeight / originalHeight > 0.95
&& !convertToGrayscale) {
log.info("Change too small, skipping compression");
return null;
}
log.info(
"Resizing to {}x{} ({}% of original)",
newWidth, newHeight, Math.round((newWidth * 100.0) / originalWidth));
BufferedImage scaledImage;
if (convertToGrayscale) {
// If already grayscale, maintain the grayscale format
scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY);
} else {
// Otherwise use original color model
scaledImage =
new BufferedImage(
newWidth,
newHeight,
bufferedImage.getColorModel().hasAlpha()
? BufferedImage.TYPE_INT_ARGB
: BufferedImage.TYPE_INT_RGB);
}
Graphics2D g2d = scaledImage.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
g2d.dispose();
return scaledImage;
}
/**
* Converts a BufferedImage to a byte array with specified JPEG quality. Checks if compression
* is beneficial compared to original.
*/
private byte[] convertToBytes(BufferedImage scaledImage, float jpegQuality) throws IOException {
String format = scaledImage.getColorModel().hasAlpha() ? "png" : "jpeg";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
if ("jpeg".equals(format)) {
// Get the best available JPEG writer
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = writers.next();
JPEGImageWriteParam param = (JPEGImageWriteParam) writer.getDefaultWriteParam();
// Set compression parameters
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(jpegQuality);
param.setOptimizeHuffmanTables(true); // Better compression
param.setProgressiveMode(ImageWriteParam.MODE_DEFAULT); // Progressive scanning
// Write compressed image
try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream)) {
writer.setOutput(ios);
writer.write(null, new IIOImage(scaledImage, null, null), param);
}
writer.dispose();
} else {
ImageIO.write(scaledImage, format, outputStream);
}
return outputStream.toByteArray();
}
/** Modified hash function to consistently identify identical image content */
private String generateImageHash(PDImageXObject image) {
try {
// Create a stream for the raw stream data
try (InputStream stream = image.getCOSObject().createRawInputStream()) {
// Read up to first 8KB of data for the hash
byte[] buffer = new byte[8192];
int bytesRead = stream.read(buffer);
if (bytesRead > 0) {
byte[] dataToHash =
bytesRead == buffer.length ? buffer : Arrays.copyOf(buffer, bytesRead);
return bytesToHexString(generatMD5(dataToHash));
}
return "empty-stream";
}
} catch (Exception e) {
log.error("Error generating image hash", e);
return "fallback-" + System.identityHashCode(image);
}
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
private byte[] generatMD5(byte[] data) throws IOException {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(data); // Get the MD5 hash of the image bytes
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not available", e);
}
}
private byte[] generateImageMD5(PDImageXObject image) throws IOException {
return generatMD5(ImageProcessingUtils.getImageData(image.getImage()));
}
/** Generates a hash string from a byte array */
private String generateHashFromBytes(byte[] data) {
try {
// Use the existing method to generate MD5 hash
byte[] hash = generatMD5(data);
return bytesToHexString(hash);
} catch (Exception e) {
log.error("Error generating hash from bytes", e);
// Return a unique string as fallback
return "fallback-" + System.identityHashCode(data);
}
}
@ -382,13 +458,14 @@ public class CompressController {
@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")
"This endpoint accepts a PDF file and optimizes it based on the provided"
+ " parameters. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> optimizePdf(@ModelAttribute OptimizePdfRequest request)
throws Exception {
MultipartFile inputFile = request.getFileInput();
Integer optimizeLevel = request.getOptimizeLevel();
String expectedOutputSizeString = request.getExpectedOutputSize();
Boolean convertToGrayscale = request.getGrayscale();
if (expectedOutputSizeString == null && optimizeLevel == null) {
throw new Exception("Both expected output size and optimize level are not specified");
}
@ -400,48 +477,61 @@ public class CompressController {
autoMode = true;
}
Path tempInputFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(tempInputFile.toFile());
long inputFileSize = Files.size(tempInputFile);
Path tempOutputFile = null;
byte[] pdfBytes;
// Create initial input file
Path originalFile = Files.createTempFile("input_", ".pdf");
inputFile.transferTo(originalFile.toFile());
long inputFileSize = Files.size(originalFile);
// Start with original as current working file
Path currentFile = originalFile;
// Keep track of all temporary files for cleanup
List<Path> tempFiles = new ArrayList<>();
tempFiles.add(originalFile);
try {
tempOutputFile = Files.createTempFile("output_", ".pdf");
if (autoMode) {
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
optimizeLevel = determineOptimizeLevel(sizeReductionRatio);
}
boolean sizeMet = false;
boolean imageCompressionApplied = false; // Track if we've already compressed images
boolean imageCompressionApplied = false;
boolean qpdfCompressionApplied = false;
while (!sizeMet && optimizeLevel <= 9) {
// Apply appropriate compression based on level
// Levels 4-9: Apply image compression
if (optimizeLevel >= 4 && !imageCompressionApplied) {
// Apply image compression for levels 4-9
if ((optimizeLevel >= 4 || Boolean.TRUE.equals(convertToGrayscale))
&& !imageCompressionApplied) {
double scaleFactor = getScaleFactorForLevel(optimizeLevel);
float jpegQuality = getJpegQualityForLevel(optimizeLevel);
compressImagesInPDF(tempInputFile, scaleFactor, jpegQuality);
imageCompressionApplied = true; // Mark that we've compressed images
// Use the returned path from compressImagesInPDF
Path compressedImageFile = compressImagesInPDF(
currentFile,
scaleFactor,
jpegQuality,
Boolean.TRUE.equals(convertToGrayscale));
// Add to temp files list and update current file
tempFiles.add(compressedImageFile);
currentFile = compressedImageFile;
imageCompressionApplied = true;
}
// All levels (1-9): Apply QPDF compression
// Apply QPDF compression for all levels
if (!qpdfCompressionApplied) {
long preQpdfSize = Files.size(tempInputFile);
long preQpdfSize = Files.size(currentFile);
log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize));
// For levels 1-3, map to qpdf compression levels 1-9
int qpdfCompressionLevel = optimizeLevel;
if (optimizeLevel <= 3) {
qpdfCompressionLevel = optimizeLevel * 3; // Level 1->3, 2->6, 3->9
} else {
qpdfCompressionLevel = 9; // Max QPDF compression for levels 4-9
}
// Map optimization levels to QPDF compression levels
int qpdfCompressionLevel = optimizeLevel <= 3
? optimizeLevel * 3 // Level 1->3, 2->6, 3->9
: 9; // Max compression for levels 4-9
// Create output file for QPDF
Path qpdfOutputFile = Files.createTempFile("qpdf_output_", ".pdf");
tempFiles.add(qpdfOutputFile);
// Run QPDF optimization
List<String> command = new ArrayList<>();
@ -456,48 +546,50 @@ public class CompressController {
command.add("--compression-level=" + qpdfCompressionLevel);
command.add("--compress-streams=y");
command.add("--object-streams=generate");
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
command.add(currentFile.toString());
command.add(qpdfOutputFile.toString());
ProcessExecutorResult returnCode = null;
try {
returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
.runCommandWithOutputHandling(command);
returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
.runCommandWithOutputHandling(command);
qpdfCompressionApplied = true;
// Update current file to the QPDF output
currentFile = qpdfOutputFile;
long postQpdfSize = Files.size(currentFile);
double qpdfReduction = 100.0 - ((postQpdfSize * 100.0) / preQpdfSize);
log.info(
"Post-QPDF file size: {} (reduced by {}%)",
GeneralUtils.formatBytes(postQpdfSize),
String.format("%.1f", qpdfReduction));
} catch (Exception e) {
if (returnCode != null && returnCode.getRc() != 3) {
throw e;
}
// If QPDF fails, keep using the current file
log.warn("QPDF compression failed, continuing with current file");
}
long postQpdfSize = Files.size(tempOutputFile);
double qpdfReduction = 100.0 - ((postQpdfSize * 100.0) / preQpdfSize);
log.info(
"Post-QPDF file size: {} (reduced by {:.1f}%)",
GeneralUtils.formatBytes(postQpdfSize), qpdfReduction);
} else {
tempOutputFile = tempInputFile;
}
// Check if file size is within expected size or not auto mode
long outputFileSize = Files.size(tempOutputFile);
long outputFileSize = Files.size(currentFile);
if (outputFileSize <= expectedOutputSize || !autoMode) {
sizeMet = true;
} else {
int newOptimizeLevel =
incrementOptimizeLevel(
optimizeLevel, outputFileSize, expectedOutputSize);
int newOptimizeLevel = incrementOptimizeLevel(
optimizeLevel, outputFileSize, expectedOutputSize);
// Check if we can't increase the level further
if (newOptimizeLevel == optimizeLevel) {
if (autoMode) {
log.info(
"Maximum optimization level reached without meeting target size.");
log.info("Maximum optimization level reached without meeting target size.");
sizeMet = true;
}
} else {
// Reset image compression if moving to a new level
// Reset flags for next iteration with higher optimization level
imageCompressionApplied = false;
qpdfCompressionApplied = false;
optimizeLevel = newOptimizeLevel;
@ -505,26 +597,30 @@ public class CompressController {
}
}
// Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile);
Path finalFile = tempOutputFile;
// Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) {
log.warn(
"Optimized file is larger than the original. Returning the original file instead.");
finalFile = tempInputFile;
long finalFileSize = Files.size(currentFile);
if (finalFileSize > inputFileSize) {
log.warn("Optimized file is larger than the original. Using the original file instead.");
// Use the stored reference to the original file
currentFile = originalFile;
}
String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
String outputFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_Optimized.pdf";
return WebResponseUtils.pdfDocToWebResponse(
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
pdfDocumentFactory.load(currentFile.toFile()), outputFilename);
} finally {
Files.deleteIfExists(tempOutputFile);
// Clean up all temporary files
for (Path tempFile : tempFiles) {
try {
Files.deleteIfExists(tempFile);
} catch (IOException e) {
log.warn("Failed to delete temporary file: " + tempFile, e);
}
}
}
}

View File

@ -25,7 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -34,10 +34,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class DecompressPdfController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public DecompressPdfController(CustomPDDocumentFactory pdfDocumentFactory) {
public DecompressPdfController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -32,7 +32,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@ -46,10 +46,10 @@ public class ExtractImageScansController {
private static final String REPLACEFIRST = "[.][^.]+$";
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ExtractImageScansController(CustomPDDocumentFactory pdfDocumentFactory) {
public ExtractImageScansController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -57,7 +57,10 @@ public class ExtractImageScansController {
@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")
"This endpoint extracts image scans from a given file based on certain"
+ " parameters. Users can specify angle threshold, tolerance, minimum area,"
+ " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP"
+ " Type:SIMO")
public ResponseEntity<byte[]> extractImageScans(
@RequestBody(
description = "Form data containing file and extraction parameters",

View File

@ -40,7 +40,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.ImageProcessingUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -50,10 +50,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class ExtractImagesController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ExtractImagesController(CustomPDDocumentFactory pdfDocumentFactory) {
public ExtractImagesController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -61,7 +61,9 @@ public class ExtractImagesController {
@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")
"This endpoint extracts images from a given PDF file and returns them in a zip"
+ " file. Users can specify the output image format. Input:PDF"
+ " Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity<byte[]> extractImages(@ModelAttribute PDFExtractImagesRequest request)
throws IOException, InterruptedException, ExecutionException {
MultipartFile file = request.getFileInput();

View File

@ -26,7 +26,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.FlattenRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -35,10 +35,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class FlattenController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public FlattenController(CustomPDDocumentFactory pdfDocumentFactory) {
public FlattenController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -46,7 +46,8 @@ public class FlattenController {
@Operation(
summary = "Flatten PDF form fields or full page",
description =
"Flattening just PDF form fields or converting each page to images to make text unselectable. Input:PDF, Output:PDF. Type:SISO")
"Flattening just PDF form fields or converting each page to images to make text"
+ " unselectable. Input:PDF, Output:PDF. Type:SISO")
public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception {
MultipartFile file = request.getFileInput();

View File

@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.MetadataRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
@ -33,10 +33,10 @@ import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class MetadataController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public MetadataController(CustomPDDocumentFactory pdfDocumentFactory) {
public MetadataController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -59,7 +59,9 @@ public class MetadataController {
@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")
"This endpoint allows you to update the metadata of a given PDF file. You can"
+ " add, modify, or delete standard and custom metadata fields. Input:PDF"
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> metadata(@ModelAttribute MetadataRequest request)
throws IOException {

View File

@ -33,7 +33,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
@RestController
@RequestMapping("/api/v1/misc")
@ -43,11 +43,11 @@ public class OCRController {
private final ApplicationProperties applicationProperties;
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
public OCRController(
ApplicationProperties applicationProperties,
CustomPDDocumentFactory pdfDocumentFactory) {
CustomPDFDocumentFactory pdfDocumentFactory) {
this.applicationProperties = applicationProperties;
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -70,7 +70,9 @@ public class OCRController {
@Operation(
summary = "Process PDF files with OCR using Tesseract",
description =
"Takes a PDF file as input, performs OCR using specified languages and OCR type (skip-text/force-ocr), and returns the processed PDF. Input:PDF Output:PDF Type:SISO")
"Takes a PDF file as input, performs OCR using specified languages and OCR type"
+ " (skip-text/force-ocr), and returns the processed PDF. Input:PDF"
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> processPdfWithOCR(
@ModelAttribute ProcessPdfWithOcrRequest request)
throws IOException, InterruptedException {

View File

@ -18,7 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -28,10 +28,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class OverlayImageController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public OverlayImageController(CustomPDDocumentFactory pdfDocumentFactory) {
public OverlayImageController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -39,7 +39,9 @@ public class OverlayImageController {
@Operation(
summary = "Overlay image onto a PDF file",
description =
"This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:SISO")
"This endpoint overlays an image onto a PDF file at the specified coordinates."
+ " The image can be overlaid on every page of the PDF if specified. "
+ " Input:PDF/IMAGE Output:PDF Type:SISO")
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
MultipartFile pdfFile = request.getFileInput();
MultipartFile imageFile = request.getImageFile();

View File

@ -24,7 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -33,10 +33,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class PageNumbersController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public PageNumbersController(CustomPDDocumentFactory pdfDocumentFactory) {
public PageNumbersController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -44,7 +44,8 @@ public class PageNumbersController {
@Operation(
summary = "Add page numbers to a PDF document",
description =
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
"This operation takes an input PDF file and adds page numbers to it. Input:PDF"
+ " Output:PDF Type:SISO")
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
throws IOException {

View File

@ -19,7 +19,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -29,10 +29,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class RepairController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public RepairController(CustomPDDocumentFactory pdfDocumentFactory) {
public RepairController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -40,7 +40,9 @@ public class RepairController {
@Operation(
summary = "Repair a PDF file",
description =
"This endpoint repairs a given PDF file by running qpdf command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO")
"This endpoint repairs a given PDF file by running qpdf command. The PDF is"
+ " first saved to a temporary location, repaired, read back, and then"
+ " returned as a response. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request)
throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput();

View File

@ -20,7 +20,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -28,10 +28,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class ShowJavascript {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public ShowJavascript(CustomPDDocumentFactory pdfDocumentFactory) {
public ShowJavascript(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -38,7 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AddStampRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -46,10 +46,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class StampController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public StampController(CustomPDDocumentFactory pdfDocumentFactory) {
public StampController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -57,7 +57,9 @@ public class StampController {
@Operation(
summary = "Add stamp to a PDF file",
description =
"This endpoint adds a stamp to a given PDF file. Users can specify the stamp type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
"This endpoint adds a stamp to a given PDF file. Users can specify the stamp"
+ " type (text or image), rotation, opacity, width spacer, and height"
+ " spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addStamp(@ModelAttribute AddStampRequest request)
throws IOException, Exception {
MultipartFile pdfFile = request.getFileInput();

View File

@ -68,7 +68,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -81,15 +81,15 @@ public class CertSignController {
Security.addProvider(new BouncyCastleProvider());
}
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
public CertSignController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
private static void sign(
CustomPDDocumentFactory pdfDocumentFactory,
CustomPDFDocumentFactory pdfDocumentFactory,
MultipartFile input,
OutputStream output,
CreateSignature instance,

View File

@ -62,7 +62,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -73,10 +73,10 @@ public class GetInfoOnPDF {
static ObjectMapper objectMapper = new ObjectMapper();
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public GetInfoOnPDF(CustomPDDocumentFactory pdfDocumentFactory) {
public GetInfoOnPDF(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -19,7 +19,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -27,10 +27,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs")
public class PasswordController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public PasswordController(CustomPDDocumentFactory pdfDocumentFactory) {
public PasswordController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -38,8 +38,8 @@ public class PasswordController {
@Operation(
summary = "Remove password from a PDF file",
description =
"This endpoint removes the password from a protected PDF file. Users need to provide the"
+ " existing password. Input:PDF Output:PDF Type:SISO")
"This endpoint removes the password from a protected PDF file. Users need to"
+ " provide the existing password. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
throws IOException {
MultipartFile fileInput = request.getFileInput();
@ -57,8 +57,9 @@ public class PasswordController {
@Operation(
summary = "Add password to a PDF file",
description =
"This endpoint adds password protection to a PDF file. Users can specify a set of"
+ " permissions that should be applied to the file. Input:PDF Output:PDF")
"This endpoint adds password protection to a PDF file. Users can specify a set"
+ " of permissions that should be applied to the file. Input:PDF"
+ " Output:PDF")
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
throws IOException {
MultipartFile fileInput = request.getFileInput();

View File

@ -35,7 +35,7 @@ import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
import stirling.software.SPDF.model.api.security.RedactionArea;
import stirling.software.SPDF.pdf.TextFinder;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -47,10 +47,10 @@ import stirling.software.SPDF.utils.propertyeditor.StringToArrayListPropertyEdit
@Tag(name = "Security", description = "Security APIs")
public class RedactController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public RedactController(CustomPDDocumentFactory pdfDocumentFactory) {
public RedactController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -64,7 +64,9 @@ public class RedactController {
@Operation(
summary = "Redacts areas and pages in a PDF document",
description =
"This operation takes an input PDF file with a list of areas, page number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF, Type:SISO")
"This operation takes an input PDF file with a list of areas, page"
+ " number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF,"
+ " Type:SISO")
public ResponseEntity<byte[]> redactPDF(@ModelAttribute ManualRedactPdfRequest request)
throws IOException {
MultipartFile file = request.getFileInput();
@ -196,8 +198,8 @@ public class RedactController {
@Operation(
summary = "Redacts listOfText in a PDF document",
description =
"This operation takes an input PDF file and redacts the provided listOfText. Input:PDF,"
+ " Output:PDF, Type:SISO")
"This operation takes an input PDF file and redacts the provided listOfText."
+ " Input:PDF, Output:PDF, Type:SISO")
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request)
throws Exception {
MultipartFile file = request.getFileInput();

View File

@ -21,7 +21,7 @@ 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.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -29,10 +29,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs")
public class RemoveCertSignController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public RemoveCertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
public RemoveCertSignController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -40,7 +40,8 @@ public class RemoveCertSignController {
@Operation(
summary = "Remove digital signature from PDF",
description =
"This endpoint accepts a PDF file and returns the PDF file without the digital signature. Input:PDF, Output:PDF Type:SISO")
"This endpoint accepts a PDF file and returns the PDF file without the digital"
+ " signature. Input:PDF, Output:PDF Type:SISO")
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
throws Exception {
MultipartFile pdf = request.getFileInput();

View File

@ -25,7 +25,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@ -33,10 +33,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs")
public class SanitizeController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public SanitizeController(CustomPDDocumentFactory pdfDocumentFactory) {
public SanitizeController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}

View File

@ -35,19 +35,19 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.SignatureValidationRequest;
import stirling.software.SPDF.model.api.security.SignatureValidationResult;
import stirling.software.SPDF.service.CertificateValidationService;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
@RestController
@RequestMapping("/api/v1/security")
@Tag(name = "Security", description = "Security APIs")
public class ValidateSignatureController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final CertificateValidationService certValidationService;
@Autowired
public ValidateSignatureController(
CustomPDDocumentFactory pdfDocumentFactory,
CustomPDFDocumentFactory pdfDocumentFactory,
CertificateValidationService certValidationService) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.certValidationService = certValidationService;
@ -56,7 +56,8 @@ public class ValidateSignatureController {
@Operation(
summary = "Validate PDF Digital Signature",
description =
"Validates the digital signatures in a PDF file against default or custom certificates. Input:PDF Output:JSON Type:SISO")
"Validates the digital signatures in a PDF file against default or custom"
+ " certificates. Input:PDF Output:JSON Type:SISO")
@PostMapping(value = "/validate-signature")
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
@ModelAttribute SignatureValidationRequest request) throws IOException {

View File

@ -36,7 +36,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@ -45,10 +45,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs")
public class WatermarkController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final CustomPDFDocumentFactory pdfDocumentFactory;
@Autowired
public WatermarkController(CustomPDDocumentFactory pdfDocumentFactory) {
public WatermarkController(CustomPDFDocumentFactory pdfDocumentFactory) {
this.pdfDocumentFactory = pdfDocumentFactory;
}
@ -56,7 +56,9 @@ public class WatermarkController {
@Operation(
summary = "Add watermark to a PDF file",
description =
"This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
"This endpoint adds a watermark to a given PDF file. Users can specify the"
+ " watermark type (text or image), rotation, opacity, width spacer, and"
+ " height spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addWatermark(@ModelAttribute AddWatermarkRequest request)
throws IOException, Exception {
MultipartFile pdfFile = request.getFileInput();

View File

@ -29,7 +29,7 @@ import stirling.software.SPDF.model.api.PDFFile;
*/
@Component
@Slf4j
public class CustomPDDocumentFactory {
public class CustomPDFDocumentFactory {
private final PdfMetadataService pdfMetadataService;
@ -63,7 +63,7 @@ public class CustomPDDocumentFactory {
// Counter for tracking temporary resources
private static final AtomicLong tempCounter = new AtomicLong(0);
public CustomPDDocumentFactory(PdfMetadataService pdfMetadataService) {
public CustomPDFDocumentFactory(PdfMetadataService pdfMetadataService) {
this.pdfMetadataService = pdfMetadataService;
}
@ -82,6 +82,21 @@ public class CustomPDDocumentFactory {
return loadAdaptively(file, fileSize);
}
/**
* Main entry point for loading a PDF document from a Path. Automatically selects the most
* appropriate loading strategy.
*/
public PDDocument load(Path path) throws IOException {
if (path == null) {
throw new IllegalArgumentException("File cannot be null");
}
long fileSize = Files.size(path);
log.info("Loading PDF from file, size: {}MB", fileSize / (1024 * 1024));
return loadAdaptively(path.toFile(), fileSize);
}
/** Load a PDF from byte array with automatic optimization. */
public PDDocument load(byte[] input) throws IOException {
if (input == null) {
@ -168,8 +183,8 @@ public class CustomPDDocumentFactory {
private PDDocument loadAdaptively(Object source, long contentSize) throws IOException {
// Get the appropriate caching strategy
StreamCacheCreateFunction cacheFunction = getStreamCacheFunction(contentSize);
//If small handle as bytes and remove original file
// If small handle as bytes and remove original file
if (contentSize <= SMALL_FILE_THRESHOLD && source instanceof File file) {
source = Files.readAllBytes(file.toPath());
file.delete();
@ -192,7 +207,7 @@ public class CustomPDDocumentFactory {
throws IOException {
// Get the appropriate caching strategy
StreamCacheCreateFunction cacheFunction = getStreamCacheFunction(contentSize);
//If small handle as bytes and remove original file
// If small handle as bytes and remove original file
if (contentSize <= SMALL_FILE_THRESHOLD && source instanceof File file) {
source = Files.readAllBytes(file.toPath());
file.delete();
@ -246,6 +261,7 @@ public class CustomPDDocumentFactory {
removePassword(doc);
}
private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache)
throws IOException {
return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache);

View File

@ -35,7 +35,7 @@ import io.github.pixee.security.Filenames;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.service.CustomPDFDocumentFactory;
@Slf4j
public class PdfUtils {
@ -127,7 +127,7 @@ public class PdfUtils {
}
public static byte[] convertFromPdf(
CustomPDDocumentFactory pdfDocumentFactory,
CustomPDFDocumentFactory pdfDocumentFactory,
byte[] inputStream,
String imageType,
ImageType colorType,
@ -315,7 +315,7 @@ public class PdfUtils {
String fitOption,
boolean autoRotate,
String colorType,
CustomPDDocumentFactory pdfDocumentFactory)
CustomPDFDocumentFactory pdfDocumentFactory)
throws IOException {
try (PDDocument doc = pdfDocumentFactory.createNewDocument()) {
for (MultipartFile file : files) {
@ -405,7 +405,7 @@ public class PdfUtils {
}
public static byte[] overlayImage(
CustomPDDocumentFactory pdfDocumentFactory,
CustomPDFDocumentFactory pdfDocumentFactory,
byte[] pdfBytes,
byte[] imageBytes,
float x,

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=إصلاح

View File

@ -860,7 +860,8 @@ sign.last=Son səhifə
sign.next=Növbəti səhifə
sign.previous=Əvvəlki səhifə
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Bərpa Et

View File

@ -860,7 +860,8 @@ sign.last=Последна страница
sign.next=Следваща страница
sign.previous=Предишна стараница
sign.maintainRatio=Превключване за поддържане на съотношението на страните
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Поправи

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparar

View File

@ -860,7 +860,8 @@ sign.last=Poslední stránka
sign.next=Další stránka
sign.previous=Předchozí stránka
sign.maintainRatio=Přepnout zachování poměru stran
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Opravit

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparér

View File

@ -860,7 +860,8 @@ sign.last=Letzte Seite
sign.next=Nächste Seite
sign.previous=Vorherige Seite
sign.maintainRatio=Seitenverhältnis beibehalten ein-/ausschalten
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparieren

View File

@ -860,7 +860,8 @@ sign.last=Τελευταία σελίδα
sign.next=Επόμενη σελίδα
sign.previous=Προηγούμενη σελίδα
sign.maintainRatio=Εναλλαγή διατήρησης αναλογίας διαστάσεων
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Επιδιόρθωση

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Repair

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Repair

View File

@ -860,7 +860,8 @@ sign.last=Última página
sign.next=Siguiente página
sign.previous=Página anterior
sign.maintainRatio=Activar/desactivar la relación de aspecto
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparar

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Konpondu

View File

@ -860,7 +860,8 @@ sign.last=صفحه آخر
sign.next=صفحه بعدی
sign.previous=صفحه قبلی
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=تعمیر

View File

@ -860,7 +860,8 @@ sign.last=Dernière page
sign.next=Page suivante
sign.previous=Page précédente
sign.maintainRatio=Conserver les proportions
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Réparer

View File

@ -860,7 +860,8 @@ sign.last=An leathanach deiridh
sign.next=An chéad leathanach eile
sign.previous=Leathanach roimhe seo
sign.maintainRatio=Scoránaigh, coinnigh an cóimheas gné
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Deisiúchán

View File

@ -860,7 +860,8 @@ sign.last=अंतिम पृष्ठ
sign.next=अगला पृष्ठ
sign.previous=पिछला पृष्ठ
sign.maintainRatio=आनुपातिक अनुपात बनाए रखें टॉगल करें
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=मरम्मत

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Popravi

View File

@ -860,7 +860,8 @@ sign.last=Utolsó oldal
sign.next=Következő oldal
sign.previous=Előző oldal
sign.maintainRatio=Képarány fenntartása váltása
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Javítás

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Perbaiki

View File

@ -860,7 +860,8 @@ sign.last=Ultima pagina
sign.next=Prossima pagina
sign.previous=Pagina precedente
sign.maintainRatio=Attiva il mantenimento delle proporzioni
sign.undo=Annulla
sign.redo=Rifare
#repair
repair.title=Ripara

View File

@ -860,7 +860,8 @@ sign.last=最後のページ
sign.next=次のページ
sign.previous=前のページ
sign.maintainRatio=アスペクト比を維持を切替え
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=修復

View File

@ -860,7 +860,8 @@ sign.last=마지막 페이지
sign.next=다음 페이지
sign.previous=이전 페이지
sign.maintainRatio=종횡비 유지 토글
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=복구

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Repareren

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparer

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Napraw

View File

@ -860,7 +860,8 @@ sign.last=Última página
sign.next=Próxima página
sign.previous=Página anterior
sign.maintainRatio=Habilitar manter proporção
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparar

View File

@ -860,7 +860,8 @@ sign.last=Última página
sign.next=Próxima página
sign.previous=Página anterior
sign.maintainRatio=Alternar manter proporção
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparar

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Repară

View File

@ -860,7 +860,8 @@ sign.last=Последняя страница
sign.next=Следующая страница
sign.previous=Предыдущая страница
sign.maintainRatio=Переключить сохранение пропорций
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Восстановление

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Opraviť

View File

@ -860,7 +860,8 @@ sign.last=Zadnja stran
sign.next=Naslednja stran
sign.previous=Prejšnja stran
sign.maintainRatio=Preklopi ohranjanje razmerja stranic
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Popravilo

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Popravi

View File

@ -860,7 +860,8 @@ sign.last=Sista sidan
sign.next=Nästa sida
sign.previous=Föregående sida
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Reparera

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=ซ่อมแซม

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Onar

View File

@ -860,7 +860,8 @@ sign.last=Остання сторінка
sign.next=Наступна сторінка
sign.previous=Попередня сторінка
sign.maintainRatio=Переключити збереження пропорцій
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Ремонт

View File

@ -860,7 +860,8 @@ sign.last=Last page
sign.next=Next page
sign.previous=Previous page
sign.maintainRatio=Toggle maintain aspect ratio
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=Sửa chữa

View File

@ -860,7 +860,8 @@ sign.last=ཤོག་ངོས་མཐའ་མ།
sign.next=ཤོག་ངོས་རྗེས་མ།
sign.previous=ཤོག་ངོས་སྔོན་མ།
sign.maintainRatio=བསྡུར་ཚད་རྒྱུན་འཁྱོངས་སྒོ་རྒྱག་པ།
sign.undo=Undo
sign.redo=Redo
#repair
repair.title=བཟོ་བཅོས།

View File

@ -860,7 +860,8 @@ sign.last=末页
sign.next=下一页
sign.previous=上一页
sign.maintainRatio=切换保持长宽比
sign.undo=撤销
sign.redo=重做
#repair
repair.title=修复

View File

@ -860,7 +860,8 @@ sign.last=最後一頁
sign.next=下一頁
sign.previous=上一頁
sign.maintainRatio=切換維持長寬比
sign.undo=撤销
sign.redo=重做
#repair
repair.title=修復

View File

@ -530,13 +530,6 @@
"moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "commons-logging:commons-logging",
"moduleUrl": "https://commons.apache.org/proper/commons-logging/",
"moduleVersion": "1.3.4",
"moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "io.dropwizard.metrics:metrics-core",
"moduleVersion": "4.2.25",
@ -560,7 +553,7 @@
{
"moduleName": "io.micrometer:micrometer-core",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.14.4",
"moduleVersion": "1.14.5",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},

View File

@ -1,10 +1,48 @@
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
const undoButton = document.getElementById("signature-undo-button");
const redoButton = document.getElementById("signature-redo-button");
const signaturePad = new SignaturePad(signaturePadCanvas, {
minWidth: 1,
maxWidth: 2,
penColor: 'black',
});
let undoData = [];
signaturePad.addEventListener("endStroke", () => {
undoData = [];
});
window.addEventListener("keydown", (event) => {
switch (true) {
case event.key === "z" && event.ctrlKey:
undoButton.click();
break;
case event.key === "y" && event.ctrlKey:
redoButton.click();
break;
}
});
function undoDraw() {
const data = signaturePad.toData();
if (data && data.length > 0) {
const removed = data.pop();
undoData.push(removed);
signaturePad.fromData(data);
}
}
function redoDraw() {
if (undoData.length > 0) {
const data = signaturePad.toData();
data.push(undoData.pop());
signaturePad.fromData(data);
}
}
function addDraggableFromPad() {
if (signaturePad.isEmpty()) return;
const startTime = Date.now();

View File

@ -34,11 +34,15 @@
<!-- Bootstrap -->
<script th:src="@{'/js/thirdParty/popper.min.js'}"></script>
<script th:src="@{'/js/thirdParty/bootstrap.min.js'}"></script>
<link rel="stylesheet" th:href="@{'/css/bootstrap.min.css'}">
<!-- Bootstrap Icons -->
<link rel="stylesheet" th:href="@{'/css/bootstrap-icons.min.css'}">
<!-- Pixel, doesn't collect any PII-->
<img referrerpolicy="no-referrer-when-downgrade" src="https://pixel.stirlingpdf.com/a.png?x-pxid=4f5fa02f-a065-4efb-bb2c-24509a4b6b92" style="position: absolute; visibility: hidden;"/>
<!-- Custom -->
<link rel="stylesheet" th:href="@{'/css/general.css'}">
<link rel="stylesheet" th:href="@{'/css/theme/theme.css'}">

View File

@ -56,6 +56,10 @@
th:text="#{sign.clear}"></button>
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()"
th:text="#{sign.add}"></button>
<button id="signature-undo-button" class="btn btn-outline-secondary mt-2" th:text="#{sign.undo}"
onclick="undoDraw()"></button>
<button id="signature-redo-button" class="btn btn-outline-secondary mt-2" th:text="#{sign.redo}"
onclick="redoDraw()"></button>
</div>
<div class="tab-container" th:title="#{sign.saved}" th:data-title="#{sign.saved}">

View File

@ -1,11 +1,6 @@
package stirling.software.SPDF;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -16,22 +11,15 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.env.Environment;
import stirling.software.SPDF.model.ApplicationProperties;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.Files.createFile;
import static java.nio.file.Files.delete;
import static java.nio.file.Files.exists;
@ExtendWith(MockitoExtension.class)
public class SPDFApplicationTest {
@Mock
private Environment env;
@Mock private Environment env;
@Mock
private ApplicationProperties applicationProperties;
@Mock private ApplicationProperties applicationProperties;
@InjectMocks
private SPDFApplication sPDFApplication;
@InjectMocks private SPDFApplication sPDFApplication;
@BeforeEach
public void setUp() {
@ -48,5 +36,4 @@ public class SPDFApplicationTest {
public void testGetStaticPort() {
assertEquals("8080", SPDFApplication.getStaticPort());
}
}

View File

@ -1,27 +1,29 @@
package stirling.software.SPDF.config.security;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.ApplicationProperties;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CustomLogoutSuccessHandlerTest {
@Mock
private ApplicationProperties applicationProperties;
@Mock private ApplicationProperties applicationProperties;
@InjectMocks
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@InjectMocks private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@Test
void testSuccessfulLogout() throws IOException {
@ -44,7 +46,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken oAuth2AuthenticationToken = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
@ -70,7 +73,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
@ -100,7 +104,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter(error)).thenReturn("true");
@ -125,7 +130,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
@ -151,7 +157,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
@ -179,7 +186,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
@ -209,7 +217,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);
@ -240,7 +249,8 @@ class CustomLogoutSuccessHandlerTest {
HttpServletResponse response = mock(HttpServletResponse.class);
OAuth2AuthenticationToken authentication = mock(OAuth2AuthenticationToken.class);
ApplicationProperties.Security security = mock(ApplicationProperties.Security.class);
ApplicationProperties.Security.OAUTH2 oauth = mock(ApplicationProperties.Security.OAUTH2.class);
ApplicationProperties.Security.OAUTH2 oauth =
mock(ApplicationProperties.Security.OAUTH2.class);
when(response.isCommitted()).thenReturn(false);
when(request.getParameter("oAuth2AuthenticationErrorWeb")).thenReturn(null);

View File

@ -1,6 +1,12 @@
package stirling.software.SPDF.config.security.database;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import javax.sql.DataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -8,18 +14,14 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.exception.UnsupportedProviderException;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DatabaseConfigTest {
@Mock
private ApplicationProperties applicationProperties;
@Mock private ApplicationProperties applicationProperties;
private DatabaseConfig databaseConfig;

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