mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 09:29:24 +00:00
lots of improvements
This commit is contained in:
parent
a949019d5d
commit
ff9c0e9bd4
@ -56,7 +56,8 @@ public class BookletImpositionController {
|
|||||||
|
|
||||||
// Validate pages per sheet for booklet
|
// Validate pages per sheet for booklet
|
||||||
if (pagesPerSheet != 2 && pagesPerSheet != 4) {
|
if (pagesPerSheet != 2 && pagesPerSheet != 4) {
|
||||||
throw new IllegalArgumentException("pagesPerSheet must be 2 or 4 for booklet imposition");
|
throw new IllegalArgumentException(
|
||||||
|
"pagesPerSheet must be 2 or 4 for booklet imposition");
|
||||||
}
|
}
|
||||||
|
|
||||||
PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
||||||
@ -65,8 +66,11 @@ public class BookletImpositionController {
|
|||||||
// Step 1: Reorder pages for booklet (reusing logic from RearrangePagesPDFController)
|
// Step 1: Reorder pages for booklet (reusing logic from RearrangePagesPDFController)
|
||||||
List<Integer> bookletOrder = getBookletPageOrder(bookletType, totalPages);
|
List<Integer> bookletOrder = getBookletPageOrder(bookletType, totalPages);
|
||||||
|
|
||||||
// Step 2: Create new document with multi-page layout (reusing logic from MultiPageLayoutController)
|
// Step 2: Create new document with multi-page layout (reusing logic from
|
||||||
PDDocument newDocument = createBookletWithLayout(sourceDocument, bookletOrder, pagesPerSheet, addBorder, pageOrientation);
|
// MultiPageLayoutController)
|
||||||
|
PDDocument newDocument =
|
||||||
|
createBookletWithLayout(
|
||||||
|
sourceDocument, bookletOrder, pagesPerSheet, addBorder, pageOrientation);
|
||||||
|
|
||||||
sourceDocument.close();
|
sourceDocument.close();
|
||||||
|
|
||||||
@ -112,10 +116,16 @@ public class BookletImpositionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reused and adapted logic from MultiPageLayoutController
|
// Reused and adapted logic from MultiPageLayoutController
|
||||||
private PDDocument createBookletWithLayout(PDDocument sourceDocument, List<Integer> pageOrder,
|
private PDDocument createBookletWithLayout(
|
||||||
int pagesPerSheet, boolean addBorder, String pageOrientation) throws IOException {
|
PDDocument sourceDocument,
|
||||||
|
List<Integer> pageOrder,
|
||||||
|
int pagesPerSheet,
|
||||||
|
boolean addBorder,
|
||||||
|
String pageOrientation)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
PDDocument newDocument = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
PDDocument newDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
|
|
||||||
int cols = pagesPerSheet == 2 ? 2 : 2; // 2x1 for 2 pages, 2x2 for 4 pages
|
int cols = pagesPerSheet == 2 ? 2 : 2; // 2x1 for 2 pages, 2x2 for 4 pages
|
||||||
int rows = pagesPerSheet == 2 ? 1 : 2;
|
int rows = pagesPerSheet == 2 ? 1 : 2;
|
||||||
@ -125,16 +135,23 @@ public class BookletImpositionController {
|
|||||||
|
|
||||||
while (currentPageIndex < totalOrderedPages) {
|
while (currentPageIndex < totalOrderedPages) {
|
||||||
// Use landscape orientation for booklets (A4 landscape -> A5 portrait when folded)
|
// Use landscape orientation for booklets (A4 landscape -> A5 portrait when folded)
|
||||||
PDRectangle pageSize = "LANDSCAPE".equals(pageOrientation) ?
|
PDRectangle pageSize =
|
||||||
new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth()) : PDRectangle.A4;
|
"LANDSCAPE".equals(pageOrientation)
|
||||||
|
? new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())
|
||||||
|
: PDRectangle.A4;
|
||||||
PDPage newPage = new PDPage(pageSize);
|
PDPage newPage = new PDPage(pageSize);
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
|
|
||||||
float cellWidth = newPage.getMediaBox().getWidth() / cols;
|
float cellWidth = newPage.getMediaBox().getWidth() / cols;
|
||||||
float cellHeight = newPage.getMediaBox().getHeight() / rows;
|
float cellHeight = newPage.getMediaBox().getHeight() / rows;
|
||||||
|
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(
|
PDPageContentStream contentStream =
|
||||||
newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
|
new PDPageContentStream(
|
||||||
|
newDocument,
|
||||||
|
newPage,
|
||||||
|
PDPageContentStream.AppendMode.APPEND,
|
||||||
|
true,
|
||||||
|
true);
|
||||||
LayerUtility layerUtility = new LayerUtility(newDocument);
|
LayerUtility layerUtility = new LayerUtility(newDocument);
|
||||||
|
|
||||||
if (addBorder) {
|
if (addBorder) {
|
||||||
@ -143,7 +160,9 @@ public class BookletImpositionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Place pages on the current sheet
|
// Place pages on the current sheet
|
||||||
for (int sheetPosition = 0; sheetPosition < pagesPerSheet && currentPageIndex < totalOrderedPages; sheetPosition++) {
|
for (int sheetPosition = 0;
|
||||||
|
sheetPosition < pagesPerSheet && currentPageIndex < totalOrderedPages;
|
||||||
|
sheetPosition++) {
|
||||||
int sourcePageIndex = pageOrder.get(currentPageIndex);
|
int sourcePageIndex = pageOrder.get(currentPageIndex);
|
||||||
PDPage sourcePage = sourceDocument.getPage(sourcePageIndex);
|
PDPage sourcePage = sourceDocument.getPage(sourcePageIndex);
|
||||||
PDRectangle rect = sourcePage.getMediaBox();
|
PDRectangle rect = sourcePage.getMediaBox();
|
||||||
@ -156,14 +175,17 @@ public class BookletImpositionController {
|
|||||||
int colIndex = sheetPosition % cols;
|
int colIndex = sheetPosition % cols;
|
||||||
|
|
||||||
float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
|
float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
|
||||||
float y = newPage.getMediaBox().getHeight()
|
float y =
|
||||||
- ((rowIndex + 1) * cellHeight - (cellHeight - rect.getHeight() * scale) / 2);
|
newPage.getMediaBox().getHeight()
|
||||||
|
- ((rowIndex + 1) * cellHeight
|
||||||
|
- (cellHeight - rect.getHeight() * scale) / 2);
|
||||||
|
|
||||||
contentStream.saveGraphicsState();
|
contentStream.saveGraphicsState();
|
||||||
contentStream.transform(Matrix.getTranslateInstance(x, y));
|
contentStream.transform(Matrix.getTranslateInstance(x, y));
|
||||||
contentStream.transform(Matrix.getScaleInstance(scale, scale));
|
contentStream.transform(Matrix.getScaleInstance(scale, scale));
|
||||||
|
|
||||||
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, sourcePageIndex);
|
PDFormXObject formXObject =
|
||||||
|
layerUtility.importPageAsForm(sourceDocument, sourcePageIndex);
|
||||||
contentStream.drawForm(formXObject);
|
contentStream.drawForm(formXObject);
|
||||||
|
|
||||||
contentStream.restoreGraphicsState();
|
contentStream.restoreGraphicsState();
|
||||||
|
@ -13,8 +13,6 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||||
import stirling.software.common.configuration.AppConfig;
|
import stirling.software.common.configuration.AppConfig;
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
@ -76,7 +74,8 @@ public class ConfigController {
|
|||||||
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
|
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
|
||||||
|
|
||||||
// Server certificate settings
|
// Server certificate settings
|
||||||
configData.put("serverCertificateEnabled",
|
configData.put(
|
||||||
|
"serverCertificateEnabled",
|
||||||
serverCertificateService != null && serverCertificateService.isEnabled());
|
serverCertificateService != null && serverCertificateService.isEnabled());
|
||||||
|
|
||||||
// Legal settings
|
// Legal settings
|
||||||
|
@ -53,6 +53,7 @@ import org.bouncycastle.operator.InputDecryptorProvider;
|
|||||||
import org.bouncycastle.operator.OperatorCreationException;
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||||
import org.bouncycastle.pkcs.PKCSException;
|
import org.bouncycastle.pkcs.PKCSException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -70,12 +71,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||||
import stirling.software.common.service.ServerCertificateServiceInterface;
|
|
||||||
import stirling.software.common.annotations.AutoJobPostMapping;
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.service.ServerCertificateServiceInterface;
|
||||||
import stirling.software.common.util.ExceptionUtils;
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@ -106,7 +105,8 @@ public class CertSignController {
|
|||||||
|
|
||||||
public CertSignController(
|
public CertSignController(
|
||||||
CustomPDFDocumentFactory pdfDocumentFactory,
|
CustomPDFDocumentFactory pdfDocumentFactory,
|
||||||
@Autowired(required = false) ServerCertificateServiceInterface serverCertificateService) {
|
@Autowired(required = false)
|
||||||
|
ServerCertificateServiceInterface serverCertificateService) {
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
this.serverCertificateService = serverCertificateService;
|
this.serverCertificateService = serverCertificateService;
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,14 @@ import org.springframework.stereotype.Component;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.service.ServerCertificateService;
|
import stirling.software.common.service.ServerCertificateServiceInterface;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ServerCertificateInitializer {
|
public class ServerCertificateInitializer {
|
||||||
|
|
||||||
private final ServerCertificateService serverCertificateService;
|
private final ServerCertificateServiceInterface serverCertificateService;
|
||||||
|
|
||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
public void initializeServerCertificate() {
|
public void initializeServerCertificate() {
|
||||||
|
@ -112,8 +112,7 @@ public class ServerCertificateController {
|
|||||||
@GetMapping("/certificate")
|
@GetMapping("/certificate")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Download server certificate",
|
summary = "Download server certificate",
|
||||||
description =
|
description = "Download the server certificate in DER format for validation purposes")
|
||||||
"Download the server certificate in DER format for validation purposes")
|
|
||||||
public ResponseEntity<byte[]> getServerCertificate() {
|
public ResponseEntity<byte[]> getServerCertificate() {
|
||||||
try {
|
try {
|
||||||
if (!serverCertificateService.hasServerCertificate()) {
|
if (!serverCertificateService.hasServerCertificate()) {
|
||||||
|
@ -11,8 +11,14 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||||
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
import org.bouncycastle.cert.X509CertificateHolder;
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
@ -186,6 +192,36 @@ public class ServerCertificateService implements ServerCertificateServiceInterfa
|
|||||||
new JcaX509v3CertificateBuilder(
|
new JcaX509v3CertificateBuilder(
|
||||||
subject, serialNumber, notBefore, notAfter, subject, keyPair.getPublic());
|
subject, serialNumber, notBefore, notAfter, subject, keyPair.getPublic());
|
||||||
|
|
||||||
|
// Add PDF-specific certificate extensions for optimal PDF signing compatibility
|
||||||
|
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
|
||||||
|
|
||||||
|
// 1) End-entity certificate, not a CA (critical)
|
||||||
|
certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
|
||||||
|
|
||||||
|
// 2) Key usage for PDF digital signatures (critical)
|
||||||
|
certBuilder.addExtension(
|
||||||
|
Extension.keyUsage,
|
||||||
|
true,
|
||||||
|
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation));
|
||||||
|
|
||||||
|
// 3) Extended key usage for document signing (non-critical, widely accepted)
|
||||||
|
certBuilder.addExtension(
|
||||||
|
Extension.extendedKeyUsage,
|
||||||
|
false,
|
||||||
|
new ExtendedKeyUsage(KeyPurposeId.id_kp_codeSigning));
|
||||||
|
|
||||||
|
// 4) Subject Key Identifier for chain building (non-critical)
|
||||||
|
certBuilder.addExtension(
|
||||||
|
Extension.subjectKeyIdentifier,
|
||||||
|
false,
|
||||||
|
extUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
|
||||||
|
|
||||||
|
// 5) Authority Key Identifier for self-signed cert (non-critical)
|
||||||
|
certBuilder.addExtension(
|
||||||
|
Extension.authorityKeyIdentifier,
|
||||||
|
false,
|
||||||
|
extUtils.createAuthorityKeyIdentifier(keyPair.getPublic()));
|
||||||
|
|
||||||
// Sign certificate
|
// Sign certificate
|
||||||
ContentSigner signer =
|
ContentSigner signer =
|
||||||
new JcaContentSignerBuilder("SHA256WithRSA")
|
new JcaContentSignerBuilder("SHA256WithRSA")
|
||||||
@ -213,5 +249,4 @@ public class ServerCertificateService implements ServerCertificateServiceInterfa
|
|||||||
keyStore.store(fos, DEFAULT_PASSWORD.toCharArray());
|
keyStore.store(fos, DEFAULT_PASSWORD.toCharArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user