From 87cd6dfb54efaa6b842bb71916be937652c713ce Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 19 May 2023 20:43:30 +0100 Subject: [PATCH 1/7] compress --- .github/workflows/push-docker.yml | 37 ++++- .../api/other/CompressController.java | 157 +++++++++++++----- .../templates/other/compress-pdf.html | 39 ++--- 3 files changed, 166 insertions(+), 67 deletions(-) diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index 1ffb470e..f76434a8 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -50,13 +50,13 @@ jobs: id: meta uses: docker/metadata-action@v4.4.0 with: - images: | - ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf - ghcr.io/${{ github.repository_owner }}/s-pdf - tags: | - ${{ steps.versionNumber.outputs.versionNumber }} - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} - type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }} + images: | + ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf + ghcr.io/${{ github.repository_owner }}/s-pdf + tags: | + ${{ steps.versionNumber.outputs.versionNumber }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} + type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }} - name: Set up QEMU uses: docker/setup-qemu-action@v2.1.0 @@ -76,6 +76,22 @@ jobs: labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64/v8 + - name: Generate tags + id: meta2 + uses: docker/metadata-action@v4.4.0 + with: + images: | + ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf + ghcr.io/${{ github.repository_owner }}/s-pdf + tags: | + ${{ steps.versionNumber.outputs.versionNumber }}-ultra-light + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} + type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Convert repository owner to lowercase + id: repoowner + run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" + - name: Build and push Dockerfile-ultralite uses: docker/build-push-action@v4.0.0 with: @@ -84,12 +100,15 @@ jobs: push: true cache-from: type=gha cache-to: type=gha,mode=max + tags: ${{ steps.meta2.outputs.tags }} + labels: ${{ steps.meta2.outputs.labels }} tags: | ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf:ultra-light-latest - ghcr.io/${{ github.repository_owner }}/s-pdf:ultra-light-latest + ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf:ultra-light-latest labels: | - ${{ steps.meta.outputs.labels }} + ${{ steps.meta2.outputs.labels }} type=raw,value=ultra-light-latest,enable=${{ github.ref == 'refs/heads/master' }} type=raw,value=ultra-light-alpha,enable=${{ github.ref == 'refs/heads/main' }} platforms: linux/amd64,linux/arm64/v8 + diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index 89f00981..009cd6e9 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -1,11 +1,27 @@ package stirling.software.SPDF.controller.api.other; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; + +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.graphics.PDXObject; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; + import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import javax.imageio.ImageIO; +import javax.imageio.stream.MemoryCacheImageOutputStream; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -20,31 +36,20 @@ import io.swagger.v3.oas.annotations.Parameter; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; import io.swagger.v3.oas.annotations.media.Schema; + @RestController public class CompressController { private static final Logger logger = LoggerFactory.getLogger(CompressController.class); @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") - @Operation( - summary = "Optimize PDF file", - description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters." - ) + @Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters.") public ResponseEntity optimizePdf( - @RequestPart(required = true, value = "fileInput") - @Parameter(description = "The input PDF file to be optimized.", required = true) - MultipartFile inputFile, - @RequestParam("optimizeLevel") - @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", - schema = @Schema(allowableValues = {"0", "1", "2", "3"}), example = "1") - int optimizeLevel, - @RequestParam(name = "fastWebView", required = false) - @Parameter(description = "If true, optimize the PDF for fast web view. This increases the file size by about 25%.", example = "false") - Boolean fastWebView, - @RequestParam(name = "jbig2Lossy", required = false) - @Parameter(description = "If true, apply lossy JB2 compression to the PDF file.", example = "false") - Boolean jbig2Lossy) - throws IOException, InterruptedException { + @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile, + @RequestParam("optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = { + "0", "1", "2", "3" }), example = "1") int optimizeLevel, + @RequestParam("expectedOutputSize") @Parameter(description = "The expected output size in bytes.", required = false) Long expectedOutputSize) + throws IOException, InterruptedException { // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); @@ -53,31 +58,109 @@ public class CompressController { // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Prepare the OCRmyPDF command + // Prepare the Ghostscript command List command = new ArrayList<>(); - command.add("ocrmypdf"); - command.add("--skip-text"); - command.add("--tesseract-timeout=0"); - command.add("--optimize"); - command.add(String.valueOf(optimizeLevel)); - command.add("--output-type"); - command.add("pdf"); + command.add("gs"); + command.add("-sDEVICE=pdfwrite"); + command.add("-dCompatibilityLevel=1.4"); - if (fastWebView != null && fastWebView) { - long fileSize = inputFile.getSize(); - long fastWebViewSize = (long) (fileSize * 1.25); // 25% higher than file size - command.add("--fast-web-view"); - command.add(String.valueOf(fastWebViewSize)); - } - - if (jbig2Lossy != null && jbig2Lossy) { - command.add("--jbig2-lossy"); + switch (optimizeLevel) { + case 0: + command.add("-dPDFSETTINGS=/default"); + break; + case 1: + command.add("-dPDFSETTINGS=/ebook"); + break; + case 2: + command.add("-dPDFSETTINGS=/printer"); + break; + case 3: + command.add("-dPDFSETTINGS=/prepress"); + break; + default: + command.add("-dPDFSETTINGS=/default"); } + command.add("-dNOPAUSE"); + command.add("-dQUIET"); + command.add("-dBATCH"); + command.add("-sOutputFile=" + tempOutputFile.toString()); command.add(tempInputFile.toString()); - command.add(tempOutputFile.toString()); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + + if (expectedOutputSize != null) { + long outputFileSize = Files.size(tempOutputFile); + if (outputFileSize > expectedOutputSize) { + try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) { + + double scaleFactor = 1.0; + while (true) { + for (PDPage page : doc.getPages()) { + PDResources res = page.getResources(); + + for (COSName name : res.getXObjectNames()) { + PDXObject xobj = res.getXObject(name); + if (xobj instanceof PDImageXObject) { + PDImageXObject image = (PDImageXObject) xobj; + + // Get the image in BufferedImage format + BufferedImage bufferedImage = image.getImage(); + + // Calculate the new dimensions + int newWidth = (int)(bufferedImage.getWidth() * scaleFactor); + int newHeight = (int)(bufferedImage.getHeight() * scaleFactor); + + // If the new dimensions are zero, skip this iteration + if (newWidth == 0 || newHeight == 0) { + continue; + } + + // Otherwise, proceed with the scaling + Image scaledImage = bufferedImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + + // Convert the scaled image back to a BufferedImage + BufferedImage scaledBufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); + scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null); + + // Compress the scaled image + ByteArrayOutputStream compressedImageStream = new ByteArrayOutputStream(); + ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream); + byte[] imageBytes = compressedImageStream.toByteArray(); + compressedImageStream.close(); + + // Convert compressed image back to PDImageXObject + ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes); + PDImageXObject compressedImage = PDImageXObject.createFromByteArray(doc, imageBytes, image.getCOSObject().toString()); + + // Replace the image in the resources with the compressed version + res.put(name, compressedImage); + } + } + } + + // save the document to tempOutputFile again + doc.save(tempOutputFile.toString()); + + // Check if the overall PDF size is still larger than expectedOutputSize + if (Files.size(tempOutputFile) > expectedOutputSize) { + // The file is still too large, reduce scaleFactor and try again + scaleFactor *= 0.9; // reduce scaleFactor by 10% + // Avoid scaleFactor being too small, causing the image to shrink to 0 + if(scaleFactor < 0.1){ + throw new RuntimeException("Could not reach the desired size without excessively degrading image quality"); + } + } else { + // The file is small enough, break the loop + break; + } + } + + } + + + } + } // Read the optimized PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); diff --git a/src/main/resources/templates/other/compress-pdf.html b/src/main/resources/templates/other/compress-pdf.html index f173dba4..a082e622 100644 --- a/src/main/resources/templates/other/compress-pdf.html +++ b/src/main/resources/templates/other/compress-pdf.html @@ -11,31 +11,28 @@


-
+
R

-
-
- - -
-
- - -
-
- - -
- -
+
+
+ + +
+
+ + +
+ + +

From 9d80458250d410748c81f2520b01ca032ee2350e Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 19 May 2023 23:58:54 +0100 Subject: [PATCH 2/7] as --- DockerfileBase | 27 +--- .../api/other/CompressController.java | 124 ++++++++++++------ .../software/SPDF/utils/PdfUtils.java | 27 ++++ src/main/resources/messages_en_GB.properties | 7 +- src/main/resources/static/css/dark-mode.css | 5 + src/main/resources/templates/home.html | 5 + .../templates/other/compress-pdf.html | 33 +++-- 7 files changed, 145 insertions(+), 83 deletions(-) diff --git a/DockerfileBase b/DockerfileBase index 876b5a10..d43f0e65 100644 --- a/DockerfileBase +++ b/DockerfileBase @@ -1,27 +1,3 @@ -# Build jbig2enc in a separate stage -FROM debian:bullseye-slim as jbig2enc_builder - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - git \ - automake \ - autoconf \ - libtool \ - libleptonica-dev \ - pkg-config \ - ca-certificates \ - zlib1g-dev \ - make \ - g++ - -RUN git clone https://github.com/agl/jbig2enc && \ - cd jbig2enc && \ - ./autogen.sh && \ - ./configure && \ - make && \ - make install - - # Main stage FROM openjdk:17-jdk-slim AS base RUN apt-get update && \ @@ -58,5 +34,4 @@ RUN apt-get update && \ # Final stage: Copy necessary files from the previous stage FROM base -COPY --from=python-packages /usr/local /usr/local -COPY --from=jbig2enc_builder /usr/local/bin/jbig2 /usr/local/bin/jbig2 \ No newline at end of file +COPY --from=python-packages /usr/local /usr/local \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index 009cd6e9..2671e1b5 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -5,8 +5,8 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; +import org.apache.commons.io.FileUtils; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -45,55 +45,98 @@ public class CompressController { @PostMapping(consumes = "multipart/form-data", value = "/compress-pdf") @Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters.") public ResponseEntity optimizePdf( - @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile, - @RequestParam("optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = { - "0", "1", "2", "3" }), example = "1") int optimizeLevel, - @RequestParam("expectedOutputSize") @Parameter(description = "The expected output size in bytes.", required = false) Long expectedOutputSize) - throws IOException, InterruptedException { + @RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile, + @RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = { + "1", "2", "3", "4", "5" })) Integer optimizeLevel, + @RequestParam(value = "expectedOutputSize", required = false) @Parameter(description = "The expected output size, e.g. '100MB', '25KB', etc.", required = false) String expectedOutputSizeString) + throws Exception { + + if(expectedOutputSizeString == null && optimizeLevel == null) { + throw new Exception("Both expected output size and optimize level are not specified"); + } + + Long expectedOutputSize = 0L; + if (expectedOutputSizeString != null) { + expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString); + } // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); inputFile.transferTo(tempInputFile.toFile()); + long inputFileSize = Files.size(tempInputFile); + // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Prepare the Ghostscript command - List command = new ArrayList<>(); - command.add("gs"); - command.add("-sDEVICE=pdfwrite"); - command.add("-dCompatibilityLevel=1.4"); - - switch (optimizeLevel) { - case 0: - command.add("-dPDFSETTINGS=/default"); - break; - case 1: - command.add("-dPDFSETTINGS=/ebook"); - break; - case 2: - command.add("-dPDFSETTINGS=/printer"); - break; - case 3: - command.add("-dPDFSETTINGS=/prepress"); - break; - default: - command.add("-dPDFSETTINGS=/default"); + // Determine initial optimization level based on expected size reduction, only if optimizeLevel is not provided + if(optimizeLevel == null) { + double sizeReductionRatio = expectedOutputSize / (double) inputFileSize; + if (sizeReductionRatio > 0.7) { + optimizeLevel = 1; + } else if (sizeReductionRatio > 0.5) { + optimizeLevel = 2; + } else if (sizeReductionRatio > 0.35) { + optimizeLevel = 3; + } else { + optimizeLevel = 4; + } } - command.add("-dNOPAUSE"); - command.add("-dQUIET"); - command.add("-dBATCH"); - command.add("-sOutputFile=" + tempOutputFile.toString()); - command.add(tempInputFile.toString()); + boolean sizeMet = expectedOutputSize == 0L; + while (!sizeMet && optimizeLevel <= 5) { + // Prepare the Ghostscript command + List command = new ArrayList<>(); + command.add("gs"); + command.add("-sDEVICE=pdfwrite"); + command.add("-dCompatibilityLevel=1.4"); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + switch (optimizeLevel) { + case 1: + command.add("-dPDFSETTINGS=/prepress"); + break; + case 2: + command.add("-dPDFSETTINGS=/printer"); + break; + case 3: + command.add("-dPDFSETTINGS=/default"); + break; + case 4: + command.add("-dPDFSETTINGS=/ebook"); + break; + case 5: + command.add("-dPDFSETTINGS=/screen"); + break; + default: + command.add("-dPDFSETTINGS=/default"); + } + + command.add("-dNOPAUSE"); + command.add("-dQUIET"); + command.add("-dBATCH"); + command.add("-sOutputFile=" + tempOutputFile.toString()); + command.add(tempInputFile.toString()); + + int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + + // Check if file size is within expected size + long outputFileSize = Files.size(tempOutputFile); + if (outputFileSize <= expectedOutputSize) { + sizeMet = true; + } else { + // Increase optimization level for next iteration + optimizeLevel++; + System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); + } + } + + if (expectedOutputSize != null) { long outputFileSize = Files.size(tempOutputFile); if (outputFileSize > expectedOutputSize) { try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) { - + long previousFileSize = 0; double scaleFactor = 1.0; while (true) { for (PDPage page : doc.getPages()) { @@ -142,14 +185,21 @@ public class CompressController { // save the document to tempOutputFile again doc.save(tempOutputFile.toString()); + long currentSize = Files.size(tempOutputFile); // Check if the overall PDF size is still larger than expectedOutputSize - if (Files.size(tempOutputFile) > expectedOutputSize) { + if (currentSize > expectedOutputSize) { + // Log the current file size and scaleFactor + + System.out.println("Current file size: " + FileUtils.byteCountToDisplaySize(currentSize)); + System.out.println("Current scale factor: " + scaleFactor); + // The file is still too large, reduce scaleFactor and try again scaleFactor *= 0.9; // reduce scaleFactor by 10% // Avoid scaleFactor being too small, causing the image to shrink to 0 - if(scaleFactor < 0.1){ - throw new RuntimeException("Could not reach the desired size without excessively degrading image quality"); + if(scaleFactor < 0.2 || previousFileSize == currentSize){ + throw new RuntimeException("Could not reach the desired size without excessively degrading image quality, lowest size recommended is " + FileUtils.byteCountToDisplaySize(currentSize) + ", " + currentSize + " bytes"); } + previousFileSize = currentSize; } else { // The file is small enough, break the loop break; diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 2fd871b0..d3427f79 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -287,4 +288,30 @@ public class PdfUtils { return PdfUtils.boasToWebResponse(baos, docName); } + + public static Long convertSizeToBytes(String sizeStr) { + if (sizeStr == null) { + return null; + } + + sizeStr = sizeStr.trim().toUpperCase(); + try { + if (sizeStr.endsWith("KB")) { + return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024; + } else if (sizeStr.endsWith("MB")) { + return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024; + } else if (sizeStr.endsWith("GB")) { + return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024; + } else if (sizeStr.endsWith("B")) { + return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); + } else { + // Input string does not have a valid format, handle this case + } + } catch (NumberFormatException e) { + // The numeric part of the input string cannot be parsed, handle this case + } + + return null; + } + } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 2b33ccf3..2604350f 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -224,12 +224,7 @@ compress.title=Compress compress.header=Compress PDF compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation. compress.selectText.1=Optimization level: -compress.selectText.2=0 (No optimization) -compress.selectText.3=1 (Default, lossless optimization) -compress.selectText.4=2 (Lossy optimization) -compress.selectText.5=3 (Lossy optimization, more aggressive) -compress.selectText.6=Enable fast web view (linearize PDF) -compress.selectText.7=Enable lossy JBIG2 encoding +compress.selectText.2=Expected PDF Size (e.g. 100MB, 25KB, 500B) compress.submit=Compress diff --git a/src/main/resources/static/css/dark-mode.css b/src/main/resources/static/css/dark-mode.css index 3cc945db..0e78e4ae 100644 --- a/src/main/resources/static/css/dark-mode.css +++ b/src/main/resources/static/css/dark-mode.css @@ -5,6 +5,11 @@ body { background-color: rgb(var(--body-background-color)) !important; color: rgb(var(--base-font-color)) !important; } +.card { + background-color: rgb(var(--body-background-color)) !important; + border: 1px solid #999; + color: rgb(var(--base-font-color)) !important; +} .dark-card { background-color: rgb(var(--body-background-color)) !important; diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 5689eaab..da2a4c70 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -71,6 +71,11 @@ filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg); .favorite-icon img { filter: brightness(0); } + +.jumbotron { + padding: 3rem 3rem; /* Reduce vertical padding */ +} + diff --git a/src/main/resources/templates/other/compress-pdf.html b/src/main/resources/templates/other/compress-pdf.html index a082e622..bd3931ae 100644 --- a/src/main/resources/templates/other/compress-pdf.html +++ b/src/main/resources/templates/other/compress-pdf.html @@ -17,24 +17,29 @@

-
- - +
+
+

Manual Mode - From 1 to 5

+ + +
-
- - + +
+
+

Auto mode - Auto adjusts quality to get PDF to exact size

+ + +
- -

-
From 3cad43006a2a6b889e678986a80a702f28c502d5 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 20 May 2023 22:48:59 +0100 Subject: [PATCH 3/7] compress finalise, cert test, locale test --- build.gradle | 5 +- .../stirling/software/SPDF/config/Beans.java | 13 ++ .../api/other/CompressController.java | 34 +++-- .../api/security/CertSignController.java | 144 ++++++++++++++++++ .../software/SPDF/utils/PdfUtils.java | 7 +- src/main/resources/messages_en_GB.properties | 7 +- .../templates/other/compress-pdf.html | 13 +- 7 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java diff --git a/build.gradle b/build.gradle index 3d8100e9..fabbcfeb 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,9 @@ dependencies { //general PDF implementation 'org.apache.pdfbox:pdfbox:2.0.28' - - + implementation 'org.bouncycastle:bcprov-jdk15on:1.70' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' + implementation 'com.itextpdf:itext7-core:7.2.5' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-core' diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index a65879d3..982aedd2 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -2,8 +2,10 @@ package stirling.software.SPDF.config; import java.util.Locale; +import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -50,4 +52,15 @@ public class Beans implements WebMvcConfigurer { return slr; } + @Bean + public MessageSource messageSource() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + //messageSource.setBasename("classpath:messages"); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setUseCodeAsDefaultMessage(true); + messageSource.setDefaultLocale(Locale.UK); // setting default locale + return messageSource; + } + + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index 2671e1b5..0c0ce77f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -56,8 +56,10 @@ public class CompressController { } Long expectedOutputSize = 0L; - if (expectedOutputSizeString != null) { + boolean autoMode = false; + if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) { expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString); + autoMode = true; } // Save the uploaded file to a temporary location @@ -69,8 +71,8 @@ public class CompressController { // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Determine initial optimization level based on expected size reduction, only if optimizeLevel is not provided - if(optimizeLevel == null) { + // Determine initial optimization level based on expected size reduction, only if in autoMode + if(autoMode) { double sizeReductionRatio = expectedOutputSize / (double) inputFileSize; if (sizeReductionRatio > 0.7) { optimizeLevel = 1; @@ -79,12 +81,12 @@ public class CompressController { } else if (sizeReductionRatio > 0.35) { optimizeLevel = 3; } else { - optimizeLevel = 4; + optimizeLevel = 3; } } - boolean sizeMet = expectedOutputSize == 0L; - while (!sizeMet && optimizeLevel <= 5) { + boolean sizeMet = false; + while (!sizeMet && optimizeLevel <= 4) { // Prepare the Ghostscript command List command = new ArrayList<>(); command.add("gs"); @@ -99,12 +101,9 @@ public class CompressController { command.add("-dPDFSETTINGS=/printer"); break; case 3: - command.add("-dPDFSETTINGS=/default"); - break; - case 4: command.add("-dPDFSETTINGS=/ebook"); break; - case 5: + case 4: command.add("-dPDFSETTINGS=/screen"); break; default: @@ -119,20 +118,27 @@ public class CompressController { int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); - // Check if file size is within expected size + // Check if file size is within expected size or not auto mode so instantly finish long outputFileSize = Files.size(tempOutputFile); - if (outputFileSize <= expectedOutputSize) { + if (outputFileSize <= expectedOutputSize || !autoMode) { sizeMet = true; } else { // Increase optimization level for next iteration optimizeLevel++; - System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); + if(autoMode && optimizeLevel > 3) { + System.out.println("Skipping level 4 due to bad results in auto mode"); + sizeMet = true; + } else if(optimizeLevel == 5) { + + } else { + System.out.println("Increasing ghostscript optimisation level to " + optimizeLevel); + } } } - if (expectedOutputSize != null) { + if (expectedOutputSize != null && autoMode) { long outputFileSize = Files.size(tempOutputFile); if (outputFileSize > expectedOutputSize) { try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) { 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 new file mode 100644 index 00000000..321e6444 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -0,0 +1,144 @@ +package stirling.software.SPDF.controller.api.security; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Security; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.itextpdf.kernel.pdf.*; +import com.itextpdf.signatures.*; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + + +@RestController +public class CertSignController { + + private static final Logger logger = LoggerFactory.getLogger(CertSignController.class); + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") + public ResponseEntity signPDF( + @RequestParam("pdf") MultipartFile pdf, + @RequestParam(value = "key", required = false) MultipartFile privateKeyFile, + @RequestParam(value = "cert", required = false) MultipartFile certFile, + @RequestParam(value = "p12", required = false) MultipartFile p12File, + @RequestParam(value = "password", required = false) String password) throws Exception { + BouncyCastleProvider provider = new BouncyCastleProvider(); + Security.addProvider(provider); + + PrivateKey privateKey = null; + X509Certificate cert = null; + + if (p12File != null) { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); + String alias = ks.aliases().nextElement(); + privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); + cert = (X509Certificate) ks.getCertificate(alias); + } else { + // Load private key + KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider); + if (isPEM(privateKeyFile.getBytes())) { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); + } else { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); + } + + // Load certificate + CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider); + if (isPEM(certFile.getBytes())) { + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); + } else { + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes())); + } + } + + // Set up the PDF reader and stamper + PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes())); + ByteArrayOutputStream signedPdf = new ByteArrayOutputStream(); + PdfSigner signer = new PdfSigner(reader, signedPdf, new StampingProperties()); + + // Set up the signing appearance + PdfSignatureAppearance appearance = signer.getSignatureAppearance() + .setReason("Test") + .setLocation("TestLocation"); + + // Set up the signer + PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); + IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); + IExternalDigest digest = new BouncyCastleDigest(); + + // Call iTex7 to sign the PDF + signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS); + + // This is just an example, you might want to save this signed PDF into your system or send it back in the response. + // For simplicity, we will just print out the size of the signed PDF. + System.out.println("Signed PDF size: " + signedPdf.size()); + + return ResponseEntity.ok("Signed PDF successfully"); + } + + private byte[] parsePEM(byte[] content) throws IOException { + PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); + return pemReader.readPemObject().getContent(); + } + + private boolean isPEM(byte[] content) { + String contentStr = new String(content); + return contentStr.contains("-----BEGIN") && contentStr.contains("-----END"); + } + + + + + +} diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index d3427f79..2787363d 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -297,11 +297,11 @@ public class PdfUtils { sizeStr = sizeStr.trim().toUpperCase(); try { if (sizeStr.endsWith("KB")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024; + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); } else if (sizeStr.endsWith("MB")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024; + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024); } else if (sizeStr.endsWith("GB")) { - return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024; + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024 * 1024); } else if (sizeStr.endsWith("B")) { return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); } else { @@ -313,5 +313,6 @@ public class PdfUtils { return null; } + } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 2604350f..5f29d6c5 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -223,8 +223,11 @@ fileToPDF.submit=Convert to PDF compress.title=Compress compress.header=Compress PDF compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation. -compress.selectText.1=Optimization level: -compress.selectText.2=Expected PDF Size (e.g. 100MB, 25KB, 500B) +compress.selectText.1=Manual Mode - From 1 to 4 +compress.selectText.2=Optimization level: +compress.selectText.3=4 (Terrible for text images) +compress.selectText.4=Auto mode - Auto adjusts quality to get PDF to exact size +compress.selectText.5=Expected PDF Size (e.g. 25MB, 10.8MB, 25KB) compress.submit=Compress diff --git a/src/main/resources/templates/other/compress-pdf.html b/src/main/resources/templates/other/compress-pdf.html index bd3931ae..50c2b5cc 100644 --- a/src/main/resources/templates/other/compress-pdf.html +++ b/src/main/resources/templates/other/compress-pdf.html @@ -11,7 +11,7 @@


-
R +

@@ -19,22 +19,21 @@
-

Manual Mode - From 1 to 5

- +

+
-

Auto mode - Auto adjusts quality to get PDF to exact size

- +

+
From 763aeb5fae2a9f261ebc28b7b838654890b52b42 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 21 May 2023 12:32:24 +0100 Subject: [PATCH 4/7] further sign stuff --- .../stirling/software/SPDF/config/Beans.java | 18 +-- .../api/security/CertSignController.java | 144 ++++++++++++++---- .../controller/web/SecurityWebController.java | 7 + src/main/resources/messages_en_GB.properties | 11 ++ .../resources/templates/fragments/common.html | 4 +- .../templates/security/cert-sign.html | 134 ++++++++++++++++ 6 files changed, 280 insertions(+), 38 deletions(-) create mode 100644 src/main/resources/templates/security/cert-sign.html diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index 982aedd2..814fa653 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -52,15 +52,15 @@ public class Beans implements WebMvcConfigurer { return slr; } - @Bean - public MessageSource messageSource() { - ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); - //messageSource.setBasename("classpath:messages"); - messageSource.setDefaultEncoding("UTF-8"); - messageSource.setUseCodeAsDefaultMessage(true); - messageSource.setDefaultLocale(Locale.UK); // setting default locale - return messageSource; - } +// @Bean +// public MessageSource messageSource() { +// ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); +// //messageSource.setBasename("classpath:messages"); +// messageSource.setDefaultEncoding("UTF-8"); +// messageSource.setUseCodeAsDefaultMessage(true); +// messageSource.setDefaultLocale(Locale.UK); // setting default locale +// return messageSource; +// } } 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 321e6444..c6f9b1c6 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 @@ -44,6 +44,9 @@ import org.springframework.web.multipart.MultipartFile; import com.itextpdf.kernel.pdf.*; import com.itextpdf.signatures.*; + +import stirling.software.SPDF.utils.PdfUtils; + import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -55,7 +58,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; - +import com.itextpdf.kernel.geom.Rectangle; @RestController public class CertSignController { @@ -66,39 +69,55 @@ public class CertSignController { } @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") - public ResponseEntity signPDF( + public ResponseEntity signPDF( @RequestParam("pdf") MultipartFile pdf, + @RequestParam(value = "certType", required = false) String certType, @RequestParam(value = "key", required = false) MultipartFile privateKeyFile, @RequestParam(value = "cert", required = false) MultipartFile certFile, @RequestParam(value = "p12", required = false) MultipartFile p12File, - @RequestParam(value = "password", required = false) String password) throws Exception { + @RequestParam(value = "password", required = false) String password, + @RequestParam(value = "showSignature", required = false) Boolean showSignature, + @RequestParam(value = "reason", required = false) String reason, + @RequestParam(value = "location", required = false) String location, + @RequestParam(value = "name", required = false) String name, + @RequestParam(value = "pageNumber", required = false) Integer pageNumber) throws Exception { + BouncyCastleProvider provider = new BouncyCastleProvider(); Security.addProvider(provider); PrivateKey privateKey = null; X509Certificate cert = null; + + if (certType != null) { + switch (certType) { + case "PKCS12": + if (p12File != null) { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); + String alias = ks.aliases().nextElement(); + privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); + cert = (X509Certificate) ks.getCertificate(alias); + } + break; + case "PEM": + if (privateKeyFile != null && certFile != null) { + // Load private key + KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider); + if (isPEM(privateKeyFile.getBytes())) { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); + } else { + privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); + } - if (p12File != null) { - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray()); - String alias = ks.aliases().nextElement(); - privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); - cert = (X509Certificate) ks.getCertificate(alias); - } else { - // Load private key - KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider); - if (isPEM(privateKeyFile.getBytes())) { - privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes()))); - } else { - privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes())); - } - - // Load certificate - CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider); - if (isPEM(certFile.getBytes())) { - cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); - } else { - cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes())); + // Load certificate + CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider); + if (isPEM(certFile.getBytes())) { + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes()))); + } else { + cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes())); + } + } + break; } } @@ -112,6 +131,42 @@ public class CertSignController { .setReason("Test") .setLocation("TestLocation"); + if (showSignature != null && showSignature) { + // Get the page size + PdfPage page = signer.getDocument().getPage(1); + Rectangle pageSize = page.getPageSize(); + + // Define the size of the signature rectangle + float sigWidth = 200; // adjust this as needed + float sigHeight = 100; // adjust this as needed + + // Define the margins from the page edges + float marginRight = 36; // adjust this as needed + float marginBottom = 36; // adjust this as needed + + // Define the position and dimension of the signature field + Rectangle rect = new Rectangle( + pageSize.getRight() - sigWidth - marginRight, + pageSize.getBottom() + marginBottom, + sigWidth, + sigHeight + ); + + // Creating the appearance + appearance + .setPageRect(rect) + .setPageNumber(pageNumber) + .setReason(reason) + .setLocation(location) + .setLayer2Text(name) // Set the signer name to be displayed in the signature field + .setReuseAppearance(false); + signer.setFieldName("sig"); + + signer.setFieldName("sig"); + } else { + appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); + } + // Set up the signer PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName()); @@ -120,13 +175,48 @@ public class CertSignController { // Call iTex7 to sign the PDF signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS); - // This is just an example, you might want to save this signed PDF into your system or send it back in the response. - // For simplicity, we will just print out the size of the signed PDF. + System.out.println("Signed PDF size: " + signedPdf.size()); - return ResponseEntity.ok("Signed PDF successfully"); + System.out.println("PDF signed = " + isPdfSigned(signedPdf.toByteArray())); + return PdfUtils.bytesToWebResponse(signedPdf.toByteArray(), "example.pdf"); } +public boolean isPdfSigned(byte[] pdfData) throws IOException { + InputStream pdfStream = new ByteArrayInputStream(pdfData); + PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfStream)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDoc); + List names = signatureUtil.getSignatureNames(); + + boolean isSigned = false; + + for (String name : names) { + PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(name); + if (pkcs7 != null) { + System.out.println("Signature found."); + + // Log certificate details + Certificate[] signChain = pkcs7.getSignCertificateChain(); + for (Certificate cert : signChain) { + if (cert instanceof X509Certificate) { + X509Certificate x509 = (X509Certificate) cert; + System.out.println("Certificate Details:"); + System.out.println("Subject: " + x509.getSubjectDN()); + System.out.println("Issuer: " + x509.getIssuerDN()); + System.out.println("Serial: " + x509.getSerialNumber()); + System.out.println("Not Before: " + x509.getNotBefore()); + System.out.println("Not After: " + x509.getNotAfter()); + } + } + + isSigned = true; + } + } + + pdfDoc.close(); + + return isSigned; +} private byte[] parsePEM(byte[] content) throws IOException { PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content))); return pemReader.readPemObject().getContent(); diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index 9eb10267..98821c85 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -34,4 +34,11 @@ public class SecurityWebController { model.addAttribute("currentPage", "add-watermark"); return "security/add-watermark"; } + + @GetMapping("/cert-sign") + @Hidden + public String certSignForm(Model model) { + model.addAttribute("currentPage", "cert-sign"); + return "security/cert-sign"; + } } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 5f29d6c5..d3adacf9 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -129,6 +129,17 @@ downloadPdf=Download PDF text=Text font=Font +certSign.title=Certificate Signing +certSign.header=Sign a PDF with your certificate +certSign.selectPDF=Select a PDF File for Signing: +certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der): +certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der): +certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate): +certSign.password=Enter Your Keystore or Private Key Password (If Any): +certSign.submit=Sign PDF + + + removeBlanks.title=Remove Blanks removeBlanks.header=Remove Blank Pages removeBlanks.threshold=Threshold: diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index dfd066e3..7d12a536 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -218,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function () { - + + + +
+ +
+ +
+
+
+
+
+
+ + \ No newline at end of file From 187b47eddd39aeb7943fd1ad25274d424dc7ef77 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 21 May 2023 17:12:18 +0100 Subject: [PATCH 5/7] cleanups --- .../stirling/software/SPDF/config/Beans.java | 13 -- .../SPDF/config/EndpointConfiguration.java | 4 +- .../software/SPDF/config/MetricsConfig.java | 4 - .../software/SPDF/config/MetricsFilter.java | 4 - .../api/other/CompressController.java | 17 +-- .../api/security/CertSignController.java | 133 ++++++++++-------- .../api/security/PasswordController.java | 2 +- .../controller/web/MetricsController.java | 10 +- .../software/SPDF/utils/PdfUtils.java | 3 - 9 files changed, 87 insertions(+), 103 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/Beans.java index 814fa653..e6b17a90 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -2,10 +2,8 @@ package stirling.software.SPDF.config; import java.util.Locale; -import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -51,16 +49,5 @@ public class Beans implements WebMvcConfigurer { slr.setDefaultLocale(defaultLocale); return slr; } - -// @Bean -// public MessageSource messageSource() { -// ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); -// //messageSource.setBasename("classpath:messages"); -// messageSource.setDefaultEncoding("UTF-8"); -// messageSource.setUseCodeAsDefaultMessage(true); -// messageSource.setDefaultLocale(Locale.UK); // setting default locale -// return messageSource; -// } - } diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index a98edbb2..749ae2ac 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -1,15 +1,13 @@ package stirling.software.SPDF.config; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import org.slf4j.Logger; @Service public class EndpointConfiguration { private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); diff --git a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java index 928a3868..25d6d8d6 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsConfig.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsConfig.java @@ -1,12 +1,8 @@ package stirling.software.SPDF.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.core.instrument.config.MeterFilterReply; diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java index 2407b649..d4f64596 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java @@ -3,15 +3,11 @@ package stirling.software.SPDF.config; import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.config.MeterFilter; -import io.micrometer.core.instrument.config.MeterFilterReply; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index 0c0ce77f..f410fe14 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -5,6 +5,12 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import javax.imageio.ImageIO; import org.apache.commons.io.FileUtils; import org.apache.pdfbox.cos.COSName; @@ -13,15 +19,6 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import javax.imageio.ImageIO; -import javax.imageio.stream.MemoryCacheImageOutputStream; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -33,9 +30,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.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; -import io.swagger.v3.oas.annotations.media.Schema; @RestController public class CompressController { 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 c6f9b1c6..3c948d19 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,64 +1,53 @@ package stirling.software.SPDF.controller.api.security; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.KeyFactory; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.Principal; import java.security.PrivateKey; import java.security.Security; -import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.List; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties; -import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.io.pem.PemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.itextpdf.kernel.pdf.*; -import com.itextpdf.signatures.*; +import com.itextpdf.io.font.constants.StandardFonts; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfPage; +import com.itextpdf.kernel.pdf.PdfReader; +import com.itextpdf.kernel.pdf.StampingProperties; +import com.itextpdf.signatures.BouncyCastleDigest; +import com.itextpdf.signatures.DigestAlgorithms; +import com.itextpdf.signatures.IExternalDigest; +import com.itextpdf.signatures.IExternalSignature; +import com.itextpdf.signatures.PdfPKCS7; +import com.itextpdf.signatures.PdfSignatureAppearance; +import com.itextpdf.signatures.PdfSigner; +import com.itextpdf.signatures.PrivateKeySignature; +import com.itextpdf.signatures.SignatureUtil; import stirling.software.SPDF.utils.PdfUtils; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.*; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import com.itextpdf.kernel.geom.Rectangle; @RestController public class CertSignController { @@ -121,6 +110,18 @@ public class CertSignController { } } + Principal principal = cert.getSubjectDN(); + String dn = principal.getName(); + + // Extract the "CN" (Common Name) field from the distinguished name (if it's present) + String cn = null; + for (String part : dn.split(",")) { + if (part.trim().startsWith("CN=")) { + cn = part.trim().substring("CN=".length()); + break; + } + } + // Set up the PDF reader and stamper PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes())); ByteArrayOutputStream signedPdf = new ByteArrayOutputStream(); @@ -132,36 +133,48 @@ public class CertSignController { .setLocation("TestLocation"); if (showSignature != null && showSignature) { - // Get the page size - PdfPage page = signer.getDocument().getPage(1); - Rectangle pageSize = page.getPageSize(); + float fontSize = 4; // the font size of the signature + float marginRight = 36; // Margin from the right + float marginBottom = 36; // Margin from the bottom + String signingDate = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z").format(new Date()); - // Define the size of the signature rectangle - float sigWidth = 200; // adjust this as needed - float sigHeight = 100; // adjust this as needed + // Prepare the text for the digital signature + String layer2Text = String.format("Digitally signed by: %s\nDate: %s\nReason: %s\nLocation: %s", name, signingDate, reason, location); - // Define the margins from the page edges - float marginRight = 36; // adjust this as needed - float marginBottom = 36; // adjust this as needed + // Get the PDF font and measure the width and height of the text block + PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD); + float textWidth = Arrays.stream(layer2Text.split("\n")) + .map(line -> font.getWidth(line, fontSize)) + .max(Float::compare) + .orElse(0f); + int numLines = layer2Text.split("\n").length; + float textHeight = numLines * fontSize; + + // Calculate the signature rectangle size + float sigWidth = textWidth + marginRight * 2; + float sigHeight = textHeight + marginBottom * 2; + + // Get the page size + PdfPage page = signer.getDocument().getPage(1); + Rectangle pageSize = page.getPageSize(); + + // Define the position and dimension of the signature field + Rectangle rect = new Rectangle( + pageSize.getRight() - sigWidth - marginRight, + pageSize.getBottom() + marginBottom, + sigWidth, + sigHeight + ); + + // Configure the appearance of the digital signature + appearance.setPageRect(rect) + .setContact(name) + .setPageNumber(pageNumber) + .setReason(reason) + .setLocation(location) + .setReuseAppearance(false) + .setLayer2Text(layer2Text); - // Define the position and dimension of the signature field - Rectangle rect = new Rectangle( - pageSize.getRight() - sigWidth - marginRight, - pageSize.getBottom() + marginBottom, - sigWidth, - sigHeight - ); - - // Creating the appearance - appearance - .setPageRect(rect) - .setPageNumber(pageNumber) - .setReason(reason) - .setLocation(location) - .setLayer2Text(name) // Set the signer name to be displayed in the signature field - .setReuseAppearance(false); - signer.setFieldName("sig"); - signer.setFieldName("sig"); } else { appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index 1872d6eb..585e41f3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -16,8 +16,8 @@ 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.PdfUtils; import io.swagger.v3.oas.annotations.media.Schema; +import stirling.software.SPDF.utils.PdfUtils; @RestController public class PasswordController { diff --git a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java index 97d6db93..de73f9e2 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java @@ -1,9 +1,4 @@ package stirling.software.SPDF.controller.web; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.MeterRegistry; -import io.swagger.v3.oas.annotations.Operation; - import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -13,6 +8,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.swagger.v3.oas.annotations.Operation; + @RestController @RequestMapping("/api/v1") public class MetricsController { diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 2787363d..337e0335 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -2,8 +2,6 @@ package stirling.software.SPDF.utils; import java.awt.Graphics; import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ColorConvertOp; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -22,7 +20,6 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; From 6854e96fb8bb518c0c2b1ea6a63416ab38262e1c Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 21 May 2023 23:25:34 +0100 Subject: [PATCH 6/7] cahnges --- Dockerfile | 2 +- .../api/security/CertSignController.java | 60 +++++++++++++++---- src/main/resources/messages_ar_AR.properties | 36 +++++++---- src/main/resources/messages_ca_CA.properties | 37 ++++++++---- src/main/resources/messages_de_DE.properties | 30 +++++++--- src/main/resources/messages_en_GB.properties | 16 ++++- src/main/resources/messages_es_ES.properties | 30 +++++++--- src/main/resources/messages_fr_FR.properties | 30 +++++++--- src/main/resources/messages_it_IT.properties | 31 +++++++--- src/main/resources/messages_pl_PL.properties | 33 ++++++---- src/main/resources/messages_ru_RU.properties | 30 +++++++--- src/main/resources/messages_sv_SE.properties | 30 +++++++--- src/main/resources/messages_zh_CN.properties | 30 +++++++--- src/main/resources/static/images/award.svg | 4 ++ .../resources/templates/fragments/navbar.html | 6 +- src/main/resources/templates/home.html | 2 + .../templates/security/cert-sign.html | 17 +++--- 17 files changed, 309 insertions(+), 115 deletions(-) create mode 100644 src/main/resources/static/images/award.svg diff --git a/Dockerfile b/Dockerfile index fbd71f5f..d07badf4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build jbig2enc in a separate stage -FROM frooodle/stirling-pdf-base:beta2 +FROM frooodle/stirling-pdf-base:beta3 # Create scripts folder and copy local scripts RUN mkdir /scripts 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 3c948d19..925c57f8 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,6 +1,7 @@ package stirling.software.SPDF.controller.api.security; import java.io.ByteArrayInputStream; +import io.swagger.v3.oas.annotations.media.Schema; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -26,6 +27,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -47,6 +49,8 @@ import com.itextpdf.signatures.PdfSigner; import com.itextpdf.signatures.PrivateKeySignature; import com.itextpdf.signatures.SignatureUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import stirling.software.SPDF.utils.PdfUtils; @RestController public class CertSignController { @@ -58,18 +62,52 @@ public class CertSignController { } @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") + @Operation(summary = "Sign PDF with a Digital Certificate", + description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file.") public ResponseEntity signPDF( - @RequestParam("pdf") MultipartFile pdf, - @RequestParam(value = "certType", required = false) String certType, - @RequestParam(value = "key", required = false) MultipartFile privateKeyFile, - @RequestParam(value = "cert", required = false) MultipartFile certFile, - @RequestParam(value = "p12", required = false) MultipartFile p12File, - @RequestParam(value = "password", required = false) String password, - @RequestParam(value = "showSignature", required = false) Boolean showSignature, - @RequestParam(value = "reason", required = false) String reason, - @RequestParam(value = "location", required = false) String location, - @RequestParam(value = "name", required = false) String name, - @RequestParam(value = "pageNumber", required = false) Integer pageNumber) throws Exception { + @RequestPart(required = true, value = "fileInput") + @Parameter(description = "The input PDF file to be signed") + MultipartFile pdf, + + @RequestParam(value = "certType", required = false) + @Parameter(description = "The type of the digital certificate", schema = @Schema(allowableValues = {"PKCS12", "PEM"})) + String certType, + + @RequestParam(value = "key", required = false) + @Parameter(description = "The private key for the digital certificate (required for PEM type certificates)") + MultipartFile privateKeyFile, + + @RequestParam(value = "cert", required = false) + @Parameter(description = "The digital certificate (required for PEM type certificates)") + MultipartFile certFile, + + @RequestParam(value = "p12", required = false) + @Parameter(description = "The PKCS12 keystore file (required for PKCS12 type certificates)") + MultipartFile p12File, + + @RequestParam(value = "password", required = false) + @Parameter(description = "The password for the keystore or the private key") + String password, + + @RequestParam(value = "showSignature", required = false) + @Parameter(description = "Whether to visually show the signature in the PDF file") + Boolean showSignature, + + @RequestParam(value = "reason", required = false) + @Parameter(description = "The reason for signing the PDF") + String reason, + + @RequestParam(value = "location", required = false) + @Parameter(description = "The location where the PDF is signed") + String location, + + @RequestParam(value = "name", required = false) + @Parameter(description = "The name of the signer") + String name, + + @RequestParam(value = "pageNumber", required = false) + @Parameter(description = "The page number where the signature should be visible. This is required if showSignature is set to true") + Integer pageNumber) throws Exception { BouncyCastleProvider provider = new BouncyCastleProvider(); Security.addProvider(provider); diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 305b444d..8db6cc95 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -135,6 +135,22 @@ home.compare.desc = يقارن ويظهر الاختلافات بين 2 من م downloadPdf = تنزيل PDF text=نص font=الخط +selectFillter = - حدد - +pageNum = رقم الصفحة + +certSign.title = توقيع الشهادة +certSign.header = قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم) +certSign.selectPDF = حدد ملف PDF للتوقيع: +certSign.selectKey = حدد ملف المفتاح الخاص (تنسيق PKCS # 8 ، يمكن أن يكون .pem أو .der): +certSign.selectCert = حدد ملف الشهادة الخاص بك (تنسيق X.509 ، يمكن أن يكون .pem أو .der): +certSign.selectP12 = حدد ملف تخزين المفاتيح PKCS # 12 (.p12 أو .pfx) (اختياري ، إذا تم توفيره ، يجب أن يحتوي على مفتاحك الخاص وشهادتك): +certSign.certType = نوع الشهادة +certSign.password = أدخل ملف تخزين المفاتيح أو كلمة المرور الخاصة (إن وجدت): +certSign.showSig = إظهار التوقيع +certSign.reason = السبب +certSign.location = الموقع +certSign.name = الاسم +certSign.submit = تسجيل PDF removeBlanks.title = إزالة الفراغات removeBlanks.header = إزالة الصفحات الفارغة @@ -229,17 +245,15 @@ addImage.everyPage=كل صفحة؟ addImage.submit=إضافة صورة #compress -compress.title=ضغط -compress.header=\u0636\u063A\u0637 PDF -compress.credit=\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0644\u0636\u063A\u0637 / \u062A\u062D\u0633\u064A\u0646 PDF. -compress.selectText.1=\u0645\u0633\u062A\u0648\u0649 \u0627\u0644\u062A\u062D\u0633\u064A\u0646: -compress.selectText.2=0 (\u0628\u062F\u0648\u0646 \u062A\u062D\u0633\u064A\u0646) -compress.selectText.3=1 (\u0627\u0641\u062A\u0631\u0627\u0636\u064A\u060C \u062A\u062D\u0633\u064A\u0646 \u0628\u062F\u0648\u0646 \u0641\u0642\u062F\u0627\u0646) -compress.selectText.4=2 (\u062A\u062D\u0633\u064A\u0646 \u0636\u064A\u0627\u0639) -compress.selectText.5=3 (\u062A\u062D\u0633\u064A\u0646 \u0636\u064A\u0627\u0639 \u060C \u0623\u0643\u062B\u0631 \u0639\u062F\u0648\u0627\u0646\u064A\u0629) -compress.selectText.6=\u062A\u0645\u0643\u064A\u0646 \u0639\u0631\u0636 \u0627\u0644\u0648\u064A\u0628 \u0627\u0644\u0633\u0631\u064A\u0639 (\u062E\u0637\u064A PDF) -compress.selectText.7=\u062A\u0645\u0643\u064A\u0646 \u062A\u0631\u0645\u064A\u0632 JBIG2 \u0627\u0644\u0645\u0641\u0642\u0648\u062F -compress.submit=ضغط +compress.title = ضغط +compress.header = ضغط ملف PDF +compress.credit = تستخدم هذه الخدمة OCRmyPDF لضغط / تحسين PDF. +compress.selectText.1 = الوضع اليدوي - من 1 إلى 4 +compress.selectText.2 = مستوى التحسين: +compress.selectText.3 = 4 (رهيب للصور النصية) +compress.selectText.4 = الوضع التلقائي - يضبط الجودة تلقائيًا للحصول على ملف PDF بالحجم المحدد +compress.selectText.5 = حجم PDF المتوقع (على سبيل المثال 25 ميجا بايت ، 10.8 ميجا بايت ، 25 كيلو بايت) +compress.submit = ضغطضغط #merge diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 92e560fe..119ed479 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -128,6 +128,22 @@ home.compare.desc=Compara i mostra les diferències entre 2 documents PDF downloadPdf=Descarregueu PDF text=Text font=Tipus de lletra +selectFillter=-- Selecciona -- +pageNum=Número de pàgina + +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: +certSign.selectKey=Seleccioneu el vostre fitxer de clau privada (format PKCS#8, podria ser .pem o .der): +certSign.selectCert=Seleccioneu el vostre fitxer de certificat (format X.509, podria ser .pem o .der): +certSign.selectP12=Seleccioneu el vostre fitxer de magatzem de claus PKCS#12 (.p12 o .pfx) (Opcional, si es proporciona, hauria de contenir la vostra clau privada i certificat): +certSign.certType=Tipus de certificat +certSign.password=Introduïu el vostre magatzem de claus o contrasenya de clau privada (si n'hi ha): +certSign.showSig=Mostra la signatura +certSign.reason=Motiu +certSign.location=Ubicació +certSign.name=Nom +certSign.submit=Firma PDF removeBlanks.title=Elimina els espais en blanc removeBlanks.header=Elimina les pàgines en blanc @@ -220,18 +236,15 @@ fileToPDF.submit=Converteix a PDF #compress -compress.title=Comprimeix -compress.header=Comprimeix PDF -compress.credit=Utilitza OCRmyPDF per Compressió/Optimització de PDF. -compress.selectText.1=Nivell Optimització: -compress.selectText.2=0 (Sense Optimització) -compress.selectText.3=1 (Defecte, sense pèrdua Optimització) -compress.selectText.4=2 (Pèrdua Optimització) -compress.selectText.5=3 (Pèrdua Optimització, més agressiu) -compress.selectText.6=Activa la visualització web ràpida (linealitza PDF) -compress.selectText.7=Activa pèrdua codificació JBIG2 -compress.submit=Comprimeix - +compress.title=Comprimir +compress.header=Comprimir PDF +compress.credit=Aquest servei utilitza Ghostscript per a la compressió/optimització de PDF. +compress.selectText.1=Mode manual: de l'1 al 4 +compress.selectText.2=Nivell d'optimització: +compress.selectText.3=4 (terrible per a imatges de text) +compress.selectText.4=Mode automàtic: ajusta automàticament la qualitat per tal que el PDF tingui la mida exacta +compress.selectText.5=Mida esperada del PDF (p. ex. 25 MB, 10,8 MB, 25 KB) +compress.submit=Comprimir #Add image addImage.title=Afegir Imatge diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index c9e48adb..7abe0546 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -127,6 +127,22 @@ home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokume downloadPdf=PDF herunterladen text=Text font=Schriftart +selectFillter=-- Auswählen -- +pageNum=Seitenzahl + +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: +certSign.selectKey=Wählen Sie Ihre private Schlüsseldatei aus (PKCS#8-Format, könnte .pem oder .der sein): +certSign.selectCert=Wählen Sie Ihre Zertifikatsdatei aus (X.509-Format, könnte .pem oder .der sein): +certSign.selectP12=Wählen Sie Ihre PKCS#12-Keystore-Datei (.p12 oder .pfx) aus (optional, falls angegeben, sollte sie Ihren privaten Schlüssel und Ihr Zertifikat enthalten): +certSign.certType=Zertifikattyp +certSign.password=Geben Sie Ihr Keystore- oder Private-Key-Passwort ein (falls vorhanden): +certSign.showSig=Signatur anzeigen +certSign.reason=Grund +certSign.location=Standort +certSign.name=Name +certSign.submit=PDF signieren removeBlanks.title=Leerzeichen entfernen removeBlanks.header=Leere Seiten entfernen @@ -226,14 +242,12 @@ addImage.submit=Bild hinzufügen #compress compress.title=Komprimieren compress.header=PDF komprimieren -compress.credit=Dieser Dienst verwendet OCRmyPDF für die PDF-Komprimierung/-Optimierung. -compress.selectText.1=Optimierungsstufe: -compress.selectText.2=0 (Keine Optimierung) -compress.selectText.3=1 (Standard, verlustfreie Optimierung) -compress.selectText.4=2 (Verlustbehaftete Optimierung) -compress.selectText.5=3 (Verlustbehaftete Optimierung, aggressiver) -compress.selectText.6=Schnelle Webansicht aktivieren (PDF linearisieren) -compress.selectText.7=Verlustbehaftete JBIG2-Kodierung aktivieren +compress.credit=Dieser Dienst verwendet Ghostscript für die PDF-Komprimierung/-Optimierung. +compress.selectText.1=Manueller Modus – Von 1 bis 4 +compress.selectText.2=Optimierungsstufe: +compress.selectText.3=4 (Schrecklich für Textbilder) +compress.selectText.4=Automatischer Modus – Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen +compress.selectText.5=Erwartete PDF-Größe (z. B. 25 MB, 10,8 MB, 25 KB) compress.submit=Komprimieren diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index d3adacf9..84307fb3 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -125,17 +125,29 @@ home.removeBlanks.desc=Detects and removes blank pages from a document home.compare.title=Compare home.compare.desc=Compares and shows the differences between 2 PDF Documents +home.certSign.title=Sign with Certificate +home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12) + + downloadPdf=Download PDF text=Text font=Font +selectFillter=-- Select -- +pageNum=Page Number certSign.title=Certificate Signing -certSign.header=Sign a PDF with your certificate +certSign.header=Sign a PDF with your certificate (Work in progress) certSign.selectPDF=Select a PDF File for Signing: certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der): certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der): certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate): +certSign.certType=Certificate Type certSign.password=Enter Your Keystore or Private Key Password (If Any): +certSign.showSig=Show Signature +certSign.reason=Reason +certSign.location=Location +certSign.name=Name + certSign.submit=Sign PDF @@ -233,7 +245,7 @@ fileToPDF.submit=Convert to PDF #compress compress.title=Compress compress.header=Compress PDF -compress.credit=This service uses OCRmyPDF for PDF Compress/Optimisation. +compress.credit=This service uses Ghostscript for PDF Compress/Optimisation. compress.selectText.1=Manual Mode - From 1 to 4 compress.selectText.2=Optimization level: compress.selectText.3=4 (Terrible for text images) diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index f247192f..524283a5 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -127,6 +127,22 @@ home.compare.desc=Compara y muestra las diferencias entre 2 documentos PDF downloadPdf=Descargar PDF text=Texto font=Fuente +selectFilter=-- Seleccionar -- +pageNum=Número de página + +certSign.title=Firma de certificado +certSign.header=Firme un PDF con su certificado (Trabajo en progreso) +certSign.selectPDF=Seleccione un archivo PDF para firmar: +certSign.selectKey=Seleccione su archivo de clave privada (formato PKCS#8, podría ser .pem o .der): +certSign.selectCert=Seleccione su archivo de certificado (formato X.509, podría ser .pem o .der): +certSign.selectP12=Seleccione su archivo de almacén de claves PKCS#12 (.p12 o .pfx) (Opcional, si se proporciona, debe contener su clave privada y certificado): +certSign.certType=Tipo de certificado +certSign.password=Ingrese su almacén de claves o contraseña de clave privada (si corresponde): +certSign.showSig=Mostrar firma +certSign.reason=Razón +certSign.location=Ubicación +certSign.name=Nombre +certSign.submit=Firmar PDF removeBlanks.title=Eliminar espacios en blanco removeBlanks.header=Eliminar páginas en blanco @@ -220,14 +236,12 @@ fileToPDF.submit=Convertir a PDF #compress compress.title=Comprimir compress.header=Comprimir PDF -compress.credit=Este servicio usa OCRmyPDF para la Compresión/Optimizatión del PDF. -compress.selectText.1=Nivel de Optimización: -compress.selectText.2=0 (Sin optimización) -compress.selectText.3=1 (Por defecto, optimización sin pérdidas) -compress.selectText.4=2 (Optimización con pérdida) -compress.selectText.5=3 (Optimización con pérdida, más agresiva) -compress.selectText.6=Habilita la vista web rápida (linealizar PDF) -compress.selectText.7=Habilita la codificación JBIG2 con pérdida +compress.credit=Este servicio utiliza Ghostscript para compresión/optimización de PDF. +compress.selectText.1=Modo manual - De 1 a 4 +compress.selectText.2=Nivel de optimización: +compress.selectText.3=4 (Terrible para imágenes de texto) +compress.selectText.4=Modo automático: ajusta automáticamente la calidad para que el PDF tenga el tamaño exacto +compress.selectText.5=Tamaño de PDF esperado (por ejemplo, 25 MB, 10,8 MB, 25 KB) compress.submit=Comprimir diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index 157cfd3b..4038c78b 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -133,6 +133,22 @@ home.compare.desc=Compare et affiche les différences entre 2 documents PDF downloadPdf=Télécharger le PDF text=Texte font=Police +selectFilter=-- Sélectionner -- +pageNum=numéro de page + +certSign.title=Signature du certificat +certSign.header=Signer un PDF avec votre certificat (Travail en cours) +certSign.selectPDF=Sélectionnez un fichier PDF à signer : +certSign.selectKey=Sélectionnez votre fichier de clé privée (format PKCS#8, peut être .pem ou .der) : +certSign.selectCert=Sélectionnez votre fichier de certificat (format X.509, peut être .pem ou .der) : +certSign.selectP12=Sélectionnez votre fichier de magasin de clés PKCS#12 (.p12 ou .pfx) (facultatif, s'il est fourni, il doit contenir votre clé privée et votre certificat) : +certSign.certType=Type de certificat +certSign.password=Entrez votre mot de passe de keystore ou de clé privée (le cas échéant) : +certSign.showSig=Afficher la signature +certSign.reason=Raison +certSign.location=Emplacement +certSign.name=Nom +certSign.submit=Signer le PDF removeBlanks.title=Supprimer les blancs removeBlanks.header=Supprimer les pages vierges @@ -229,14 +245,12 @@ addImage.submit=Ajouter une image #compress compress.title=Compresser compress.header=Compresser le PDF -compress.credit=Ce service utilise OCRmyPDF pour la compression/optimisation PDF. -compress.selectText.1=Niveau d'optimisation : -compress.selectText.2=0 (pas d'optimisation) -compress.selectText.3=1 (par défaut, optimisation sans perte) -compress.selectText.4=2 (optimisation avec perte) -compress.selectText.5=3 (optimisation avec perte, plus agressive) -compress.selectText.6=Activer l'affichage Web rapide (linéariser PDF) -compress.selectText.7=Activer l'encodage JBIG2 avec perte +compress.credit=Ce service utilise Ghostscript pour PDF Compress/Optimisation. +compress.selectText.1=Mode manuel - De 1 à 4 +compress.selectText.2=Niveau d'optimisation : +compress.selectText.3=4 (Terrible pour les images de texte) +compress.selectText.4=Mode automatique - Ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte +compress.selectText.5=Taille PDF attendue (par exemple, 25 Mo, 10,8 Mo, 25 Ko) compress.submit=Compresser diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index f4d299ec..c888ab68 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -128,6 +128,22 @@ home.compare.desc=Vedi e compara le differenze tra due PDF. downloadPdf=Scarica PDF text=Testo font=Font +selectFillter=-- Seleziona -- +pageNum=Numero pagina + +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: +certSign.selectKey=Seleziona il file della tua chiave privata (formato PKCS#8, potrebbe essere .pem o .der): +certSign.selectCert=Seleziona il tuo file di certificato (formato X.509, potrebbe essere .pem o .der): +certSign.selectP12=Selezionare il file keystore PKCS#12 (.p12 o .pfx) (facoltativo, se fornito, dovrebbe contenere la chiave privata e il certificato): +certSign.certType=Tipo di certificato +certSign.password=Inserisci la tua password dell'archivio chiavi o della chiave privata (se presente): +certSign.showSig=Mostra firma +certSign.reason=Motivo +certSign.location=Posizione +certSign.name=Nome +certSign.submit=Firma PDF removeBlanks.title=Rimuovi spazi vuoti removeBlanks.header=Rimuovi pagine vuote @@ -222,17 +238,14 @@ fileToPDF.submit=Converti in PDF #compress compress.title=Comprimi compress.header=Comprimi PDF -compress.credit=Questo servizio utilizza OCRmyPDF per la compressione e ottimizzazione. -compress.selectText.1=Livello di ottimizzazione: -compress.selectText.2=0 (Nessuna compressione) -compress.selectText.3=1 (Default, nessuna perdita di qualità) -compress.selectText.4=2 (Perdita di qualità) -compress.selectText.5=3 (Perdita di qualità, più aggressivo) -compress.selectText.6=Visualizzazione rapida sul web (linearizza PDF) -compress.selectText.7=Attiva codifica JBIG2 (lossy) +compress.credit=Questo servizio utilizza Ghostscript per la compressione/ottimizzazione dei PDF. +compress.selectText.1=Modalità manuale - Da 1 a 4 +compress.selectText.2=Livello di ottimizzazione: +compress.selectText.3=4 (Terribile per le immagini di testo) +compress.selectText.4=Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF +compress.selectText.5=Dimensioni PDF previste (ad es. 25 MB, 10,8 MB, 25 KB) compress.submit=Comprimi - #Add image addImage.title=Aggiungi Immagine addImage.header=Aggiungi un'immagine ad un PDF. diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 095d4e55..1ce94260 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -128,6 +128,22 @@ home.compare.desc=Porównuje i pokazuje różnice między 2 dokumentami PDF downloadPdf=Pobierz PDF text=Tekst font=Czcionka +selectFillter=-- Wybierz -- +pageNum=Numer strony + +certSign.title=Podpisywanie certyfikatu +certSign.header=Podpisz plik PDF swoim certyfikatem (prace w toku) +certSign.selectPDF=Wybierz plik PDF do podpisania: +certSign.selectKey=Wybierz plik klucza prywatnego (format PKCS#8, może to być .pem lub .der): +certSign.selectCert=Wybierz plik certyfikatu (format X.509, może to być .pem lub .der): +certSign.selectP12=Wybierz plik magazynu kluczy PKCS#12 (.p12 lub .pfx) (opcjonalnie, jeśli jest podany, powinien zawierać klucz prywatny i certyfikat): +certSign.certType=Typ certyfikatu +certSign.password=Wprowadź hasło do magazynu kluczy lub klucza prywatnego (jeśli istnieje): +certSign.showSig=Pokaż podpis +certSign.reason=Powód +certSign.location=Lokalizacja +certSign.name=Nazwa +certSign.submit=Podpisz PDF removeBlanks.title=Usuń puste removeBlanks.header=Usuń puste strony @@ -221,18 +237,15 @@ fileToPDF.submit=Konwertuj na PDF #compress compress.title=Kompresuj -compress.header=Kompresuj dokument PDF -compress.credit=Ta usługa wykorzystuje OCRmyPDF do kompresji/optymalizacji PDF. -compress.selectText.1=Poziom optymalizacji: -compress.selectText.2=0 (Brak optymalizacji) -compress.selectText.3=1 (Domyślna, bezstratna optymalizacja) -compress.selectText.4=2 (Stratna optymalizacja) -compress.selectText.5=3 (Stratna optymalizacja, bardziej agresywna) -compress.selectText.6=Włącz szybki podgląd w Internecie (linearyzacja PDF) -compress.selectText.7=Włącz stratne kodowanie JBIG2 +compress.header=Kompresuj PDF +compress.credit=Ta usługa używa Ghostscript do kompresji/optymalizacji PDF. +compress.selectText.1=Tryb ręczny - Od 1 do 4 +compress.selectText.2=Poziom optymalizacji: +compress.selectText.3=4 (Straszne dla obrazów tekstowych) +compress.selectText.4=Tryb automatyczny - Automatycznie dostosowuje jakość, aby uzyskać dokładny rozmiar pliku PDF +compress.selectText.5=Oczekiwany rozmiar pliku PDF (np. 25 MB, 10,8 MB, 25 KB) compress.submit=Kompresuj - #Add image addImage.title=Dodaj obraz addImage.header=Dodaj obraz do PDF diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index 769bf84c..b4d87bde 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -128,6 +128,22 @@ home.compare.desc=Сравнивает и показывает различия downloadPdf=Скачать PDF text=Текст font=Шрифт +selectFillter=-- Выбрать -- +pageNum=номер страницы + +certSign.title=Подписание сертификата +certSign.header=Подпишите PDF своим сертификатом (работа в процессе) +certSign.selectPDF=Выберите файл PDF для подписи: +certSign.selectKey=Выберите файл закрытого ключа (формат PKCS#8, может быть .pem или .der): +certSign.selectCert=Выберите файл сертификата (формат X.509, может быть .pem или .der): +certSign.selectP12=Выберите файл хранилища ключей PKCS#12 (.p12 или .pfx) (необязательно, если он предоставлен, он должен содержать ваш закрытый ключ и сертификат): +certSign.certType=Тип сертификата +certSign.password=Введите пароль от хранилища ключей или личного ключа (если есть): +certSign.showSig=Показать подпись +certSign.reason=Причина +certSign.location=Местоположение +certSign.name=Имя +certSign.submit=Подписать PDF removeBlanks.title=Удалить Пустые removeBlanks.header=Удалить Пустые Страницы @@ -222,14 +238,12 @@ fileToPDF.submit=Преобразовать в PDF #compress compress.title=Сжать compress.header=Сжать PDF -compress.credit=Этот сервис использует OCRmyPDF для сжатия/оптимизации PDF. -compress.selectText.1=Уровень оптимизации: -compress.selectText.2=0 (Без оптимизации) -compress.selectText.3=1 (Оптимизация по умолчанию без потерь) -compress.selectText.4=2 (Оптимизация с потерями) -compress.selectText.5=3 (Оптимизация с потерями, более агрессивная) -compress.selectText.6=Включить быстрый веб-просмотр (линеаризовать PDF) -compress.selectText.7=Включить кодирование JBIG2 с потерями +compress.credit=Эта служба использует Ghostscript для сжатия/оптимизации PDF. +compress.selectText.1=Ручной режим - от 1 до 4 +compress.selectText.2=Уровень оптимизации: +compress.selectText.3=4 (Ужасно для текстовых изображений) +compress.selectText.4=Автоматический режим - автоматически настраивает качество для получения PDF точного размера +compress.selectText.5=Ожидаемый размер PDF (например, 25 МБ, 10,8 МБ, 25 КБ) compress.submit=Сжать diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 5c0afbbb..38b300ee 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -128,6 +128,22 @@ home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument downloadPdf=Ladda ner PDF text=Text font=Teckensnitt +selectFillter=-- Välj -- +pageNum=Sidnummer + +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: +certSign.selectKey=Välj din privata nyckelfil (PKCS#8-format, kan vara .pem eller .der): +certSign.selectCert=Välj din certifikatfil (X.509-format, kan vara .pem eller .der): +certSign.selectP12=Välj din PKCS#12-nyckellagringsfil (.p12 eller .pfx) (Valfritt, om den tillhandahålls bör den innehålla din privata nyckel och certifikat): +certSign.certType=Certifikattyp +certSign.password=Ange ditt nyckellager eller privata nyckellösenord (om något): +certSign.showSig=Visa signatur +certSign.reason=Anledning +certSign.location=Plats +certSign.name=Namn +certSign.submit=Skriv under PDF removeBlanks.title=Ta bort tomrum removeBlanks.header=Ta bort tomma sidor @@ -222,14 +238,12 @@ fileToPDF.submit=Konvertera till PDF #komprimera compress.title=Komprimera compress.header=Komprimera PDF -compress.credit=Denna tjänst använder OCRmyPDF för PDF-komprimering/optimering. -compress.selectText.1=Optimeringsnivå: -compress.selectText.2=0 (Ingen optimering) -compress.selectText.3=1 (Standard, förlustfri optimering) -compress.selectText.4=2 (förlustoptimering) -compress.selectText.5=3 (förlustoptimering, mer aggressiv) -compress.selectText.6=Aktivera snabb webbvy (linjärisera PDF) -compress.selectText.7=Aktivera JBIG2-kodning med förlust +compress.credit=Denna tjänst använder Ghostscript för PDF-komprimering/optimering. +compress.selectText.1=Manuellt läge - Från 1 till 4 +compress.selectText.2=Optimeringsnivå: +compress.selectText.3=4 (Fruktansvärt för textbilder) +compress.selectText.4=Autoläge - Autojusterar kvaliteten för att få PDF till exakt storlek +compress.selectText.5=Förväntad PDF-storlek (t.ex. 25MB, 10,8MB, 25KB) compress.submit=Komprimera diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 5b006821..b2e6ec9f 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -128,6 +128,22 @@ home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B downloadPdf=\u4E0B\u8F7DPDF text=\u6587\u672C font=\u5B57\u4F53 +selectFillter=-- 选择-- +pageNum=页码 + +certSign.title=证书签名 +certSign.header=使用您的证书签署 PDF(进行中) +certSign.selectPDF=选择要签名的 PDF 文件: +certSign.selectKey=选择您的私钥文件(PKCS#8 格式,可以是 .pem 或 .der): +certSign.selectCert=选择您的证书文件(X.509 格式,可以是 .pem 或 .der): +certSign.selectP12=选择您的 PKCS#12 密钥库文件(.p12 或 .pfx)(可选,如果提供,它应该包含您的私钥和证书): +certSign.certType=证书类型 +certSign.password=输入您的密钥库或私钥密码(如果有): +certSign.showSig=显示签名 +certSign.reason=原因 +certSign.location=位置 +certSign.name=名称 +certSign.submit=签署 PDF removeBlanks.title=\u5220\u9664\u7A7A\u767D removeBlanks.header=\u5220\u9664\u7A7A\u767D\u9875 @@ -221,14 +237,12 @@ fileToPDF.submit=转换为 PDF #compress compress.title=压缩 compress.header=压缩PDF -compress.credit=此服务使用OCRmyPDF进行PDF压缩/优化。 -compress.selectText.1=优化水平: -compress.selectText.2=0 (无优化) -compress.selectText.3=1 (默认,无损优化) -compress.selectText.4=2 (有损优化) -compress.selectText.5=3 (有损优化,更激进) -compress.selectText.6=启用快速网络视图(线性化PDF)。 -compress.selectText.7=启用有损的JBIG2编码 +compress.credit=此服务使用 Ghostscript 进行 PDF 压缩/优化。 +compress.selectText.1=手动模式 - 从 1 到 4 +compress.selectText.2=优化级别: +compress.selectText.3=4(文本图像很糟糕) +compress.selectText.4=自动模式 - 自动调整质量以获得精确大小的 PDF +compress.selectText.5=预期 PDF 大小(例如 25MB、10.8MB、25KB) compress.submit=压缩 diff --git a/src/main/resources/static/images/award.svg b/src/main/resources/static/images/award.svg new file mode 100644 index 00000000..8f572ff0 --- /dev/null +++ b/src/main/resources/static/images/award.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 69dbd54a..e5128121 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -186,7 +186,7 @@ function compareVersions(version1, version2) { -
-