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/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/build.gradle b/build.gradle index 84e203ef..1dd67c3e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'stirling.software' -version = '0.8.3' +version = '0.9.0' sourceCompatibility = '17' repositories { @@ -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..e6b17a90 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/Beans.java @@ -49,5 +49,5 @@ public class Beans implements WebMvcConfigurer { slr.setDefaultLocale(defaultLocale); return slr; } - + } 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 89f00981..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 @@ -1,11 +1,24 @@ package stirling.software.SPDF.controller.api.other; -import java.io.IOException; +import java.awt.Image; +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; +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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -17,67 +30,190 @@ 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 { 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(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; + boolean autoMode = false; + if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1 ) { + expectedOutputSize = PdfUtils.convertSizeToBytes(expectedOutputSizeString); + autoMode = true; + } // 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 OCRmyPDF 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"); - - 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)); + // 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; + } else if (sizeReductionRatio > 0.5) { + optimizeLevel = 2; + } else if (sizeReductionRatio > 0.35) { + optimizeLevel = 3; + } else { + optimizeLevel = 3; + } } - if (jbig2Lossy != null && jbig2Lossy) { - command.add("--jbig2-lossy"); + boolean sizeMet = false; + while (!sizeMet && optimizeLevel <= 4) { + // Prepare the Ghostscript command + List command = new ArrayList<>(); + command.add("gs"); + command.add("-sDEVICE=pdfwrite"); + command.add("-dCompatibilityLevel=1.4"); + + switch (optimizeLevel) { + case 1: + command.add("-dPDFSETTINGS=/prepress"); + break; + case 2: + command.add("-dPDFSETTINGS=/printer"); + break; + case 3: + command.add("-dPDFSETTINGS=/ebook"); + break; + case 4: + command.add("-dPDFSETTINGS=/screen"); + break; + default: + command.add("-dPDFSETTINGS=/default"); + } + + 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 or not auto mode so instantly finish + long outputFileSize = Files.size(tempOutputFile); + if (outputFileSize <= expectedOutputSize || !autoMode) { + sizeMet = true; + } else { + // Increase optimization level for next iteration + 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); + } + } } - command.add(tempInputFile.toString()); - command.add(tempOutputFile.toString()); + - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + if (expectedOutputSize != null && autoMode) { + 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()) { + 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()); + + long currentSize = Files.size(tempOutputFile); + // Check if the overall PDF size is still larger than expectedOutputSize + if (currentSize > expectedOutputSize) { + // Log the current file size and scaleFactor + + System.out.println("Current file size: " + FileUtils.byteCountToDisplaySize(currentSize)); + System.out.println("Current scale factor: " + scaleFactor); + + // The file is still too large, reduce scaleFactor and try again + scaleFactor *= 0.9; // reduce scaleFactor by 10% + // Avoid scaleFactor being too small, causing the image to shrink to 0 + if(scaleFactor < 0.2 || previousFileSize == currentSize){ + throw new RuntimeException("Could not reach the desired size without excessively degrading image quality, lowest size recommended is " + FileUtils.byteCountToDisplaySize(currentSize) + ", " + currentSize + " bytes"); + } + previousFileSize = currentSize; + } 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/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..925c57f8 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -0,0 +1,285 @@ +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; +import java.io.InputStreamReader; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.Certificate; +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 org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.itextpdf.io.font.constants.StandardFonts; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.kernel.pdf.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 io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import stirling.software.SPDF.utils.PdfUtils; +@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") + @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( + @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); + + 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())); + } + + // 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; + } + } + + 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(); + PdfSigner signer = new PdfSigner(reader, signedPdf, new StampingProperties()); + + // Set up the signing appearance + PdfSignatureAppearance appearance = signer.getSignatureAppearance() + .setReason("Test") + .setLocation("TestLocation"); + + if (showSignature != null && showSignature) { + 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()); + + // 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); + + // 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); + + 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()); + IExternalDigest digest = new BouncyCastleDigest(); + + // Call iTex7 to sign the PDF + signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS); + + + System.out.println("Signed PDF size: " + signedPdf.size()); + + 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(); + } + + 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/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/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/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 2fd871b0..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; @@ -287,4 +285,31 @@ 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) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); + } else if (sizeStr.endsWith("MB")) { + return (long) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024 * 1024); + } else if (sizeStr.endsWith("GB")) { + 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 { + // 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_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 2b33ccf3..84307fb3 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -125,9 +125,32 @@ 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 (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 + + removeBlanks.title=Remove Blanks removeBlanks.header=Remove Blank Pages @@ -222,14 +245,12 @@ fileToPDF.submit=Convert to PDF #compress 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.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) +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/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/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/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/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