From 4594765cbdd6acc0aa6b57b27692c3016a8f2617 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 17:21:59 +0100 Subject: [PATCH 01/10] rearrange support n numbers, downloader.js fixes --- .../api/RearrangePagesPDFController.java | 412 +++++++++--------- src/main/resources/messages_en_GB.properties | 2 +- src/main/resources/static/js/downloader.js | 64 +-- .../resources/templates/pdf-organizer.html | 2 +- 4 files changed, 235 insertions(+), 245 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index 718f8520..8ae1b533 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -6,6 +6,11 @@ import stirling.software.SPDF.utils.WebResponseUtils; import java.util.ArrayList; import java.util.List; +import javax.script.ScriptEngineManager; +import javax.script.ScriptEngine; +import javax.script.ScriptException; + +import javax.script.ScriptEngine; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -24,228 +29,241 @@ import io.swagger.v3.oas.annotations.Parameter; @RestController public class RearrangePagesPDFController { - private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); + private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class); + @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") + @Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.") + public ResponseEntity deletePages( + @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile, + @RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete) + throws IOException { - @PostMapping(consumes = "multipart/form-data", value = "/remove-pages") - @Operation(summary = "Remove pages from a PDF file", - description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete.") - public ResponseEntity deletePages( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file from which pages will be removed") - MultipartFile pdfFile, - @RequestParam("pagesToDelete") - @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") - String pagesToDelete) throws IOException { + PDDocument document = PDDocument.load(pdfFile.getBytes()); - PDDocument document = PDDocument.load(pdfFile.getBytes()); + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pagesToDelete.split(","); - // Split the page order string into an array of page numbers or range of numbers - String[] pageOrderArr = pagesToDelete.split(","); + List pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); - List pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); + for (int i = pagesToRemove.size() - 1; i >= 0; i--) { + int pageIndex = pagesToRemove.get(i); + document.removePage(pageIndex); + } + return WebResponseUtils.pdfDocToWebResponse(document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); - for (int i = pagesToRemove.size() - 1; i >= 0; i--) { - int pageIndex = pagesToRemove.get(i); - document.removePage(pageIndex); - } - return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf"); + } - } + private enum CustomMode { + REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, + } - private List pageOrderToString(String[] pageOrderArr, int totalPages) { - List newPageOrder = new ArrayList<>(); - // loop through the page order array - for (String element : pageOrderArr) { - // check if the element contains a range of pages - if (element.contains("-")) { - // split the range into start and end page - String[] range = element.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - // check if the end page is greater than total pages - if (end > totalPages) { - end = totalPages; - } - // loop through the range of pages - for (int j = start; j <= end; j++) { - // print the current index - newPageOrder.add(j - 1); - } - } else { - // if the element is a single page - newPageOrder.add(Integer.parseInt(element) - 1); - } - } + private List removeFirst(int totalPages) { + if (totalPages <= 1) + return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 2; i <= totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - return newPageOrder; - } + private List removeLast(int totalPages) { + if (totalPages <= 1) + return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 1; i < totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private enum CustomMode { - REVERSE_ORDER, - DUPLEX_SORT, - BOOKLET_SORT, - ODD_EVEN_SPLIT, - REMOVE_FIRST, - REMOVE_LAST, - REMOVE_FIRST_AND_LAST, - } + private List removeFirstAndLast(int totalPages) { + if (totalPages <= 2) + return new ArrayList<>(); + List newPageOrder = new ArrayList<>(); + for (int i = 2; i < totalPages; i++) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List removeFirst(int totalPages) { - if (totalPages <= 1) return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 2; i <= totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List reverseOrder(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = totalPages; i >= 1; i--) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List removeLast(int totalPages) { - if (totalPages <= 1) return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 1; i < totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List duplexSort(int totalPages) { + List newPageOrder = new ArrayList<>(); + int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages + for (int i = 1; i <= half; i++) { + newPageOrder.add(i - 1); + if (i <= totalPages - half) { // Avoid going out of bounds + newPageOrder.add(totalPages - i); + } + } + return newPageOrder; + } - private List removeFirstAndLast(int totalPages) { - if (totalPages <= 2) return new ArrayList<>(); - List newPageOrder = new ArrayList<>(); - for (int i = 2; i < totalPages; i++) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List bookletSort(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 0; i < totalPages / 2; i++) { + newPageOrder.add(i); + newPageOrder.add(totalPages - i - 1); + } + return newPageOrder; + } - - private List reverseOrder(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = totalPages; i >= 1; i--) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + private List oddEvenSplit(int totalPages) { + List newPageOrder = new ArrayList<>(); + for (int i = 1; i <= totalPages; i += 2) { + newPageOrder.add(i - 1); + } + for (int i = 2; i <= totalPages; i += 2) { + newPageOrder.add(i - 1); + } + return newPageOrder; + } - private List duplexSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages - for (int i = 1; i <= half; i++) { - newPageOrder.add(i - 1); - if (i <= totalPages - half) { // Avoid going out of bounds - newPageOrder.add(totalPages - i); - } - } - return newPageOrder; - } + private List processCustomMode(String customMode, int totalPages) { + try { + CustomMode mode = CustomMode.valueOf(customMode.toUpperCase()); + switch (mode) { + case REVERSE_ORDER: + return reverseOrder(totalPages); + case DUPLEX_SORT: + return duplexSort(totalPages); + case BOOKLET_SORT: + return bookletSort(totalPages); + case ODD_EVEN_SPLIT: + return oddEvenSplit(totalPages); + case REMOVE_FIRST: + return removeFirst(totalPages); + case REMOVE_LAST: + return removeLast(totalPages); + case REMOVE_FIRST_AND_LAST: + return removeFirstAndLast(totalPages); + default: + throw new IllegalArgumentException("Unsupported custom mode"); + } + } catch (IllegalArgumentException e) { + logger.error("Unsupported custom mode", e); + return null; + } + } + @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") + @Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.") + public ResponseEntity rearrangePages( + @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile, + @RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder, + @RequestParam(required = false, value = "customMode") @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. " + + "Valid values are:\n" + "REVERSE_ORDER: Reverses the order of all pages.\n" + + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" + + "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n" + + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) String customMode) { + try { + // Load the input PDF + PDDocument document = PDDocument.load(pdfFile.getInputStream()); - private List bookletSort(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 0; i < totalPages / 2; i++) { - newPageOrder.add(i); - newPageOrder.add(totalPages - i - 1); - } - return newPageOrder; - } + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; + int totalPages = document.getNumberOfPages(); + System.out.println("pageOrder=" + pageOrder); + System.out.println("customMode length =" + customMode.length()); + List newPageOrder; + if (customMode != null && customMode.length() > 0) { + newPageOrder = processCustomMode(customMode, totalPages); + } else { + newPageOrder = pageOrderToString(pageOrderArr, totalPages); + } - private List oddEvenSplit(int totalPages) { - List newPageOrder = new ArrayList<>(); - for (int i = 1; i <= totalPages; i += 2) { - newPageOrder.add(i - 1); - } - for (int i = 2; i <= totalPages; i += 2) { - newPageOrder.add(i - 1); - } - return newPageOrder; - } + // Create a new list to hold the pages in the new order + List newPages = new ArrayList<>(); + for (int i = 0; i < newPageOrder.size(); i++) { + newPages.add(document.getPage(newPageOrder.get(i))); + } - private List processCustomMode(String customMode, int totalPages) { - try { - CustomMode mode = CustomMode.valueOf(customMode.toUpperCase()); - switch (mode) { - case REVERSE_ORDER: - return reverseOrder(totalPages); - case DUPLEX_SORT: - return duplexSort(totalPages); - case BOOKLET_SORT: - return bookletSort(totalPages); - case ODD_EVEN_SPLIT: - return oddEvenSplit(totalPages); - case REMOVE_FIRST: - return removeFirst(totalPages); - case REMOVE_LAST: - return removeLast(totalPages); - case REMOVE_FIRST_AND_LAST: - return removeFirstAndLast(totalPages); - default: - throw new IllegalArgumentException("Unsupported custom mode"); - } - } catch (IllegalArgumentException e) { - logger.error("Unsupported custom mode", e); - return null; - } - } + // Remove all the pages from the original document + for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { + document.removePage(i); + } - @PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages") - @Operation(summary = "Rearrange pages in a PDF file", - description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode.") - public ResponseEntity rearrangePages( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to rearrange pages") - MultipartFile pdfFile, - @RequestParam(required = false, value = "pageOrder") - @Parameter(description = "The new page order as a comma-separated list of page numbers or page ranges (e.g., '1,3,5-7')") - String pageOrder, - @RequestParam(required = false, value = "customMode") - @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. " + - "Valid values are:\n" + - "REVERSE_ORDER: Reverses the order of all pages.\n" + - "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + - "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + - "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" + - "REMOVE_FIRST: Removes the first page.\n" + - "REMOVE_LAST: Removes the last page.\n" + - "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) - String customMode) { - try { - // Load the input PDF - PDDocument document = PDDocument.load(pdfFile.getInputStream()); + // Add the pages in the new order + for (PDPage page : newPages) { + document.addPage(page); + } - // Split the page order string into an array of page numbers or range of numbers - String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; - int totalPages = document.getNumberOfPages(); - System.out.println("pageOrder=" + pageOrder); - System.out.println("customMode length =" + customMode.length()); - List newPageOrder; - if(customMode != null && customMode.length() > 0) { - newPageOrder = processCustomMode(customMode, totalPages); - } else { - newPageOrder = pageOrderToString(pageOrderArr, totalPages); - } + return WebResponseUtils.pdfDocToWebResponse(document, + pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); + } catch (IOException e) { + logger.error("Failed rearranging documents", e); + return null; + } + } - // Create a new list to hold the pages in the new order - List newPages = new ArrayList<>(); - for (int i = 0; i < newPageOrder.size(); i++) { - newPages.add(document.getPage(newPageOrder.get(i))); - } + private List pageOrderToString(String[] pageOrderArr, int totalPages) { + List newPageOrder = new ArrayList<>(); - // Remove all the pages from the original document - for (int i = document.getNumberOfPages() - 1; i >= 0; i--) { - document.removePage(i); - } + // loop through the page order array + for (String element : pageOrderArr) { + // check if the element contains a range of pages + if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { + // Handle page order as a function + int coefficient = 0; + int constant = 0; + boolean coefficientExists = false; + boolean constantExists = false; - // Add the pages in the new order - for (PDPage page : newPages) { - document.addPage(page); - } + if (element.contains("n")) { + String[] parts = element.split("n"); + if (!parts[0].equals("") && parts[0] != null) { + coefficient = Integer.parseInt(parts[0]); + coefficientExists = true; + } + if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { + constant = Integer.parseInt(parts[1]); + constantExists = true; + } + } else if (element.contains("+")) { + constant = Integer.parseInt(element.replace("+", "")); + constantExists = true; + } - return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf"); - } catch (IOException e) { - logger.error("Failed rearranging documents", e); - return null; - } - } + for (int i = 1; i <= totalPages; i++) { + int pageNum = coefficientExists ? coefficient * i : i; + pageNum += constantExists ? constant : 0; + if (pageNum <= totalPages && pageNum > 0) { + newPageOrder.add(pageNum - 1); + } + } + } else if (element.contains("-")) { + // split the range into start and end page + String[] range = element.split("-"); + int start = Integer.parseInt(range[0]); + int end = Integer.parseInt(range[1]); + // check if the end page is greater than total pages + if (end > totalPages) { + end = totalPages; + } + // loop through the range of pages + for (int j = start; j <= end; j++) { + // print the current index + newPageOrder.add(j - 1); + } + } else { + // if the element is a single page + newPageOrder.add(Integer.parseInt(element) - 1); + } + } + + return newPageOrder; + } } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 5eee2506..04f7df53 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -10,7 +10,7 @@ multiPdfDropPrompt=Select (or drag & drop) all PDFs you require imgPrompt=Select Image(s) genericSubmit=Submit processTimeWarning=Warning: This process can take up to a minute depending on file-size -pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers) : +pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) : goToPage=Go true=True false=False diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index 5dc1912f..35623db6 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -19,25 +19,9 @@ $(document).ready(function() { try { if (override === 'multi' || files.length > 1 && override !== 'single') { - // Show the progress bar - $('#progressBarContainer').show(); - // Initialize the progress bar - //let progressBar = $('#progressBar'); - //progressBar.css('width', '0%'); - //progressBar.attr('aria-valuenow', 0); - //progressBar.attr('aria-valuemax', files.length); - await submitMultiPdfForm(url, files); } else { - const downloadDetails = await handleSingleDownload(url, formData); - const downloadOption = localStorage.getItem('downloadOption'); - - // Handle the download action according to the selected option - //handleDownloadAction(downloadOption, downloadDetails.blob, downloadDetails.filename); - - // Update the progress bar - //updateProgressBar(progressBar, 1); - + await handleSingleDownload(url, formData); } $('#submitBtn').text('Submit'); @@ -49,29 +33,9 @@ $(document).ready(function() { }); }); -function handleDownloadAction(downloadOption, blob, filename) { - const url = URL.createObjectURL(blob); - switch (downloadOption) { - case 'sameWindow': - // Open the file in the same window - window.location.href = url; - break; - case 'newWindow': - // Open the file in a new window - window.open(url, '_blank'); - break; - default: - // Download the file - const link = document.createElement('a'); - link.href = url; - link.download = filename; - link.click(); - break; - } -} -async function handleSingleDownload(url, formData) { +async function handleSingleDownload(url, formData, isMulti = false) { try { const response = await fetch(url, { method: 'POST', body: formData }); const contentType = response.headers.get('content-type'); @@ -90,7 +54,7 @@ async function handleSingleDownload(url, formData) { const blob = await response.blob(); if (contentType.includes('application/pdf') || contentType.includes('image/')) { - return handleResponse(blob, filename, true); + return handleResponse(blob, filename, !isMulti); } else { return handleResponse(blob, filename); } @@ -118,7 +82,7 @@ function getFilenameFromContentDisposition(contentDisposition) { async function handleJsonResponse(response) { const json = await response.json(); const errorMessage = JSON.stringify(json, null, 2); - if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided')) { + if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) { alert('[[#{error.pdfPassword}]]'); } else { showErrorBanner(json.error + ':' + json.message, json.trace); @@ -173,10 +137,15 @@ async function submitMultiPdfForm(url, files) { const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4; const zipFiles = files.length > zipThreshold; let jszip = null; - //let progressBar = $('#progressBar'); - //progressBar.css('width', '0%'); - //progressBar.attr('aria-valuenow', 0); - //progressBar.attr('aria-valuemax', Array.from(files).length); + // Show the progress bar + $('#progressBarContainer').show(); + // Initialize the progress bar + + let progressBar = $('#progressBar'); + progressBar.css('width', '0%'); + progressBar.attr('aria-valuenow', 0); + progressBar.attr('aria-valuemax', files.length); + if (zipFiles) { jszip = new JSZip(); } @@ -202,20 +171,21 @@ async function submitMultiPdfForm(url, files) { } try { - const downloadDetails = await handleSingleDownload(url, fileFormData); + const downloadDetails = await handleSingleDownload(url, fileFormData, true); console.log(downloadDetails); if (zipFiles) { jszip.file(downloadDetails.filename, downloadDetails.blob); } else { downloadFile(downloadDetails.blob, downloadDetails.filename); } - //updateProgressBar(progressBar, Array.from(files).length); + updateProgressBar(progressBar, Array.from(files).length); } catch (error) { handleDownloadError(error); console.error(error); } }); await Promise.all(promises); + } if (zipFiles) { @@ -226,6 +196,8 @@ async function submitMultiPdfForm(url, files) { console.error('Error generating ZIP file: ' + error); } } + progressBar.css('width', '100%'); + progressBar.attr('aria-valuenow', Array.from(files).length); } diff --git a/src/main/resources/templates/pdf-organizer.html b/src/main/resources/templates/pdf-organizer.html index 580f88a0..6accc45f 100644 --- a/src/main/resources/templates/pdf-organizer.html +++ b/src/main/resources/templates/pdf-organizer.html @@ -31,7 +31,7 @@
- +
From c526e18992f74dfa63eb7e2bc16a260fdf404a42 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 22:56:15 +0100 Subject: [PATCH 02/10] Split pages support n function and other stuff --- .github/workflows/swagger.yml | 45 + .gitignore | 1 + SwaggerDoc.json | 2329 +++++++++++++++++ build.gradle | 19 + .../software/SPDF/config/OpenApiConfig.java | 30 +- .../api/RearrangePagesPDFController.java | 74 +- .../controller/api/ScalePagesController.java | 139 +- .../controller/api/SplitPDFController.java | 34 +- .../api/security/CertSignController.java | 5 +- .../controller/web/OtherWebController.java | 7 + .../software/SPDF/utils/GeneralUtils.java | 61 + .../software/SPDF/utils/PdfUtils.java | 6 - src/main/resources/messages_en_GB.properties | 9 +- src/main/resources/static/js/downloader.js | 8 +- .../resources/templates/fragments/common.html | 3 + .../resources/templates/other/auto-crop.html | 31 + 16 files changed, 2682 insertions(+), 119 deletions(-) create mode 100644 .github/workflows/swagger.yml create mode 100644 SwaggerDoc.json create mode 100644 src/main/resources/templates/other/auto-crop.html diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml new file mode 100644 index 00000000..59258e3e --- /dev/null +++ b/.github/workflows/swagger.yml @@ -0,0 +1,45 @@ +name: Update Swagger + +on: + workflow_dispatch: + push: + branches: + - master +jobs: + push: + + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v3.5.2 + + - name: Set up JDK 17 + uses: actions/setup-java@v3.11.0 + with: + java-version: '17' + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Generate Swagger documentation + run: ./gradlew generateOpenApiDocs + + - name: Get version number + id: versionNumber + run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" + + - name: Commit and push if it changed + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add -A + git diff --quiet && git diff --staged --quiet || git commit -m "Updated Swagger documentation" + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git + git push + + - name: Upload Swagger Documentation to SwaggerHub + run: | + curl -X POST -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" + env: + SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} diff --git a/.gitignore b/.gitignore index 3c11770c..0a5cd198 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ local.properties .recommenders .classpath .project +version.properties # Gradle .gradle diff --git a/SwaggerDoc.json b/SwaggerDoc.json new file mode 100644 index 00000000..39db60b4 --- /dev/null +++ b/SwaggerDoc.json @@ -0,0 +1,2329 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Stirling PDF API", + "description": "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.", + "version": "0.10.0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "paths": { + "/update-metadata": { + "post": { + "tags": [ + "metadata-controller" + ], + "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.", + "operationId": "metadata", + "parameters": [ + { + "name": "deleteAll", + "in": "query", + "description": "Delete all metadata if set to true", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "author", + "in": "query", + "description": "The author of the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "creationDate", + "in": "query", + "description": "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "creator", + "in": "query", + "description": "The creator of the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "keywords", + "in": "query", + "description": "The keywords for the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "modificationDate", + "in": "query", + "description": "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "producer", + "in": "query", + "description": "The producer of the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "subject", + "in": "query", + "description": "The subject of the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "title", + "in": "query", + "description": "The title of the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "trapped", + "in": "query", + "description": "The trapped status of the document", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "allRequestParams", + "in": "query", + "required": true, + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to update metadata", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/split-pages": { + "post": { + "tags": [ + "split-pdf-controller" + ], + "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 \u0027all\u0027 for every page.", + "operationId": "splitPdf", + "parameters": [ + { + "name": "pages", + "in": "query", + "description": "The pages to be included in separate documents. Specify individual page numbers (e.g., \u00271,3,5\u0027), ranges (e.g., \u00271-3,5-7\u0027), or \u0027all\u0027 for every page.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be split", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/scale-pages": { + "post": { + "tags": [ + "scale-pages-controller" + ], + "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.", + "operationId": "scalePages", + "parameters": [ + { + "name": "pageSize", + "in": "query", + "description": "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", + "required": true, + "schema": { + "type": "String", + "enum": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "A10", + "B0", + "B1", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "LETTER", + "TABLOID", + "LEDGER", + "LEGAL", + "EXECUTIVE" + ] + } + }, + { + "name": "scaleFactor", + "in": "query", + "description": "The scale of the content on the pages of the output PDF. Acceptable values are floats.", + "required": true, + "schema": { + "type": "float" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/rotate-pdf": { + "post": { + "tags": [ + "rotation-controller" + ], + "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.", + "operationId": "rotatePDF", + "parameters": [ + { + "name": "angle", + "in": "query", + "description": "The angle by which to rotate the PDF file. This should be a multiple of 90.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "example": 90 + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The PDF file to be rotated", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/repair": { + "post": { + "tags": [ + "repair-controller" + ], + "summary": "Repair a PDF file", + "description": "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.", + "operationId": "repairPdf", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be repaired", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/remove-password": { + "post": { + "tags": [ + "password-controller" + ], + "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.", + "operationId": "removePassword", + "parameters": [ + { + "name": "password", + "in": "query", + "description": "The password of the PDF file", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file from which the password should be removed", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/remove-pages": { + "post": { + "tags": [ + "rearrange-pages-pdf-controller" + ], + "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.", + "operationId": "deletePages", + "parameters": [ + { + "name": "pagesToDelete", + "in": "query", + "description": "Comma-separated list of pages or page ranges to delete, e.g., \u00271,3,5-8\u0027", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file from which pages will be removed", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/remove-blanks": { + "post": { + "tags": [ + "blank-page-controller" + ], + "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.", + "operationId": "removeBlankPages", + "parameters": [ + { + "name": "threshold", + "in": "query", + "description": "The threshold value to determine blank pages", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + }, + "example": 10 + }, + { + "name": "whitePercent", + "in": "query", + "description": "The percentage of white color on a page to consider it as blank", + "required": false, + "schema": { + "type": "number", + "format": "float", + "default": 99.9 + }, + "example": 99.9 + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file from which blank pages will be removed", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/rearrange-pages": { + "post": { + "tags": [ + "rearrange-pages-pdf-controller" + ], + "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.", + "operationId": "rearrangePages", + "parameters": [ + { + "name": "pageOrder", + "in": "query", + "description": "The new page order as a comma-separated list of page numbers, page ranges (e.g., \u00271,3,5-7\u0027), or functions in the format \u0027an+b\u0027 where \u0027a\u0027 is the multiplier of the page number \u0027n\u0027, and \u0027b\u0027 is a constant (e.g., \u00272n+1\u0027, \u00273n\u0027, \u00276n-5\u0027)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "customMode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "REVERSE_ORDER", + "DUPLEX_SORT", + "BOOKLET_SORT", + "ODD_EVEN_SPLIT", + "REMOVE_FIRST", + "REMOVE_LAST", + "REMOVE_FIRST_AND_LAST" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to rearrange pages", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/pdf-to-xml": { + "post": { + "tags": [ + "convert-pdf-to-office" + ], + "summary": "Convert PDF to XML", + "description": "This endpoint converts a PDF file to an XML file.", + "operationId": "processPdfToXML", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be converted to an XML file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/pdf-to-word": { + "post": { + "tags": [ + "convert-pdf-to-office" + ], + "summary": "Convert PDF to Word document", + "description": "This endpoint converts a given PDF file to a Word document format.", + "operationId": "processPdfToWord", + "parameters": [ + { + "name": "outputFormat", + "in": "query", + "description": "The output Word document format", + "required": true, + "schema": { + "type": "string", + "enum": [ + "doc", + "docx", + "odt" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/pdf-to-text": { + "post": { + "tags": [ + "convert-pdf-to-office" + ], + "summary": "Convert PDF to Text or RTF format", + "description": "This endpoint converts a given PDF file to Text or RTF format.", + "operationId": "processPdfToRTForTXT", + "parameters": [ + { + "name": "outputFormat", + "in": "query", + "description": "The output Text or RTF format", + "required": true, + "schema": { + "type": "string", + "enum": [ + "rtf", + "txt:Text" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/pdf-to-presentation": { + "post": { + "tags": [ + "convert-pdf-to-office" + ], + "summary": "Convert PDF to Presentation format", + "description": "This endpoint converts a given PDF file to a Presentation format.", + "operationId": "processPdfToPresentation", + "parameters": [ + { + "name": "outputFormat", + "in": "query", + "description": "The output Presentation format", + "required": true, + "schema": { + "type": "string", + "enum": [ + "ppt", + "pptx", + "odp" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/pdf-to-pdfa": { + "post": { + "tags": [ + "convert-pdf-to-pdfa" + ], + "summary": "Convert a PDF to a PDF/A", + "description": "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents.", + "operationId": "pdfToPdfA", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be converted to a PDF/A file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/pdf-to-img": { + "post": { + "tags": [ + "convert-img-pdf-controller" + ], + "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.", + "operationId": "convertToImage", + "parameters": [ + { + "name": "imageFormat", + "in": "query", + "description": "The output image format", + "required": true, + "schema": { + "type": "string", + "enum": [ + "png", + "jpeg", + "jpg", + "gif" + ] + } + }, + { + "name": "singleOrMultiple", + "in": "query", + "description": "Choose between a single image containing all pages or separate images for each page", + "required": true, + "schema": { + "type": "string", + "enum": [ + "single", + "multiple" + ] + } + }, + { + "name": "colorType", + "in": "query", + "description": "The color type of the output image(s)", + "required": true, + "schema": { + "type": "string", + "enum": [ + "rgb", + "greyscale", + "blackwhite" + ] + } + }, + { + "name": "dpi", + "in": "query", + "description": "The DPI (dots per inch) for the output image(s)", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be converted", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/pdf-to-html": { + "post": { + "tags": [ + "convert-pdf-to-office" + ], + "summary": "Convert PDF to HTML", + "description": "This endpoint converts a PDF file to HTML format.", + "operationId": "processPdfToHTML", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be converted to HTML format", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/ocr-pdf": { + "post": { + "tags": [ + "ocr-controller" + ], + "summary": "Process a PDF file with OCR", + "description": "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options.", + "operationId": "processPdfWithOCR", + "parameters": [ + { + "name": "languages", + "in": "query", + "description": "List of languages to use in OCR processing", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "sidecar", + "in": "query", + "description": "Include OCR text in a sidecar text file if set to true", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "deskew", + "in": "query", + "description": "Deskew the input file if set to true", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "clean", + "in": "query", + "description": "Clean the input file if set to true", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "clean-final", + "in": "query", + "description": "Clean the final output if set to true", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "ocrType", + "in": "query", + "description": "Specify the OCR type, e.g., \u0027skip-text\u0027, \u0027force-ocr\u0027, or \u0027Normal\u0027", + "required": false, + "schema": { + "type": "string", + "enum": [ + "skip-text", + "force-ocr", + "Normal" + ] + } + }, + { + "name": "ocrRenderType", + "in": "query", + "description": "Specify the OCR render type, either \u0027hocr\u0027 or \u0027sandwich\u0027", + "required": false, + "schema": { + "type": "string", + "enum": [ + "hocr", + "sandwich" + ] + } + }, + { + "name": "removeImagesAfter", + "in": "query", + "description": "Remove images from the output PDF if set to true", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be processed with OCR", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/multi-page-layout": { + "post": { + "tags": [ + "multi-page-layout-controller" + ], + "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.", + "operationId": "mergeMultiplePagesIntoOne", + "parameters": [ + { + "name": "pagesPerSheet", + "in": "query", + "description": "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", + "required": true, + "schema": { + "type": "integer", + "enum": [ + "2", + "3", + "4", + "9", + "16" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/merge-pdfs": { + "post": { + "tags": [ + "merge-controller" + ], + "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.", + "operationId": "mergePdfs", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "array", + "description": "The input PDF files to be merged into a single file", + "items": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/img-to-pdf": { + "post": { + "tags": [ + "convert-img-pdf-controller" + ], + "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.", + "operationId": "convertToPdf", + "parameters": [ + { + "name": "stretchToFit", + "in": "query", + "description": "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "colorType", + "in": "query", + "description": "The color type of the output image(s)", + "required": true, + "schema": { + "type": "string", + "enum": [ + "rgb", + "greyscale", + "blackwhite" + ] + } + }, + { + "name": "autoRotate", + "in": "query", + "description": "Whether to automatically rotate the images to better fit the PDF page", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": true + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "array", + "description": "The input images to be converted to a PDF file", + "items": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/file-to-pdf": { + "post": { + "tags": [ + "convert-office-controller" + ], + "summary": "Convert a file to a PDF using OCR", + "description": "This endpoint converts a given file to a PDF using Optical Character Recognition (OCR). The filename of the resulting PDF will be the original filename with \u0027_convertedToPDF.pdf\u0027 appended.", + "operationId": "processPdfWithOCR_1", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input file to be converted to a PDF file using OCR", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/extract-images": { + "post": { + "tags": [ + "extract-images-controller" + ], + "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.", + "operationId": "extractImages", + "parameters": [ + { + "name": "format", + "in": "query", + "description": "The output image format e.g., \u0027png\u0027, \u0027jpeg\u0027, or \u0027gif\u0027", + "required": true, + "schema": { + "type": "string", + "enum": [ + "png", + "jpeg", + "gif" + ] + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file containing images", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/extract-image-scans": { + "post": { + "tags": [ + "extract-image-scans-controller" + ], + "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.", + "operationId": "extractImageScans", + "parameters": [ + { + "name": "angle_threshold", + "in": "query", + "description": "The angle threshold for the image scan extraction", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 5 + }, + "example": 5 + }, + { + "name": "tolerance", + "in": "query", + "description": "The tolerance for the image scan extraction", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 20 + }, + "example": 20 + }, + { + "name": "min_area", + "in": "query", + "description": "The minimum area for the image scan extraction", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 8000 + }, + "example": 8000 + }, + { + "name": "min_contour_area", + "in": "query", + "description": "The minimum contour area for the image scan extraction", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 500 + }, + "example": 500 + }, + { + "name": "border_size", + "in": "query", + "description": "The border size for the image scan extraction", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + }, + "example": 1 + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input file containing image scans", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/compress-pdf": { + "post": { + "tags": [ + "compress-controller" + ], + "summary": "Optimize PDF file", + "description": "This endpoint accepts a PDF file and optimizes it based on the provided parameters.", + "operationId": "optimizePdf", + "parameters": [ + { + "name": "optimizeLevel", + "in": "query", + "description": "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1", + "2", + "3", + "4", + "5" + ] + } + }, + { + "name": "expectedOutputSize", + "in": "query", + "description": "The expected output size, e.g. \u0027100MB\u0027, \u002725KB\u0027, etc.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be optimized.", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/cert-sign": { + "post": { + "tags": [ + "cert-sign-controller" + ], + "summary": "Sign PDF with a Digital Certificate", + "description": "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file.", + "operationId": "signPDF", + "parameters": [ + { + "name": "certType", + "in": "query", + "description": "The type of the digital certificate", + "required": false, + "schema": { + "type": "string", + "enum": [ + "PKCS12", + "PEM" + ] + } + }, + { + "name": "password", + "in": "query", + "description": "The password for the keystore or the private key", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "showSignature", + "in": "query", + "description": "Whether to visually show the signature in the PDF file", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "reason", + "in": "query", + "description": "The reason for signing the PDF", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "location", + "in": "query", + "description": "The location where the PDF is signed", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "description": "The name of the signer", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pageNumber", + "in": "query", + "description": "The page number where the signature should be visible. This is required if showSignature is set to true", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to be signed", + "format": "binary" + }, + "key": { + "type": "string", + "description": "The private key for the digital certificate (required for PEM type certificates)", + "format": "binary" + }, + "cert": { + "type": "string", + "description": "The digital certificate (required for PEM type certificates)", + "format": "binary" + }, + "p12": { + "type": "string", + "description": "The PKCS12 keystore file (required for PKCS12 type certificates)", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/auto-crop": { + "post": { + "tags": [ + "scale-pages-controller" + ], + "operationId": "cropPdf", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/add-watermark": { + "post": { + "tags": [ + "watermark-controller" + ], + "summary": "Add watermark to a PDF file", + "description": "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer.", + "operationId": "addWatermark", + "parameters": [ + { + "name": "watermarkText", + "in": "query", + "description": "The watermark text to add to the PDF file", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "fontSize", + "in": "query", + "description": "The font size of the watermark text", + "required": false, + "schema": { + "type": "number", + "format": "float", + "default": 30.0 + }, + "example": 30 + }, + { + "name": "rotation", + "in": "query", + "description": "The rotation of the watermark text in degrees", + "required": false, + "schema": { + "type": "number", + "format": "float", + "default": 0.0 + }, + "example": 0 + }, + { + "name": "opacity", + "in": "query", + "description": "The opacity of the watermark text (0.0 - 1.0)", + "required": false, + "schema": { + "type": "number", + "format": "float", + "default": 0.5 + }, + "example": 0.5 + }, + { + "name": "widthSpacer", + "in": "query", + "description": "The width spacer between watermark texts", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50 + }, + "example": 50 + }, + { + "name": "heightSpacer", + "in": "query", + "description": "The height spacer between watermark texts", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 50 + }, + "example": 50 + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to add a watermark", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/add-password": { + "post": { + "tags": [ + "password-controller" + ], + "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.", + "operationId": "addPassword", + "parameters": [ + { + "name": "ownerPassword", + "in": "query", + "description": "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)", + "required": false, + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "password", + "in": "query", + "description": "The password to be added to the PDF file (Restricts the opening of the document itself.)", + "required": false, + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "keyLength", + "in": "query", + "description": "The length of the encryption key", + "required": false, + "schema": { + "type": "string", + "enum": [ + "40", + "128", + "256" + ] + } + }, + { + "name": "canAssembleDocument", + "in": "query", + "description": "Whether the document assembly is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canExtractContent", + "in": "query", + "description": "Whether content extraction for accessibility is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canExtractForAccessibility", + "in": "query", + "description": "Whether content extraction for accessibility is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canFillInForm", + "in": "query", + "description": "Whether form filling is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canModify", + "in": "query", + "description": "Whether the document modification is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canModifyAnnotations", + "in": "query", + "description": "Whether modification of annotations is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canPrint", + "in": "query", + "description": "Whether printing of the document is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + }, + { + "name": "canPrintFaithful", + "in": "query", + "description": "Whether faithful printing is allowed", + "required": false, + "schema": { + "type": "boolean", + "default": false + }, + "example": false + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to which the password should be added", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/add-image": { + "post": { + "tags": [ + "overlay-image-controller" + ], + "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.", + "operationId": "overlayImage", + "parameters": [ + { + "name": "x", + "in": "query", + "description": "The x-coordinate at which to place the top-left corner of the image.", + "required": true, + "schema": { + "type": "number", + "format": "float" + }, + "example": 0 + }, + { + "name": "y", + "in": "query", + "description": "The y-coordinate at which to place the top-left corner of the image.", + "required": true, + "schema": { + "type": "number", + "format": "float" + }, + "example": 0 + }, + { + "name": "everyPage", + "in": "query", + "description": "Whether to overlay the image onto every page of the PDF.", + "required": true, + "schema": { + "type": "boolean" + }, + "example": false + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "fileInput", + "fileInput2" + ], + "type": "object", + "properties": { + "fileInput": { + "type": "string", + "description": "The input PDF file to overlay the image onto.", + "format": "binary" + }, + "fileInput2": { + "type": "string", + "description": "The image file to be overlaid onto the PDF.", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + } + } + } + } + } + } + }, + "/api/v1/status": { + "get": { + "tags": [ + "metrics-controller" + ], + "summary": "Application status and version", + "description": "This endpoint returns the status of the application and its version number.", + "operationId": "getStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/v1/requests": { + "get": { + "tags": [ + "metrics-controller" + ], + "summary": "POST request count", + "description": "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.", + "operationId": "getTotalRequests", + "parameters": [ + { + "name": "endpoint", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "number", + "format": "double" + } + } + } + } + } + } + }, + "/api/v1/loads": { + "get": { + "tags": [ + "metrics-controller" + ], + "summary": "GET request count", + "description": "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.", + "operationId": "getPageLoads", + "parameters": [ + { + "name": "endpoint", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "number", + "format": "double" + } + } + } + } + } + } + } + }, + "components": {} +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index ea16d563..6c2095f0 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.1.0' id 'io.spring.dependency-management' version '1.1.0' + id 'org.springdoc.openapi-gradle-plugin' version '1.6.0' } group = 'stirling.software' @@ -12,6 +13,12 @@ repositories { mavenCentral() } +openApi { + apiDocsUrl = "http://localhost:8080/v3/api-docs" + outputDir = file("/") + outputFileName = "SwaggerDoc.json" +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.0' @@ -34,6 +41,18 @@ dependencies { } +task writeVersion { + def propsFile = file('src/main/resources/version.properties') + def props = new Properties() + props.setProperty('version', version) + props.store(propsFile.newWriter(), null) +} + +tasks.matching { it.name == 'generateOpenApiDocs' }.all { + dependsOn writeVersion +} + + jar { enabled = false manifest { diff --git a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index 0ab63952..d7495aca 100644 --- a/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -1,5 +1,9 @@ package stirling.software.SPDF.config; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,13 +14,23 @@ import io.swagger.v3.oas.models.info.Info; @Configuration public class OpenApiConfig { - @Bean - public OpenAPI customOpenAPI() { - String version = getClass().getPackage().getImplementationVersion(); - version = (version != null) ? version : "1.0.0"; - - return new OpenAPI().components(new Components()).info( - new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); - } + @Bean + public OpenAPI customOpenAPI() { + String version = getClass().getPackage().getImplementationVersion(); + if (version == null) { + Properties props = new Properties(); + try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) { + props.load(input); + version = props.getProperty("version"); + } catch (IOException ex) { + ex.printStackTrace(); + version = "1.0.0"; // default version if all else fails + } + } + + return new OpenAPI().components(new Components()).info( + new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); + } + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index 8ae1b533..c1a0c892 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -1,16 +1,8 @@ package stirling.software.SPDF.controller.api; import java.io.IOException; -import io.swagger.v3.oas.annotations.media.Schema; -import stirling.software.SPDF.utils.WebResponseUtils; - import java.util.ArrayList; import java.util.List; -import javax.script.ScriptEngineManager; -import javax.script.ScriptEngine; -import javax.script.ScriptException; - -import javax.script.ScriptEngine; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -25,6 +17,9 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import stirling.software.SPDF.utils.GeneralUtils; +import stirling.software.SPDF.utils.WebResponseUtils; @RestController public class RearrangePagesPDFController { @@ -43,7 +38,7 @@ public class RearrangePagesPDFController { // Split the page order string into an array of page numbers or range of numbers String[] pageOrderArr = pagesToDelete.split(","); - List pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); + List pagesToRemove = GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); for (int i = pagesToRemove.size() - 1; i >= 0; i--) { int pageIndex = pagesToRemove.get(i); @@ -180,7 +175,7 @@ public class RearrangePagesPDFController { if (customMode != null && customMode.length() > 0) { newPageOrder = processCustomMode(customMode, totalPages); } else { - newPageOrder = pageOrderToString(pageOrderArr, totalPages); + newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages); } // Create a new list to hold the pages in the new order @@ -207,63 +202,6 @@ public class RearrangePagesPDFController { } } - private List pageOrderToString(String[] pageOrderArr, int totalPages) { - List newPageOrder = new ArrayList<>(); - - // loop through the page order array - for (String element : pageOrderArr) { - // check if the element contains a range of pages - if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { - // Handle page order as a function - int coefficient = 0; - int constant = 0; - boolean coefficientExists = false; - boolean constantExists = false; - - if (element.contains("n")) { - String[] parts = element.split("n"); - if (!parts[0].equals("") && parts[0] != null) { - coefficient = Integer.parseInt(parts[0]); - coefficientExists = true; - } - if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { - constant = Integer.parseInt(parts[1]); - constantExists = true; - } - } else if (element.contains("+")) { - constant = Integer.parseInt(element.replace("+", "")); - constantExists = true; - } - - for (int i = 1; i <= totalPages; i++) { - int pageNum = coefficientExists ? coefficient * i : i; - pageNum += constantExists ? constant : 0; - - if (pageNum <= totalPages && pageNum > 0) { - newPageOrder.add(pageNum - 1); - } - } - } else if (element.contains("-")) { - // split the range into start and end page - String[] range = element.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - // check if the end page is greater than total pages - if (end > totalPages) { - end = totalPages; - } - // loop through the range of pages - for (int j = start; j <= end; j++) { - // print the current index - newPageOrder.add(j - 1); - } - } else { - // if the element is a single page - newPageOrder.add(Integer.parseInt(element) - 1); - } - } - - return newPageOrder; - } + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 508d4fc3..0c079b9e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -3,10 +3,18 @@ package stirling.software.SPDF.controller.api; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -20,14 +28,17 @@ import com.itextpdf.kernel.pdf.PdfPage; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.kernel.pdf.canvas.parser.EventType; +import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor; +import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData; +import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo; +import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener; import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import stirling.software.SPDF.utils.WebResponseUtils; -import java.util.HashMap; -import java.util.Map; @RestController public class ScalePagesController { @@ -119,4 +130,128 @@ public class ScalePagesController { pdfDoc.close(); return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); } + + + + + + + + +@PostMapping(value = "/auto-crop", consumes = "multipart/form-data") +public ResponseEntity cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { + byte[] bytes = file.getBytes(); + PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); + PdfDocument pdfDoc = new PdfDocument(reader); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(baos); + PdfDocument outputPdf = new PdfDocument(writer); + + int totalPages = pdfDoc.getNumberOfPages(); + for (int i = 1; i <= totalPages; i++) { + PdfPage page = pdfDoc.getPage(i); + Rectangle originalMediaBox = page.getMediaBox(); + + Rectangle contentBox = determineContentBox(page); + + // Make sure we don't go outside the original media box. + Rectangle intersection = originalMediaBox.getIntersection(contentBox); + page.setCropBox(intersection); + + // Copy page to the new document + outputPdf.addPage(page.copyTo(outputPdf)); + } + + outputPdf.close(); + byte[] pdfContent = baos.toByteArray(); + pdfDoc.close(); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"") + .contentType(MediaType.APPLICATION_PDF) + .body(pdfContent); +} + +private Rectangle determineContentBox(PdfPage page) { + // Extract the text from the page and find the bounding box. + TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder(); + PdfCanvasProcessor processor = new PdfCanvasProcessor(finder); + processor.processPageContent(page); + return finder.getBoundingBox(); +} +private static class TextBoundingRectangleFinder implements IEventListener { + private List allTextBoxes = new ArrayList<>(); + + public Rectangle getBoundingBox() { + // Sort the text boxes based on their vertical position + allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop)); + + // Consider a box an outlier if its top is more than 1.5 times the IQR above the third quartile. + int q1Index = allTextBoxes.size() / 4; + int q3Index = 3 * allTextBoxes.size() / 4; + double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop(); + double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr; + + // Initialize boundingBox to the first non-outlier box + int i = 0; + while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) { + i++; + } + if (i == allTextBoxes.size()) { + // If all boxes are outliers, just return the first one + return allTextBoxes.get(0); + } + Rectangle boundingBox = allTextBoxes.get(i); + + // Extend the bounding box to include all non-outlier boxes + for (; i < allTextBoxes.size(); i++) { + Rectangle textBoundingBox = allTextBoxes.get(i); + if (textBoundingBox.getTop() > threshold) { + // This box is an outlier, skip it + continue; + } + float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft()); + float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom()); + float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight()); + float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop()); + + // Add a small padding around the bounding box + float padding = 10; + boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding, top - bottom + 2 * padding); + } + return boundingBox; + } + + @Override + public void eventOccurred(IEventData data, EventType type) { + if (type == EventType.RENDER_TEXT) { + TextRenderInfo renderInfo = (TextRenderInfo) data; + allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle()); + } + } + + @Override + public Set getSupportedEvents() { + return Collections.singleton(EventType.RENDER_TEXT); + } +} + + + + + + + + + + + + + + + + + + + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 99152e69..04301b96 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -6,7 +6,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -29,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import stirling.software.SPDF.utils.GeneralUtils; @RestController public class SplitPDFController { @@ -58,39 +58,28 @@ public class SplitPDFController { pageNumbers.add(i); } } else { - List pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(","))); - if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) { - String lastpage = String.valueOf(document.getNumberOfPages()); - pageNumbersStr.add(lastpage); - } - for (String page : pageNumbersStr) { - if (page.contains("-")) { - String[] range = page.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - for (int i = start; i <= end; i++) { - pageNumbers.add(i); - } - } else { - pageNumbers.add(Integer.parseInt(page)); - } + String[] splitPoints = pages.split(","); + for (String splitPoint : splitPoints) { + List orderedPages = GeneralUtils.parsePageList(new String[] {splitPoint}, document.getNumberOfPages()); + pageNumbers.addAll(orderedPages); } + // Add the last page as a split point + pageNumbers.add(document.getNumberOfPages() - 1); } logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); // split the document List splitDocumentsBoas = new ArrayList<>(); - int currentPage = 0; - for (int pageNumber : pageNumbers) { + int previousPageNumber = 0; + for (int splitPoint : pageNumbers) { try (PDDocument splitDocument = new PDDocument()) { - for (int i = currentPage; i < pageNumber; i++) { + for (int i = previousPageNumber; i <= splitPoint; i++) { PDPage page = document.getPage(i); splitDocument.addPage(page); logger.debug("Adding page {} to split document", i); } - currentPage = pageNumber; - logger.debug("Setting current page to {}", currentPage); + previousPageNumber = splitPoint + 1; ByteArrayOutputStream baos = new ByteArrayOutputStream(); splitDocument.save(baos); @@ -102,6 +91,7 @@ public class SplitPDFController { } } + // closing the original document document.close(); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index bd2914bf..9f9dddd8 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -1,9 +1,6 @@ package stirling.software.SPDF.controller.api.security; import java.io.ByteArrayInputStream; -import io.swagger.v3.oas.annotations.media.Schema; -import stirling.software.SPDF.utils.WebResponseUtils; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -53,6 +50,8 @@ import com.itextpdf.signatures.SignatureUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import stirling.software.SPDF.utils.WebResponseUtils; @RestController public class CertSignController { diff --git a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java index 95cef9e7..5a9e93c0 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/OtherWebController.java @@ -122,4 +122,11 @@ public class OtherWebController { return "other/scale-pages"; } + @GetMapping("/auto-crop") + @Hidden + public String autoCropForm(Model model) { + model.addAttribute("currentPage", "auto-crop"); + return "other/auto-crop"; + } + } diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 0d8325e4..1e101d09 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -1,5 +1,8 @@ package stirling.software.SPDF.utils; +import java.util.ArrayList; +import java.util.List; + public class GeneralUtils { public static Long convertSizeToBytes(String sizeStr) { @@ -27,4 +30,62 @@ public class GeneralUtils { return null; } + public static List parsePageList(String[] pageOrderArr, int totalPages) { + List newPageOrder = new ArrayList<>(); + + // loop through the page order array + for (String element : pageOrderArr) { + // check if the element contains a range of pages + if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { + // Handle page order as a function + int coefficient = 0; + int constant = 0; + boolean coefficientExists = false; + boolean constantExists = false; + + if (element.contains("n")) { + String[] parts = element.split("n"); + if (!parts[0].equals("") && parts[0] != null) { + coefficient = Integer.parseInt(parts[0]); + coefficientExists = true; + } + if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { + constant = Integer.parseInt(parts[1]); + constantExists = true; + } + } else if (element.contains("+")) { + constant = Integer.parseInt(element.replace("+", "")); + constantExists = true; + } + + for (int i = 1; i <= totalPages; i++) { + int pageNum = coefficientExists ? coefficient * i : i; + pageNum += constantExists ? constant : 0; + + if (pageNum <= totalPages && pageNum > 0) { + newPageOrder.add(pageNum - 1); + } + } + } else if (element.contains("-")) { + // split the range into start and end page + String[] range = element.split("-"); + int start = Integer.parseInt(range[0]); + int end = Integer.parseInt(range[1]); + // check if the end page is greater than total pages + if (end > totalPages) { + end = totalPages; + } + // loop through the range of pages + for (int j = start; j <= end; j++) { + // print the current index + newPageOrder.add(j - 1); + } + } else { + // if the element is a single page + newPageOrder.add(Integer.parseInt(element) - 1); + } + } + + return newPageOrder; + } } diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 3956a4a1..e5d35c08 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -9,12 +9,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 04f7df53..e38bb558 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -54,14 +54,11 @@ home.pdfOrganiser.title=Organise home.pdfOrganiser.desc=Remove/Rearrange pages in any order home.addImage.title=Add image -home.addImage.desc=Adds a image onto a set location on the PDF (Work in progress) +home.addImage.desc=Adds a image onto a set location on the PDF home.watermark.title=Add Watermark home.watermark.desc=Add a custom watermark to your PDF document. -home.remove-watermark.title=Remove Watermark -home.remove-watermark.desc=Remove watermarks from your PDF document. - home.permissions.title=Change Permissions home.permissions.desc=Change the permissions of your PDF document @@ -131,8 +128,8 @@ home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page -home.scalePages.title=Adjust page-scale -home.scalePages.desc=Change the size of page contents while maintaining a set page-size +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index 35623db6..bfc0860a 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -14,7 +14,7 @@ $(document).ready(function() { const files = $('#fileInput-input')[0].files; const formData = new FormData(this); const override = $('#override').val() || ''; - + const originalButtonText = $('#submitBtn').text(); $('#submitBtn').text('Processing...'); try { @@ -24,10 +24,10 @@ $(document).ready(function() { await handleSingleDownload(url, formData); } - $('#submitBtn').text('Submit'); + $('#submitBtn').text(originalButtonText); } catch (error) { handleDownloadError(error); - $('#submitBtn').text('Submit'); + $('#submitBtn').text(originalButtonText); console.error(error); } }); @@ -83,7 +83,7 @@ async function handleJsonResponse(response) { const json = await response.json(); const errorMessage = JSON.stringify(json, null, 2); if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) { - alert('[[#{error.pdfPassword}]]'); + alert(pdfPasswordPrompt); } else { showErrorBanner(json.error + ':' + json.message, json.trace); } diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index e08f7b04..75870630 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -91,6 +91,9 @@ +
diff --git a/src/main/resources/templates/other/auto-crop.html b/src/main/resources/templates/other/auto-crop.html new file mode 100644 index 00000000..a7ba407a --- /dev/null +++ b/src/main/resources/templates/other/auto-crop.html @@ -0,0 +1,31 @@ + + + + + + + + +
+
+
+

