Merge pull request #196 from Frooodle/compress

Compress abd cert sign
This commit is contained in:
Anthony Stirling 2023-05-21 23:29:21 +01:00 committed by GitHub
commit b44e036be3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 944 additions and 215 deletions

View file

@ -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

View file

@ -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 && \
@ -59,4 +35,3 @@ 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

View file

@ -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'

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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<byte[]> 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
// 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;
}
}
boolean sizeMet = false;
while (!sizeMet && optimizeLevel <= 4) {
// Prepare the Ghostscript command
List<String> 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 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());
command.add(tempOutputFile.toString());
int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command);
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);
}
}
}
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);

View file

@ -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<byte[]> 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<String> 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");
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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";
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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=Сжать

View file

@ -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

View file

@ -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=压缩

View file

@ -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;

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-award" viewBox="0 0 16 16">
<path d="M9.669.864 8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68L9.669.864zm1.196 1.193.684 1.365 1.086 1.072L12.387 6l.248 1.506-1.086 1.072-.684 1.365-1.51.229L8 10.874l-1.355-.702-1.51-.229-.684-1.365-1.086-1.072L3.614 6l-.25-1.506 1.087-1.072.684-1.365 1.51-.229L8 1.126l1.356.702 1.509.229z"/>
<path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1 4 11.794z"/>
</svg>

After

Width:  |  Height:  |  Size: 649 B

View file