+
+
+
+

+
+
+
+ +
+

+
+
+
+ +
+
+
+ + \ No newline at end of file From 1f5231d905703d31141bfd599ece46ae7e0f025b Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 23:01:14 +0100 Subject: [PATCH 03/10] @./SwaggerDoc.json --- .github/workflows/swagger.yml | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 59258e3e..6798de11 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -40,6 +40,6 @@ jobs: - name: Upload Swagger Documentation to SwaggerHub run: | - curl -X POST -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" + curl -X POST -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @./SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" env: SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} diff --git a/build.gradle b/build.gradle index 6c2095f0..478ee5b4 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ repositories { openApi { apiDocsUrl = "http://localhost:8080/v3/api-docs" - outputDir = file("/") + outputDir = file("$projectDir") outputFileName = "SwaggerDoc.json" } From 48b3dea256dfb4da1bcb67a7e80b8b623ff0b8a8 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 23:05:20 +0100 Subject: [PATCH 04/10] remove push --- .github/workflows/swagger.yml | 9 - SwaggerDoc.json | 2329 --------------------------------- 2 files changed, 2338 deletions(-) delete mode 100644 SwaggerDoc.json diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 6798de11..2952c5b1 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -28,15 +28,6 @@ jobs: - name: Get version number id: versionNumber run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" - - - name: Commit and push if it changed - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add -A - git diff --quiet && git diff --staged --quiet || git commit -m "Updated Swagger documentation" - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git - git push - name: Upload Swagger Documentation to SwaggerHub run: | diff --git a/SwaggerDoc.json b/SwaggerDoc.json deleted file mode 100644 index 39db60b4..00000000 --- a/SwaggerDoc.json +++ /dev/null @@ -1,2329 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "Stirling PDF API", - "description": "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.", - "version": "0.10.0" - }, - "servers": [ - { - "url": "http://localhost:8080", - "description": "Generated server url" - } - ], - "paths": { - "/update-metadata": { - "post": { - "tags": [ - "metadata-controller" - ], - "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.", - "operationId": "metadata", - "parameters": [ - { - "name": "deleteAll", - "in": "query", - "description": "Delete all metadata if set to true", - "required": false, - "schema": { - "type": "boolean", - "default": false - } - }, - { - "name": "author", - "in": "query", - "description": "The author of the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "creationDate", - "in": "query", - "description": "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "creator", - "in": "query", - "description": "The creator of the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "keywords", - "in": "query", - "description": "The keywords for the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "modificationDate", - "in": "query", - "description": "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "producer", - "in": "query", - "description": "The producer of the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "subject", - "in": "query", - "description": "The subject of the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "title", - "in": "query", - "description": "The title of the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "trapped", - "in": "query", - "description": "The trapped status of the document", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "allRequestParams", - "in": "query", - "required": true, - "schema": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to update metadata", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/split-pages": { - "post": { - "tags": [ - "split-pdf-controller" - ], - "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 \u0027all\u0027 for every page.", - "operationId": "splitPdf", - "parameters": [ - { - "name": "pages", - "in": "query", - "description": "The pages to be included in separate documents. Specify individual page numbers (e.g., \u00271,3,5\u0027), ranges (e.g., \u00271-3,5-7\u0027), or \u0027all\u0027 for every page.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be split", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - } - } - } - }, - "/scale-pages": { - "post": { - "tags": [ - "scale-pages-controller" - ], - "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.", - "operationId": "scalePages", - "parameters": [ - { - "name": "pageSize", - "in": "query", - "description": "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", - "required": true, - "schema": { - "type": "String", - "enum": [ - "A0", - "A1", - "A2", - "A3", - "A4", - "A5", - "A6", - "A7", - "A8", - "A9", - "A10", - "B0", - "B1", - "B2", - "B3", - "B4", - "B5", - "B6", - "B7", - "B8", - "B9", - "LETTER", - "TABLOID", - "LEDGER", - "LEGAL", - "EXECUTIVE" - ] - } - }, - { - "name": "scaleFactor", - "in": "query", - "description": "The scale of the content on the pages of the output PDF. Acceptable values are floats.", - "required": true, - "schema": { - "type": "float" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/rotate-pdf": { - "post": { - "tags": [ - "rotation-controller" - ], - "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.", - "operationId": "rotatePDF", - "parameters": [ - { - "name": "angle", - "in": "query", - "description": "The angle by which to rotate the PDF file. This should be a multiple of 90.", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - }, - "example": 90 - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The PDF file to be rotated", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/repair": { - "post": { - "tags": [ - "repair-controller" - ], - "summary": "Repair a PDF file", - "description": "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.", - "operationId": "repairPdf", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be repaired", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/remove-password": { - "post": { - "tags": [ - "password-controller" - ], - "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.", - "operationId": "removePassword", - "parameters": [ - { - "name": "password", - "in": "query", - "description": "The password of the PDF file", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file from which the password should be removed", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/remove-pages": { - "post": { - "tags": [ - "rearrange-pages-pdf-controller" - ], - "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.", - "operationId": "deletePages", - "parameters": [ - { - "name": "pagesToDelete", - "in": "query", - "description": "Comma-separated list of pages or page ranges to delete, e.g., \u00271,3,5-8\u0027", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file from which pages will be removed", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/remove-blanks": { - "post": { - "tags": [ - "blank-page-controller" - ], - "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.", - "operationId": "removeBlankPages", - "parameters": [ - { - "name": "threshold", - "in": "query", - "description": "The threshold value to determine blank pages", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 10 - }, - "example": 10 - }, - { - "name": "whitePercent", - "in": "query", - "description": "The percentage of white color on a page to consider it as blank", - "required": false, - "schema": { - "type": "number", - "format": "float", - "default": 99.9 - }, - "example": 99.9 - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file from which blank pages will be removed", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/rearrange-pages": { - "post": { - "tags": [ - "rearrange-pages-pdf-controller" - ], - "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.", - "operationId": "rearrangePages", - "parameters": [ - { - "name": "pageOrder", - "in": "query", - "description": "The new page order as a comma-separated list of page numbers, page ranges (e.g., \u00271,3,5-7\u0027), or functions in the format \u0027an+b\u0027 where \u0027a\u0027 is the multiplier of the page number \u0027n\u0027, and \u0027b\u0027 is a constant (e.g., \u00272n+1\u0027, \u00273n\u0027, \u00276n-5\u0027)", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "customMode", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": [ - "REVERSE_ORDER", - "DUPLEX_SORT", - "BOOKLET_SORT", - "ODD_EVEN_SPLIT", - "REMOVE_FIRST", - "REMOVE_LAST", - "REMOVE_FIRST_AND_LAST" - ] - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to rearrange pages", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/pdf-to-xml": { - "post": { - "tags": [ - "convert-pdf-to-office" - ], - "summary": "Convert PDF to XML", - "description": "This endpoint converts a PDF file to an XML file.", - "operationId": "processPdfToXML", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be converted to an XML file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/pdf-to-word": { - "post": { - "tags": [ - "convert-pdf-to-office" - ], - "summary": "Convert PDF to Word document", - "description": "This endpoint converts a given PDF file to a Word document format.", - "operationId": "processPdfToWord", - "parameters": [ - { - "name": "outputFormat", - "in": "query", - "description": "The output Word document format", - "required": true, - "schema": { - "type": "string", - "enum": [ - "doc", - "docx", - "odt" - ] - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/pdf-to-text": { - "post": { - "tags": [ - "convert-pdf-to-office" - ], - "summary": "Convert PDF to Text or RTF format", - "description": "This endpoint converts a given PDF file to Text or RTF format.", - "operationId": "processPdfToRTForTXT", - "parameters": [ - { - "name": "outputFormat", - "in": "query", - "description": "The output Text or RTF format", - "required": true, - "schema": { - "type": "string", - "enum": [ - "rtf", - "txt:Text" - ] - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/pdf-to-presentation": { - "post": { - "tags": [ - "convert-pdf-to-office" - ], - "summary": "Convert PDF to Presentation format", - "description": "This endpoint converts a given PDF file to a Presentation format.", - "operationId": "processPdfToPresentation", - "parameters": [ - { - "name": "outputFormat", - "in": "query", - "description": "The output Presentation format", - "required": true, - "schema": { - "type": "string", - "enum": [ - "ppt", - "pptx", - "odp" - ] - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/pdf-to-pdfa": { - "post": { - "tags": [ - "convert-pdf-to-pdfa" - ], - "summary": "Convert a PDF to a PDF/A", - "description": "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents.", - "operationId": "pdfToPdfA", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be converted to a PDF/A file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/pdf-to-img": { - "post": { - "tags": [ - "convert-img-pdf-controller" - ], - "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.", - "operationId": "convertToImage", - "parameters": [ - { - "name": "imageFormat", - "in": "query", - "description": "The output image format", - "required": true, - "schema": { - "type": "string", - "enum": [ - "png", - "jpeg", - "jpg", - "gif" - ] - } - }, - { - "name": "singleOrMultiple", - "in": "query", - "description": "Choose between a single image containing all pages or separate images for each page", - "required": true, - "schema": { - "type": "string", - "enum": [ - "single", - "multiple" - ] - } - }, - { - "name": "colorType", - "in": "query", - "description": "The color type of the output image(s)", - "required": true, - "schema": { - "type": "string", - "enum": [ - "rgb", - "greyscale", - "blackwhite" - ] - } - }, - { - "name": "dpi", - "in": "query", - "description": "The DPI (dots per inch) for the output image(s)", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be converted", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - } - } - } - }, - "/pdf-to-html": { - "post": { - "tags": [ - "convert-pdf-to-office" - ], - "summary": "Convert PDF to HTML", - "description": "This endpoint converts a PDF file to HTML format.", - "operationId": "processPdfToHTML", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be converted to HTML format", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/ocr-pdf": { - "post": { - "tags": [ - "ocr-controller" - ], - "summary": "Process a PDF file with OCR", - "description": "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options.", - "operationId": "processPdfWithOCR", - "parameters": [ - { - "name": "languages", - "in": "query", - "description": "List of languages to use in OCR processing", - "required": true, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "sidecar", - "in": "query", - "description": "Include OCR text in a sidecar text file if set to true", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "deskew", - "in": "query", - "description": "Deskew the input file if set to true", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "clean", - "in": "query", - "description": "Clean the input file if set to true", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "clean-final", - "in": "query", - "description": "Clean the final output if set to true", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "ocrType", - "in": "query", - "description": "Specify the OCR type, e.g., \u0027skip-text\u0027, \u0027force-ocr\u0027, or \u0027Normal\u0027", - "required": false, - "schema": { - "type": "string", - "enum": [ - "skip-text", - "force-ocr", - "Normal" - ] - } - }, - { - "name": "ocrRenderType", - "in": "query", - "description": "Specify the OCR render type, either \u0027hocr\u0027 or \u0027sandwich\u0027", - "required": false, - "schema": { - "type": "string", - "enum": [ - "hocr", - "sandwich" - ] - } - }, - { - "name": "removeImagesAfter", - "in": "query", - "description": "Remove images from the output PDF if set to true", - "required": false, - "schema": { - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be processed with OCR", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/multi-page-layout": { - "post": { - "tags": [ - "multi-page-layout-controller" - ], - "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.", - "operationId": "mergeMultiplePagesIntoOne", - "parameters": [ - { - "name": "pagesPerSheet", - "in": "query", - "description": "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", - "required": true, - "schema": { - "type": "integer", - "enum": [ - "2", - "3", - "4", - "9", - "16" - ] - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/merge-pdfs": { - "post": { - "tags": [ - "merge-controller" - ], - "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.", - "operationId": "mergePdfs", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "array", - "description": "The input PDF files to be merged into a single file", - "items": { - "type": "string", - "format": "binary" - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/img-to-pdf": { - "post": { - "tags": [ - "convert-img-pdf-controller" - ], - "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.", - "operationId": "convertToPdf", - "parameters": [ - { - "name": "stretchToFit", - "in": "query", - "description": "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "colorType", - "in": "query", - "description": "The color type of the output image(s)", - "required": true, - "schema": { - "type": "string", - "enum": [ - "rgb", - "greyscale", - "blackwhite" - ] - } - }, - { - "name": "autoRotate", - "in": "query", - "description": "Whether to automatically rotate the images to better fit the PDF page", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": true - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "array", - "description": "The input images to be converted to a PDF file", - "items": { - "type": "string", - "format": "binary" - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/file-to-pdf": { - "post": { - "tags": [ - "convert-office-controller" - ], - "summary": "Convert a file to a PDF using OCR", - "description": "This endpoint converts a given file to a PDF using Optical Character Recognition (OCR). The filename of the resulting PDF will be the original filename with \u0027_convertedToPDF.pdf\u0027 appended.", - "operationId": "processPdfWithOCR_1", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input file to be converted to a PDF file using OCR", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/extract-images": { - "post": { - "tags": [ - "extract-images-controller" - ], - "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.", - "operationId": "extractImages", - "parameters": [ - { - "name": "format", - "in": "query", - "description": "The output image format e.g., \u0027png\u0027, \u0027jpeg\u0027, or \u0027gif\u0027", - "required": true, - "schema": { - "type": "string", - "enum": [ - "png", - "jpeg", - "gif" - ] - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file containing images", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/extract-image-scans": { - "post": { - "tags": [ - "extract-image-scans-controller" - ], - "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.", - "operationId": "extractImageScans", - "parameters": [ - { - "name": "angle_threshold", - "in": "query", - "description": "The angle threshold for the image scan extraction", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 5 - }, - "example": 5 - }, - { - "name": "tolerance", - "in": "query", - "description": "The tolerance for the image scan extraction", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 20 - }, - "example": 20 - }, - { - "name": "min_area", - "in": "query", - "description": "The minimum area for the image scan extraction", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 8000 - }, - "example": 8000 - }, - { - "name": "min_contour_area", - "in": "query", - "description": "The minimum contour area for the image scan extraction", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 500 - }, - "example": 500 - }, - { - "name": "border_size", - "in": "query", - "description": "The border size for the image scan extraction", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 1 - }, - "example": 1 - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input file containing image scans", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/compress-pdf": { - "post": { - "tags": [ - "compress-controller" - ], - "summary": "Optimize PDF file", - "description": "This endpoint accepts a PDF file and optimizes it based on the provided parameters.", - "operationId": "optimizePdf", - "parameters": [ - { - "name": "optimizeLevel", - "in": "query", - "description": "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "1", - "2", - "3", - "4", - "5" - ] - } - }, - { - "name": "expectedOutputSize", - "in": "query", - "description": "The expected output size, e.g. \u0027100MB\u0027, \u002725KB\u0027, etc.", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be optimized.", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/cert-sign": { - "post": { - "tags": [ - "cert-sign-controller" - ], - "summary": "Sign PDF with a Digital Certificate", - "description": "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file.", - "operationId": "signPDF", - "parameters": [ - { - "name": "certType", - "in": "query", - "description": "The type of the digital certificate", - "required": false, - "schema": { - "type": "string", - "enum": [ - "PKCS12", - "PEM" - ] - } - }, - { - "name": "password", - "in": "query", - "description": "The password for the keystore or the private key", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "showSignature", - "in": "query", - "description": "Whether to visually show the signature in the PDF file", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "reason", - "in": "query", - "description": "The reason for signing the PDF", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "location", - "in": "query", - "description": "The location where the PDF is signed", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "name", - "in": "query", - "description": "The name of the signer", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "pageNumber", - "in": "query", - "description": "The page number where the signature should be visible. This is required if showSignature is set to true", - "required": false, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to be signed", - "format": "binary" - }, - "key": { - "type": "string", - "description": "The private key for the digital certificate (required for PEM type certificates)", - "format": "binary" - }, - "cert": { - "type": "string", - "description": "The digital certificate (required for PEM type certificates)", - "format": "binary" - }, - "p12": { - "type": "string", - "description": "The PKCS12 keystore file (required for PKCS12 type certificates)", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/auto-crop": { - "post": { - "tags": [ - "scale-pages-controller" - ], - "operationId": "cropPdf", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/add-watermark": { - "post": { - "tags": [ - "watermark-controller" - ], - "summary": "Add watermark to a PDF file", - "description": "This endpoint adds a watermark to a given PDF file. Users can specify the watermark text, font size, rotation, opacity, width spacer, and height spacer.", - "operationId": "addWatermark", - "parameters": [ - { - "name": "watermarkText", - "in": "query", - "description": "The watermark text to add to the PDF file", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "fontSize", - "in": "query", - "description": "The font size of the watermark text", - "required": false, - "schema": { - "type": "number", - "format": "float", - "default": 30.0 - }, - "example": 30 - }, - { - "name": "rotation", - "in": "query", - "description": "The rotation of the watermark text in degrees", - "required": false, - "schema": { - "type": "number", - "format": "float", - "default": 0.0 - }, - "example": 0 - }, - { - "name": "opacity", - "in": "query", - "description": "The opacity of the watermark text (0.0 - 1.0)", - "required": false, - "schema": { - "type": "number", - "format": "float", - "default": 0.5 - }, - "example": 0.5 - }, - { - "name": "widthSpacer", - "in": "query", - "description": "The width spacer between watermark texts", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50 - }, - "example": 50 - }, - { - "name": "heightSpacer", - "in": "query", - "description": "The height spacer between watermark texts", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 50 - }, - "example": 50 - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to add a watermark", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/add-password": { - "post": { - "tags": [ - "password-controller" - ], - "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.", - "operationId": "addPassword", - "parameters": [ - { - "name": "ownerPassword", - "in": "query", - "description": "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)", - "required": false, - "schema": { - "type": "string", - "default": "" - } - }, - { - "name": "password", - "in": "query", - "description": "The password to be added to the PDF file (Restricts the opening of the document itself.)", - "required": false, - "schema": { - "type": "string", - "default": "" - } - }, - { - "name": "keyLength", - "in": "query", - "description": "The length of the encryption key", - "required": false, - "schema": { - "type": "string", - "enum": [ - "40", - "128", - "256" - ] - } - }, - { - "name": "canAssembleDocument", - "in": "query", - "description": "Whether the document assembly is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canExtractContent", - "in": "query", - "description": "Whether content extraction for accessibility is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canExtractForAccessibility", - "in": "query", - "description": "Whether content extraction for accessibility is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canFillInForm", - "in": "query", - "description": "Whether form filling is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canModify", - "in": "query", - "description": "Whether the document modification is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canModifyAnnotations", - "in": "query", - "description": "Whether modification of annotations is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canPrint", - "in": "query", - "description": "Whether printing of the document is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - }, - { - "name": "canPrintFaithful", - "in": "query", - "description": "Whether faithful printing is allowed", - "required": false, - "schema": { - "type": "boolean", - "default": false - }, - "example": false - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to which the password should be added", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/add-image": { - "post": { - "tags": [ - "overlay-image-controller" - ], - "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.", - "operationId": "overlayImage", - "parameters": [ - { - "name": "x", - "in": "query", - "description": "The x-coordinate at which to place the top-left corner of the image.", - "required": true, - "schema": { - "type": "number", - "format": "float" - }, - "example": 0 - }, - { - "name": "y", - "in": "query", - "description": "The y-coordinate at which to place the top-left corner of the image.", - "required": true, - "schema": { - "type": "number", - "format": "float" - }, - "example": 0 - }, - { - "name": "everyPage", - "in": "query", - "description": "Whether to overlay the image onto every page of the PDF.", - "required": true, - "schema": { - "type": "boolean" - }, - "example": false - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "fileInput", - "fileInput2" - ], - "type": "object", - "properties": { - "fileInput": { - "type": "string", - "description": "The input PDF file to overlay the image onto.", - "format": "binary" - }, - "fileInput2": { - "type": "string", - "description": "The image file to be overlaid onto the PDF.", - "format": "binary" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "byte" - } - } - } - } - } - } - } - }, - "/api/v1/status": { - "get": { - "tags": [ - "metrics-controller" - ], - "summary": "Application status and version", - "description": "This endpoint returns the status of the application and its version number.", - "operationId": "getStatus", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - } - } - } - } - }, - "/api/v1/requests": { - "get": { - "tags": [ - "metrics-controller" - ], - "summary": "POST request count", - "description": "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.", - "operationId": "getTotalRequests", - "parameters": [ - { - "name": "endpoint", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "number", - "format": "double" - } - } - } - } - } - } - }, - "/api/v1/loads": { - "get": { - "tags": [ - "metrics-controller" - ], - "summary": "GET request count", - "description": "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.", - "operationId": "getPageLoads", - "parameters": [ - { - "name": "endpoint", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "type": "number", - "format": "double" - } - } - } - } - } - } - } - }, - "components": {} -} \ No newline at end of file From a4bc67ff8e9e27235212a5a4ad1c472214b97df1 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 23:08:10 +0100 Subject: [PATCH 05/10] Update swagger.yml --- .github/workflows/swagger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 2952c5b1..ee4c180e 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -31,6 +31,6 @@ jobs: - name: Upload Swagger Documentation to SwaggerHub run: | - curl -X POST -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @./SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" + curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @./SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" env: SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} From cefcda9f4025a8ae4d6bf72b99af65d789da6b86 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 23:21:12 +0100 Subject: [PATCH 06/10] test --- .github/workflows/swagger.yml | 7 +------ build.gradle | 12 ++++++++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 2952c5b1..4ed4f577 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -25,12 +25,7 @@ jobs: - name: Generate Swagger documentation run: ./gradlew generateOpenApiDocs - - name: Get version number - id: versionNumber - run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" - - name: Upload Swagger Documentation to SwaggerHub - run: | - curl -X POST -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @./SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" + run: ./gradlew swaggerhubUpload env: SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} diff --git a/build.gradle b/build.gradle index 478ee5b4..5e77d666 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'org.springframework.boot' version '3.1.0' id 'io.spring.dependency-management' version '1.1.0' id 'org.springdoc.openapi-gradle-plugin' version '1.6.0' + id "io.swagger.swaggerhub" version "1.1.0" } group = 'stirling.software' @@ -48,11 +49,18 @@ task writeVersion { props.store(propsFile.newWriter(), null) } -tasks.matching { it.name == 'generateOpenApiDocs' }.all { - dependsOn 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) + version project.version // The version of your API + inputFile './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 } + jar { enabled = false manifest { From 2cfb344e13efb534aabcf12f5fb6b5e3322a2cb0 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 23:24:40 +0100 Subject: [PATCH 07/10] swagger --- .github/workflows/swagger.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 1403c12f..4ed4f577 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -26,7 +26,6 @@ jobs: run: ./gradlew generateOpenApiDocs - name: Upload Swagger Documentation to SwaggerHub - run: | - curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @./SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true" + run: ./gradlew swaggerhubUpload env: SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} From b8fa278173459782f23bd4d512bb4b9d907857a4 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 3 Jun 2023 23:32:47 +0100 Subject: [PATCH 08/10] finalise auto swagger docs --- .github/workflows/swagger.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 4ed4f577..c1e6774e 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -29,3 +29,9 @@ jobs: run: ./gradlew swaggerhubUpload env: SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} + + - 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}" + env: + SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} \ No newline at end of file From 4f851156b70243ae942477000cb2630b3b83a4a0 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 4 Jun 2023 22:40:14 +0100 Subject: [PATCH 09/10] dummy lang changes --- .../controller/api/ScalePagesController.java | 312 +++++++++--------- .../api/other/FakeScanController.java | 161 +++++++++ src/main/resources/messages_ar_AR.properties | 22 ++ src/main/resources/messages_ca_CA.properties | 22 ++ src/main/resources/messages_de_DE.properties | 22 ++ src/main/resources/messages_en_GB.properties | 2 +- src/main/resources/messages_es_ES.properties | 22 ++ src/main/resources/messages_fr_FR.properties | 22 ++ src/main/resources/messages_it_IT.properties | 22 ++ src/main/resources/messages_ko_KR.properties | 23 +- src/main/resources/messages_pl_PL.properties | 3 + src/main/resources/messages_pt_BR.properties | 21 ++ src/main/resources/messages_ro_RO.properties | 22 ++ src/main/resources/messages_ru_RU.properties | 22 ++ src/main/resources/messages_sv_SE.properties | 22 ++ src/main/resources/messages_zh_CN.properties | 24 +- src/main/resources/static/js/downloader.js | 8 +- src/main/resources/static/js/fileInput.js | 4 +- .../resources/templates/fragments/common.html | 2 +- 19 files changed, 585 insertions(+), 173 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 0c079b9e..fc743d0c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -35,10 +35,12 @@ import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo; import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener; import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import stirling.software.SPDF.utils.WebResponseUtils; + @RestController public class ScalePagesController { @@ -47,48 +49,51 @@ public class ScalePagesController { @PostMapping(value = "/scale-pages", consumes = "multipart/form-data") @Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file.") public ResponseEntity scalePages( - @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, - @Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = { "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL", "EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize, - @Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor) - throws IOException { + @Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file, + @Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = { + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4", + "B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL", + "EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize, + @Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor) + throws IOException { Map sizeMap = new HashMap<>(); - // Add A0 - A10 - sizeMap.put("A0", PageSize.A0); - sizeMap.put("A1", PageSize.A1); - sizeMap.put("A2", PageSize.A2); - sizeMap.put("A3", PageSize.A3); - sizeMap.put("A4", PageSize.A4); - sizeMap.put("A5", PageSize.A5); - sizeMap.put("A6", PageSize.A6); - sizeMap.put("A7", PageSize.A7); - sizeMap.put("A8", PageSize.A8); - sizeMap.put("A9", PageSize.A9); - sizeMap.put("A10", PageSize.A10); - // Add B0 - B9 - sizeMap.put("B0", PageSize.B0); - sizeMap.put("B1", PageSize.B1); - sizeMap.put("B2", PageSize.B2); - sizeMap.put("B3", PageSize.B3); - sizeMap.put("B4", PageSize.B4); - sizeMap.put("B5", PageSize.B5); - sizeMap.put("B6", PageSize.B6); - sizeMap.put("B7", PageSize.B7); - sizeMap.put("B8", PageSize.B8); - sizeMap.put("B9", PageSize.B9); - // Add other sizes - sizeMap.put("LETTER", PageSize.LETTER); - sizeMap.put("TABLOID", PageSize.TABLOID); - sizeMap.put("LEDGER", PageSize.LEDGER); - sizeMap.put("LEGAL", PageSize.LEGAL); - sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE); - - if (!sizeMap.containsKey(targetPageSize)) { - throw new IllegalArgumentException("Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); - } - + // Add A0 - A10 + sizeMap.put("A0", PageSize.A0); + sizeMap.put("A1", PageSize.A1); + sizeMap.put("A2", PageSize.A2); + sizeMap.put("A3", PageSize.A3); + sizeMap.put("A4", PageSize.A4); + sizeMap.put("A5", PageSize.A5); + sizeMap.put("A6", PageSize.A6); + sizeMap.put("A7", PageSize.A7); + sizeMap.put("A8", PageSize.A8); + sizeMap.put("A9", PageSize.A9); + sizeMap.put("A10", PageSize.A10); + // Add B0 - B9 + sizeMap.put("B0", PageSize.B0); + sizeMap.put("B1", PageSize.B1); + sizeMap.put("B2", PageSize.B2); + sizeMap.put("B3", PageSize.B3); + sizeMap.put("B4", PageSize.B4); + sizeMap.put("B5", PageSize.B5); + sizeMap.put("B6", PageSize.B6); + sizeMap.put("B7", PageSize.B7); + sizeMap.put("B8", PageSize.B8); + sizeMap.put("B9", PageSize.B9); + // Add other sizes + sizeMap.put("LETTER", PageSize.LETTER); + sizeMap.put("TABLOID", PageSize.TABLOID); + sizeMap.put("LEDGER", PageSize.LEDGER); + sizeMap.put("LEGAL", PageSize.LEGAL); + sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE); + + if (!sizeMap.containsKey(targetPageSize)) { + throw new IllegalArgumentException( + "Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10"); + } + PageSize pageSize = sizeMap.get(targetPageSize); - byte[] bytes = file.getBytes(); PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); @@ -97,9 +102,7 @@ public class ScalePagesController { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfWriter writer = new PdfWriter(baos); PdfDocument outputPdf = new PdfDocument(writer); - - - + int totalPages = pdfDoc.getNumberOfPages(); for (int i = 1; i <= totalPages; i++) { @@ -111,7 +114,7 @@ public class ScalePagesController { float scaleWidth = pageSize.getWidth() / rect.getWidth(); float scaleHeight = pageSize.getHeight() / rect.getHeight(); float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor; - System.out.println("Scale: " + scale); + System.out.println("Scale: " + scale); PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf); float x = (pageSize.getWidth() - rect.getWidth() * scale) / 2; // Center Page @@ -128,130 +131,111 @@ public class ScalePagesController { outputPdf.close(); byte[] pdfContent = baos.toByteArray(); pdfDoc.close(); - return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); + return WebResponseUtils.bytesToWebResponse(pdfContent, + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf"); } - - - - - - - -@PostMapping(value = "/auto-crop", consumes = "multipart/form-data") -public ResponseEntity cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { - byte[] bytes = file.getBytes(); - PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); - PdfDocument pdfDoc = new PdfDocument(reader); + //TODO + @Hidden + @PostMapping(value = "/auto-crop", consumes = "multipart/form-data") + public ResponseEntity cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { + byte[] bytes = file.getBytes(); + PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes)); + PdfDocument pdfDoc = new PdfDocument(reader); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = new PdfWriter(baos); - PdfDocument outputPdf = new PdfDocument(writer); - - int totalPages = pdfDoc.getNumberOfPages(); - for (int i = 1; i <= totalPages; i++) { - PdfPage page = pdfDoc.getPage(i); - Rectangle originalMediaBox = page.getMediaBox(); - - Rectangle contentBox = determineContentBox(page); - - // Make sure we don't go outside the original media box. - Rectangle intersection = originalMediaBox.getIntersection(contentBox); - page.setCropBox(intersection); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(baos); + PdfDocument outputPdf = new PdfDocument(writer); - // Copy page to the new document - outputPdf.addPage(page.copyTo(outputPdf)); - } + int totalPages = pdfDoc.getNumberOfPages(); + for (int i = 1; i <= totalPages; i++) { + PdfPage page = pdfDoc.getPage(i); + Rectangle originalMediaBox = page.getMediaBox(); + + Rectangle contentBox = determineContentBox(page); + + // Make sure we don't go outside the original media box. + Rectangle intersection = originalMediaBox.getIntersection(contentBox); + page.setCropBox(intersection); + + // Copy page to the new document + outputPdf.addPage(page.copyTo(outputPdf)); + } + + outputPdf.close(); + byte[] pdfContent = baos.toByteArray(); + pdfDoc.close(); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"") + .contentType(MediaType.APPLICATION_PDF).body(pdfContent); + } + + private Rectangle determineContentBox(PdfPage page) { + // Extract the text from the page and find the bounding box. + TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder(); + PdfCanvasProcessor processor = new PdfCanvasProcessor(finder); + processor.processPageContent(page); + return finder.getBoundingBox(); + } + + private static class TextBoundingRectangleFinder implements IEventListener { + private List allTextBoxes = new ArrayList<>(); + + public Rectangle getBoundingBox() { + // Sort the text boxes based on their vertical position + allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop)); + + // Consider a box an outlier if its top is more than 1.5 times the IQR above the + // third quartile. + int q1Index = allTextBoxes.size() / 4; + int q3Index = 3 * allTextBoxes.size() / 4; + double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop(); + double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr; + + // Initialize boundingBox to the first non-outlier box + int i = 0; + while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) { + i++; + } + if (i == allTextBoxes.size()) { + // If all boxes are outliers, just return the first one + return allTextBoxes.get(0); + } + Rectangle boundingBox = allTextBoxes.get(i); + + // Extend the bounding box to include all non-outlier boxes + for (; i < allTextBoxes.size(); i++) { + Rectangle textBoundingBox = allTextBoxes.get(i); + if (textBoundingBox.getTop() > threshold) { + // This box is an outlier, skip it + continue; + } + float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft()); + float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom()); + float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight()); + float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop()); + + // Add a small padding around the bounding box + float padding = 10; + boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding, + top - bottom + 2 * padding); + } + return boundingBox; + } + + @Override + public void eventOccurred(IEventData data, EventType type) { + if (type == EventType.RENDER_TEXT) { + TextRenderInfo renderInfo = (TextRenderInfo) data; + allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle()); + } + } + + @Override + public Set getSupportedEvents() { + return Collections.singleton(EventType.RENDER_TEXT); + } + } - outputPdf.close(); - byte[] pdfContent = baos.toByteArray(); - pdfDoc.close(); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"") - .contentType(MediaType.APPLICATION_PDF) - .body(pdfContent); -} - -private Rectangle determineContentBox(PdfPage page) { - // Extract the text from the page and find the bounding box. - TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder(); - PdfCanvasProcessor processor = new PdfCanvasProcessor(finder); - processor.processPageContent(page); - return finder.getBoundingBox(); -} -private static class TextBoundingRectangleFinder implements IEventListener { - private List allTextBoxes = new ArrayList<>(); - - public Rectangle getBoundingBox() { - // Sort the text boxes based on their vertical position - allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop)); - - // Consider a box an outlier if its top is more than 1.5 times the IQR above the third quartile. - int q1Index = allTextBoxes.size() / 4; - int q3Index = 3 * allTextBoxes.size() / 4; - double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop(); - double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr; - - // Initialize boundingBox to the first non-outlier box - int i = 0; - while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) { - i++; - } - if (i == allTextBoxes.size()) { - // If all boxes are outliers, just return the first one - return allTextBoxes.get(0); - } - Rectangle boundingBox = allTextBoxes.get(i); - - // Extend the bounding box to include all non-outlier boxes - for (; i < allTextBoxes.size(); i++) { - Rectangle textBoundingBox = allTextBoxes.get(i); - if (textBoundingBox.getTop() > threshold) { - // This box is an outlier, skip it - continue; - } - float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft()); - float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom()); - float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight()); - float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop()); - - // Add a small padding around the bounding box - float padding = 10; - boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding, top - bottom + 2 * padding); - } - return boundingBox; - } - - @Override - public void eventOccurred(IEventData data, EventType type) { - if (type == EventType.RENDER_TEXT) { - TextRenderInfo renderInfo = (TextRenderInfo) data; - allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle()); - } - } - - @Override - public Set getSupportedEvents() { - return Collections.singleton(EventType.RENDER_TEXT); - } -} - - - - - - - - - - - - - - - - - - - } diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java b/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java new file mode 100644 index 00000000..a4876ad0 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/other/FakeScanController.java @@ -0,0 +1,161 @@ +package stirling.software.SPDF.controller.api.other; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.itextpdf.io.source.ByteArrayOutputStream; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.WebResponseUtils; +//Required for PDF manipulation +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.rendering.ImageType; +import org.apache.pdfbox.rendering.PDFRenderer; + + +//Required for image manipulation +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.RescaleOp; +import java.awt.image.AffineTransformOp; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.awt.Color; +import java.awt.geom.AffineTransform; + +//Required for image input/output +import javax.imageio.ImageIO; + +//Required for file input/output +import java.io.File; + +//Other required classes +import java.util.Random; + +import io.swagger.v3.oas.annotations.Hidden; + +@RestController +public class FakeScanController { + + private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class); + + //TODO + @Hidden + @PostMapping(consumes = "multipart/form-data", value = "/fakeScan") + @Operation( + summary = "Repair a PDF file", + description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response." + ) + public ResponseEntity repairPdf( + @RequestPart(required = true, value = "fileInput") + @Parameter(description = "The input PDF file to be repaired", required = true) + MultipartFile inputFile) throws IOException, InterruptedException { + + PDDocument document = PDDocument.load(inputFile.getBytes()); + PDFRenderer pdfRenderer = new PDFRenderer(document); + for (int page = 0; page < document.getNumberOfPages(); ++page) + { + BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); + ImageIO.write(image, "png", new File("scanned-" + (page+1) + ".png")); + } + document.close(); + + // Constants + int scannedness = 90; // Value between 0 and 100 + int dirtiness = 0; // Value between 0 and 100 + + // Load the source image + BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png")); + + // Create the destination image + BufferedImage destinationImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType()); + + // Apply a brightness and contrast effect based on the "scanned-ness" + float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5 + float offset = scannedness * 1.5f; // Between 0 and 150 + BufferedImageOp op = new RescaleOp(scaleFactor, offset, null); + op.filter(sourceImage, destinationImage); + + // Apply a rotation effect + double rotationRequired = Math.toRadians((new Random().nextInt(3 - 1) + 1)); // Random angle between 1 and 3 degrees + double locationX = destinationImage.getWidth() / 2; + double locationY = destinationImage.getHeight() / 2; + AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY); + AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR); + destinationImage = rotateOp.filter(destinationImage, null); + + // Apply a blur effect based on the "scanned-ness" + float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2 + float[] matrix = { + blurIntensity, blurIntensity, blurIntensity, + blurIntensity, blurIntensity, blurIntensity, + blurIntensity, blurIntensity, blurIntensity + }; + BufferedImageOp blurOp = new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null); + destinationImage = blurOp.filter(destinationImage, null); + + // Add noise to the image based on the "dirtiness" + Random random = new Random(); + for (int y = 0; y < destinationImage.getHeight(); y++) { + for (int x = 0; x < destinationImage.getWidth(); x++) { + if (random.nextInt(100) < dirtiness) { + // Change the pixel color to black randomly based on the "dirtiness" + destinationImage.setRGB(x, y, Color.BLACK.getRGB()); + } + } + } + + // Save the image + ImageIO.write(destinationImage, "PNG", new File("scanned-1.png")); + + + + + + + + PDDocument documentOut = new PDDocument(); + for (int page = 1; page <= document.getNumberOfPages(); ++page) + { + BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png")); + + // Adjust the dimensions of the page + PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1)); + documentOut.addPage(pdPage); + + PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim); + PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage); + + // Draw the image with a slight offset and enlarged dimensions + contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2); + contentStream.close(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + documentOut.save(baos); + documentOut.close(); + + // Return the optimized PDF as a response + String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf"; + return WebResponseUtils.boasToWebResponse(baos, outputFilename); + } + +} diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 8db6cc95..ef3847a3 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -132,12 +132,31 @@ home.removeBlanks.desc = يكتشف ويزيل الصفحات الفارغة م home.compare.title = قارن home.compare.desc = يقارن ويظهر الاختلافات بين 2 من مستندات PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf = تنزيل PDF text=نص font=الخط selectFillter = - حدد - pageNum = رقم الصفحة +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title = توقيع الشهادة certSign.header = قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم) certSign.selectPDF = حدد ملف PDF للتوقيع: @@ -339,6 +358,9 @@ addPassword.selectText.10=منع التعديل addPassword.selectText.11=منع تعديل التعليقات التوضيحية addPassword.selectText.12=منع الطباعة addPassword.selectText.13=منع طباعة تنسيقات مختلفة +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=تشفير #watermark diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 119ed479..d613629e 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -125,12 +125,31 @@ home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document home.compare.title=Compara home.compare.desc=Compara i mostra les diferències entre 2 documents PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Descarregueu PDF text=Text font=Tipus de lletra selectFillter=-- Selecciona -- pageNum=Número de pàgina +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Significació del certificat certSign.header=Firmar un PDF amb el vostre certificat (Treball en curs) certSign.selectPDF=Seleccioneu un fitxer PDF per signar: @@ -337,6 +356,9 @@ addPassword.selectText.10=Evita modificacions addPassword.selectText.11=Evita modificacions d'annotacions addPassword.selectText.12=Evita impressió addPassword.selectText.13=Evita impressió de diferents formats +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Encripta #watermark diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 7abe0546..467c8692 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -124,12 +124,31 @@ home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument home.compare.title=Vergleichen home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=PDF herunterladen text=Text font=Schriftart selectFillter=-- Auswählen -- pageNum=Seitenzahl +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Zertifikatsignierung certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit) certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus: @@ -334,6 +353,9 @@ addPassword.selectText.10=Modifizierung verhindern addPassword.selectText.11=Ändern von Kommentaren verhindern addPassword.selectText.12=Drucken verhindern addPassword.selectText.13=Drucken verschiedener Formate verhindern +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Verschlüsseln #watermark diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index e38bb558..c8f1ae13 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -129,7 +129,7 @@ home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page home.scalePages.title=Adjust page size/scale -home.scalePages.desc=Change the size/scale of page and/or its contents. +home.scalePages.desc=Change the size/scale of a page and/or its contents. error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 524283a5..d42611c2 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -124,12 +124,31 @@ home.removeBlanks.descdetecta y elimina páginas en blanco de un documento home.compare.title=Comparar home.compare.desc=Compara y muestra las diferencias entre 2 documentos PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Descargar PDF text=Texto font=Fuente selectFilter=-- Seleccionar -- pageNum=Número de página +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Firma de certificado certSign.header=Firme un PDF con su certificado (Trabajo en progreso) certSign.selectPDF=Seleccione un archivo PDF para firmar: @@ -335,6 +354,9 @@ addPassword.selectText.10=Impedir modificación addPassword.selectText.11=Impedir modificación de anotaciones addPassword.selectText.12=Impedir imprimir addPassword.selectText.13=Impedir imprimir diferentes formatos +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Encriptar #watermark diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 4038c78b..588b62f7 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -130,12 +130,31 @@ home.removeBlanks.desc=Détecte et supprime les pages vierges d'un document home.compare.title=Comparer home.compare.desc=Compare et affiche les différences entre 2 documents PDF +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Télécharger le PDF text=Texte font=Police selectFilter=-- Sélectionner -- pageNum=numéro de page +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Signature du certificat certSign.header=Signer un PDF avec votre certificat (Travail en cours) certSign.selectPDF=Sélectionnez un fichier PDF à signer : @@ -334,6 +353,9 @@ addPassword.selectText.10=Empêcher la modification addPassword.selectText.11=Empêcher la modification des annotations addPassword.selectText.12=Empêcher l'impression addPassword.selectText.13=Empêcher l'impression de différents formats +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Crypter #watermark diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index c888ab68..3ddbc85b 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -125,12 +125,31 @@ home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF. home.compare.title=Compara home.compare.desc=Vedi e compara le differenze tra due PDF. +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Scarica PDF text=Testo font=Font selectFillter=-- Seleziona -- pageNum=Numero pagina +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Firma del certificato certSign.header=Firma un PDF con il tuo certificato (Lavoro in corso) certSign.selectPDF=Seleziona un file PDF per la firma: @@ -337,6 +356,9 @@ addPassword.selectText.10=Previeni modifiche addPassword.selectText.11=Previeni annotazioni addPassword.selectText.12=Previeni stampa addPassword.selectText.13=Previeni stampa in diversi formati +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Crittografa #watermark diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 5e34defa..b978ab2d 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -128,6 +128,13 @@ home.compare.desc=2개의 PDF 문서를 비교하고 차이를 표시합니다. home.certSign.title=인증서로 서명 home.certSign.desc=PDF에 인증서/키로 서명합니다. (PEM/P12) +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect downloadPdf=PDF 다운로드 text=텍스트 @@ -135,6 +142,17 @@ font=폰트 selectFillter=-- 선택 -- pageNum=페이지 번호 +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=인증서로 서명 certSign.header=PDF에 당신의 인증서로 서명하세요 (개발 중) certSign.selectPDF=서명할 PDF를 선택하세요: @@ -368,7 +386,10 @@ addPassword.selectText.9=양식 작성 방지 addPassword.selectText.10=수정 방지 addPassword.selectText.11=주석 수정 방지 addPassword.selectText.12=인쇄 방지 -addPassword.selectText.13=다른 형식으로 인쇄 방지 +addPassword.selectText.13=다른 형식으로 인쇄 방 +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=암호화 #watermark diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 2cca082f..19244f53 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -359,6 +359,9 @@ addPassword.selectText.10=Zablokuj modyfikacje addPassword.selectText.11=Zablokuj modyfikacje adnotacji addPassword.selectText.12=Zablokuj drukowanie addPassword.selectText.13=Zablokuj drukowanie różnych formatów +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Zablokuj #watermark diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 822b313e..c09b4ab3 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -128,6 +128,13 @@ home.compare.desc=Compara e mostra as diferenças entre 2 documentos PDF home.certSign.title=Assinar com certificado home.certSign.desc=Assina um PDF com um Certificado/Chave (PEM/P12) +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect downloadPdf=baixar PDF text=Texto @@ -135,6 +142,17 @@ font=Fonte selectFillter=-- Selecione -- pageNum=Número de página +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Assinatura de certificado certSign.header=Assine um PDF com seu certificado (Trabalho em andamento) certSign.selectPDF=Selecione um arquivo PDF para assinatura: @@ -345,6 +363,9 @@ addPassword.selectText.10=Impedir modificação addPassword.selectText.11=Impedir a modificação da anotação addPassword.selectText.12=Impedir a impressão addPassword.selectText.13=Impedir a impressão de formatos diferentes +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=criptografar #watermark diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index ffa6bd89..3ea7829a 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -109,12 +109,31 @@ home.compare.desc=Compară și arată diferențele dintre 2 documente PDF. home.certSign.title=Semnare cu certificat home.certSign.desc=Semnează un PDF cu un certificat/cheie (PEM/P12) +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Descarcă PDF text=Text font=Font selectFillter=-- Selectează -- pageNum=Numărul paginii +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title = Semnare certificat certSign.header = Semnează un fișier PDF cu certificatul tău (În curs de desfășurare) certSign.selectPDF = Selectează un fișier PDF pentru semnare: @@ -316,6 +335,9 @@ addPassword.selectText.10=Preveniți modificarea addPassword.selectText.11=Preveniți modificarea adnotărilor addPassword.selectText.12=Preveniți tipărirea addPassword.selectText.13=Preveniți tipărirea în formate diferite +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Criptare #watermark diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index b4d87bde..b163b640 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -125,12 +125,31 @@ home.removeBlanks.desc=Обнаруживает и удаляет пустые home.compare.title=Сравнение home.compare.desc=Сравнивает и показывает различия между двумя PDF-документами +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Скачать PDF text=Текст font=Шрифт selectFillter=-- Выбрать -- pageNum=номер страницы +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Подписание сертификата certSign.header=Подпишите PDF своим сертификатом (работа в процессе) certSign.selectPDF=Выберите файл PDF для подписи: @@ -338,6 +357,9 @@ addPassword.selectText.10=Предотвратить модификацию addPassword.selectText.11=Запретить модификацию аннотаций addPassword.selectText.12=Запретить печать addPassword.selectText.13=Запретить печать разных форматов +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Шифровать #watermark diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 38b300ee..9cc30a6f 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -125,12 +125,31 @@ home.removeBlanks.desc=Känner av och tar bort tomma sidor från ett dokument home.compare.title=Jämför home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=Ladda ner PDF text=Text font=Teckensnitt selectFillter=-- Välj -- pageNum=Sidnummer +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=Certifikatsignering certSign.header=Skriv under en PDF med ditt certifikat (Pågående arbete) certSign.selectPDF=Välj en PDF-fil för signering: @@ -338,6 +357,9 @@ addPassword.selectText.10=Förhindra modifiering addPassword.selectText.11=Förhindra anteckningsändring addPassword.selectText.12=Förhindra utskrift addPassword.selectText.13=Förhindra utskrift av olika format +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=Kryptera #vattenstämpel diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index b2e6ec9f..a33badea 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -125,12 +125,31 @@ home.removeBlanks.desc=\u68C0\u6D4B\u5E76\u5220\u9664\u6587\u6863\u4E2D\u7684\u7 home.compare.title=\u6BD4\u8F83 home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B\u95F4\u7684\u5DEE\u5F02 +home.pageLayout.title=Multi-Page Layout +home.pageLayout.desc=Merge multiple pages of a PDF document into a single page + +home.scalePages.title=Adjust page size/scale +home.scalePages.desc=Change the size/scale of page and/or its contents. + +error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect + downloadPdf=\u4E0B\u8F7DPDF text=\u6587\u672C font=\u5B57\u4F53 selectFillter=-- 选择-- pageNum=页码 +pageLayout.title=Multi Page Layout +pageLayout.header=Multi Page Layout +pageLayout.pagesPerSheet=Pages per sheet: +pageLayout.submit=Submit + +scalePages.title=Adjust page-scale +scalePages.header=Adjust page-scale +scalePages.pageSize=Size of a page of the document. +scalePages.scaleFactor=Zoom level (crop) of a page. +scalePages.submit=Submit + certSign.title=证书签名 certSign.header=使用您的证书签署 PDF(进行中) certSign.selectPDF=选择要签名的 PDF 文件: @@ -336,7 +355,10 @@ addPassword.selectText.9=防止填写表格 addPassword.selectText.10=防止修改 addPassword.selectText.11=防止修改注释 addPassword.selectText.12=防止打印 -addPassword.selectText.13=防止打印不同的格式 +addPassword.selectText.13=防止打印不同的格 +addPassword.selectText.14=Owner Password +addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers) +addPassword.selectText.16=Restricts the opening of the document itself addPassword.submit=加密 #watermark diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index bfc0860a..17cca631 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -5,11 +5,12 @@ function showErrorBanner(message, stackTrace) { document.querySelector("#errorContainer p").textContent = message; document.querySelector("#traceContent").textContent = stackTrace; } +let firstErrorOccurred = false; $(document).ready(function() { $('form').submit(async function(event) { event.preventDefault(); - + firstErrorOccurred = false; const url = this.action; const files = $('#fileInput-input')[0].files; const formData = new FormData(this); @@ -83,7 +84,10 @@ async function handleJsonResponse(response) { const json = await response.json(); const errorMessage = JSON.stringify(json, null, 2); if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) { - alert(pdfPasswordPrompt); + if (!firstErrorOccurred) { + firstErrorOccurred = true; + alert(pdfPasswordPrompt); + } } else { showErrorBanner(json.error + ':' + json.message, json.trace); } diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 16111d24..87454277 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -1,6 +1,5 @@ document.addEventListener('DOMContentLoaded', function() { const fileInput = document.getElementById(elementID); - // Prevent default behavior for drag events ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { fileInput.addEventListener(eventName, preventDefaults, false); @@ -22,7 +21,7 @@ document.addEventListener('DOMContentLoaded', function() { } }); -$(elementID).on("change", function() { +$("#"+elementID).on("change", function() { handleFileInputChange(this); }); @@ -34,7 +33,6 @@ function handleFileInputChange(inputElement) { fileNames.forEach(fileName => { selectedFilesContainer.append("
" + fileName + "
"); }); - console.log("fileNames.length=" + fileNames.length) if (fileNames.length === 1) { $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]); } else if (fileNames.length > 1) { diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index 75870630..92f6f84a 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -92,7 +92,7 @@ From 5a9165d7c6a877a0e8761846eebb5cdf4214a619 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Mon, 5 Jun 2023 18:23:15 +0100 Subject: [PATCH 10/10] missing lang --- src/main/resources/messages_ar_AR.properties | 3 +++ src/main/resources/messages_ca_CA.properties | 3 +++ src/main/resources/messages_de_DE.properties | 3 +++ src/main/resources/messages_es_ES.properties | 3 +++ src/main/resources/messages_fr_FR.properties | 3 +++ src/main/resources/messages_it_IT.properties | 3 +++ src/main/resources/messages_ru_RU.properties | 3 +++ src/main/resources/messages_sv_SE.properties | 3 +++ src/main/resources/messages_zh_CN.properties | 3 +++ 9 files changed, 27 insertions(+) diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index ef3847a3..1b52ba8a 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -129,6 +129,9 @@ home.repair.desc = يحاول إصلاح ملف PDF تالف / معطل home.removeBlanks.title = إزالة الصفحات الفارغة home.removeBlanks.desc = يكتشف ويزيل الصفحات الفارغة من المستند +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title = قارن home.compare.desc = يقارن ويظهر الاختلافات بين 2 من مستندات PDF diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index d613629e..0d785b2a 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -122,6 +122,9 @@ home.repair.desc=Intenta reparar un PDF danyat o trencat home.removeBlanks.title=Elimina les pàgines en blanc home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Compara home.compare.desc=Compara i mostra les diferències entre 2 documents PDF diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 467c8692..47872c91 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -121,6 +121,9 @@ home.repair.desc=Versucht, ein beschädigtes/kaputtes PDF zu reparieren home.removeBlanks.title=Leere Seiten entfernen home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Vergleichen home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index d42611c2..dc639f71 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -121,6 +121,9 @@ home.repair.desc=Intenta reparar un PDF corrupto/roto home.removeBlanks.title=Eliminar páginas en blanco home.removeBlanks.descdetecta y elimina páginas en blanco de un documento +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Comparar home.compare.desc=Compara y muestra las diferencias entre 2 documentos PDF diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 588b62f7..fdc3a81a 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -127,6 +127,9 @@ home.repair.desc=Essaye de réparer un PDF corrompu/cassé home.removeBlanks.title=Supprimer les pages vierges home.removeBlanks.desc=Détecte et supprime les pages vierges d'un document +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Comparer home.compare.desc=Compare et affiche les différences entre 2 documents PDF diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index 3ddbc85b..7512a29b 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -122,6 +122,9 @@ home.repair.desc=Prova a riparare un PDF corrotto. home.removeBlanks.title=Rimuovi pagine vuote home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF. +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.compare.title=Compara home.compare.desc=Vedi e compara le differenze tra due PDF. diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index b163b640..c7617c45 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -125,6 +125,9 @@ home.removeBlanks.desc=Обнаруживает и удаляет пустые home.compare.title=Сравнение home.compare.desc=Сравнивает и показывает различия между двумя PDF-документами +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 9cc30a6f..224fc41b 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -125,6 +125,9 @@ home.removeBlanks.desc=Känner av och tar bort tomma sidor från ett dokument home.compare.title=Jämför home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index a33badea..6ae7df00 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -125,6 +125,9 @@ home.removeBlanks.desc=\u68C0\u6D4B\u5E76\u5220\u9664\u6587\u6863\u4E2D\u7684\u7 home.compare.title=\u6BD4\u8F83 home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B\u95F4\u7684\u5DEE\u5F02 +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + home.pageLayout.title=Multi-Page Layout home.pageLayout.desc=Merge multiple pages of a PDF document into a single page