@ -218,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function () {
</dialog>
</th:block>
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true'">
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true', notRequired=${notRequired} ?: false">
<script>
$(document).ready(function() {
$('form').submit(async function(event) {
@ -486,7 +486,7 @@ document.addEventListener("DOMContentLoaded", function () {
<div class="custom-file-chooser">
<div class="custom-file">
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple required>
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple th:classappend="${notRequired ? '' : 'required'}">
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
</div>
<div class="selected-files"></div>

View file

@ -186,7 +186,7 @@ function compareVersions(version1, version2) {
<li class="nav-item nav-item-separator"></li>
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='add-watermark' OR ${currentPage}=='remove-watermark' ? 'active' : ''">
<li class="nav-item dropdown" th:classappend="${currentPage}=='add-password' OR ${currentPage}=='remove-password' OR ${currentPage}=='add-watermark' OR ${currentPage}=='cert-sign' ? 'active' : ''">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="icon" src="images/shield-check.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text" th:text="#{navbar.security}"></span>
</a>
@ -195,12 +195,12 @@ function compareVersions(version1, version2) {
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-password', 'images/unlock.svg', 'home.removePassword.title', 'home.removePassword.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc')}"></div>
</div>
</li>
<li class="nav-item nav-item-separator"></li>
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='flatten' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' ? 'active' : ''">
<li class="nav-item dropdown" th:classappend="${currentPage}=='sign' OR ${currentPage}=='repair' OR ${currentPage}=='compare' OR ${currentPage}=='flatten' OR ${currentPage}=='remove-blanks' OR ${currentPage}=='extract-image-scans' OR ${currentPage}=='change-metadata' OR ${currentPage}=='add-image' OR ${currentPage}=='ocr-pdf' OR ${currentPage}=='change-permissions' OR ${currentPage}=='extract-images' OR ${currentPage}=='compress-pdf' ? 'active' : ''">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="icon" src="images/card-list.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;">
<span class="icon-text" th:text="#{navbar.other}"></span>

View file

@ -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 */
}
</style>
<body>
@ -130,6 +135,8 @@ filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg);
<div th:replace="~{fragments/card :: card(id='remove-blanks', cardTitle=#{home.removeBlanks.title}, cardText=#{home.removeBlanks.desc}, cardLink='remove-blanks', svgPath='images/blank-file.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='compare', cardTitle=#{home.compare.title}, cardText=#{home.compare.desc}, cardLink='compare', svgPath='images/scales.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='cert-sign', cardTitle=#{home.certSign.title}, cardText=#{home.certSign.desc}, cardLink='cert-sign', svgPath='images/award.svg')}"></div>
<script>
function toggleFavorite(element) {
var img = element.querySelector('img');

View file

@ -17,27 +17,28 @@
<h2 th:text="#{compress.header}"></h2>
<form action="#" th:action="@{/compress-pdf}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div>
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label>
<select name="optimizeLevel" id="optimizeLevel">
<option value="0" th:text="#{compress.selectText.2}"></option>
<option value="1" selected th:text="#{compress.selectText.3}"></option>
<option value="2" th:text="#{compress.selectText.4}"></option>
<option value="3" th:text="#{compress.selectText.5}"></option>
<div class="card mb-3">
<div class="card-body">
<h4 th:text="#{compress.selectText.1}"></h4>
<label for="optimizeLevel" th:text="#{compress.selectText.2}"></label>
<select name="optimizeLevel" id="optimizeLevel" class="form-control">
<option value="1">1</option>
<option value="2" selected>2</option>
<option value="3">3</option>
<option value="4" th:text="#{compress.selectText.3}"></option>
</select>
</div>
<div>
<input type="checkbox" name="fastWebView" id="fastWebView">
<label for="fastWebView" th:text="#{compress.selectText.6}"></label>
</div>
<div>
<input type="checkbox" name="jbig2Lossy" id="jbig2Lossy">
<label for="jbig2Lossy" th:text="#{compress.selectText.7}"></label>
<div class="card mb-3">
<div class="card-body">
<h4 th:text="#{compress.selectText.4}"></h4>
<label for="expectedOutputSize" th:text="#{compress.selectText.5}"></label>
<input type="text" name="expectedOutputSize" id="expectedOutputSize" min="1" class="form-control">
</div>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{compress.submit}"></button>
</form>
<p class="mt-3" th:text="#{compress.credit}"></p>
</div>
</div>
</div>

View file

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html th:lang="${#locale.toString()}"
th:lang-direction="#{language.direction}"
xmlns:th="http://www.thymeleaf.org">
<th:block
th:insert="~{fragments/common :: head(title=#{certSign.title})}"></th:block>
<body>
<div id="page-container">
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{certSign.header}"></h2>
<form action="/cert-sign" method="post"
enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{certSign.selectPDF}"></label>
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label for="certType" th:text="#{certSign.certType}"></label> <select
class="form-control" id="certType" name="certType">
<option value="" th:text="#{selectFillter}"></option>
<option value="PKCS12">PKCS12</option>
<option value="PEM">PEM</option>
</select>
</div>
<div class="form-group" id="p12Group" style="display: none;">
<label th:text="#{certSign.selectP12}"></label>
<div
th:replace="~{fragments/common :: fileSelector(name='p12', notRequired=true, multiple=false, accept='.p12,.pfx')}"></div>
</div>
<div id="pemGroup" style="display: none;">
<div class="form-group">
<label th:text="#{certSign.selectKey}"></label>
<div
th:replace="~{fragments/common :: fileSelector(name='key', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
</div>
<div class="form-group">
<label th:text="#{certSign.selectCert}"></label>
<div
th:replace="~{fragments/common :: fileSelector(name='cert', multiple=false, notRequired=true, accept='.pem,.der')}"></div>
</div>
</div>
<div class="form-group">
<label th:text="#{certSign.password}"></label> <input
type="password" class="form-control" id="password"
name="password">
</div>
<div class="form-group">
<label><input type="checkbox" id="showSignature"
name="showSignature" th:text="#{signCert.showSig}"></label>
</div>
<div id="signatureDetails" style="display: none;">
<div class="form-group">
<label for="reason" th:text="#{signCert.reason}"></label> <input type="text"
class="form-control" id="reason" name="reason">
</div>
<div class="form-group">
<label for="location" th:text="#{signCert.location}"></label> <input type="text"
class="form-control" id="location" name="location">
</div>
<div class="form-group">
<label for="name" th:text="#{signCert.name}"></label> <input type="text"
class="form-control" id="name" name="name">
</div>
<div class="form-group">
<label for="pageNumber" th:text="#{pageNum}"></label> <input
type="number" class="form-control" id="pageNumber"
name="pageNumber" min="1">
</div>
</div>
<script type="text/javascript">
document
.getElementById('certType')
.addEventListener(
'change',
function() {
var p12Group = document
.getElementById('p12Group');
var pemGroup = document
.getElementById('pemGroup');
if (this.value === 'PKCS12') {
p12Group.style.display = 'block';
pemGroup.style.display = 'none';
} else if (this.value === 'PEM') {
p12Group.style.display = 'none';
pemGroup.style.display = 'block';
} else {
p12Group.style.display = 'none';
pemGroup.style.display = 'none';
}
});
document
.getElementById('showSignature')
.addEventListener(
'change',
function() {
var signatureDetails = document
.getElementById('signatureDetails');
if (this.checked) {
signatureDetails.style.display = 'block';
} else {
signatureDetails.style.display = 'none';
}
});
</script>
<div class="form-group text-center">
<button type="submit" id="submitBtn" class="btn btn-primary"
th:text="#{certSign.submit}"></button>
</div>
</form>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>