mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-03-18 05:46:49 +01:00
formatting
This commit is contained in:
@@ -53,6 +53,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -61,198 +62,228 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class CertSignController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
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. Input:PDF Output:PDF Type:MF-SISO")
|
||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
String certType = request.getCertType();
|
||||
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
||||
MultipartFile certFile = request.getCertFile();
|
||||
MultipartFile p12File = request.getP12File();
|
||||
String password = request.getPassword();
|
||||
Boolean showSignature = request.isShowSignature();
|
||||
String reason = request.getReason();
|
||||
String location = request.getLocation();
|
||||
String name = request.getName();
|
||||
Integer pageNumber = request.getPageNumber();
|
||||
@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. Input:PDF Output:PDF Type:MF-SISO")
|
||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||
throws Exception {
|
||||
MultipartFile pdf = request.getFileInput();
|
||||
String certType = request.getCertType();
|
||||
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
||||
MultipartFile certFile = request.getCertFile();
|
||||
MultipartFile p12File = request.getP12File();
|
||||
String password = request.getPassword();
|
||||
Boolean showSignature = request.isShowSignature();
|
||||
String reason = request.getReason();
|
||||
String location = request.getLocation();
|
||||
String name = request.getName();
|
||||
Integer pageNumber = request.getPageNumber();
|
||||
|
||||
PrivateKey privateKey = null;
|
||||
X509Certificate cert = null;
|
||||
PrivateKey privateKey = null;
|
||||
X509Certificate cert = null;
|
||||
|
||||
if (certType != null) {
|
||||
logger.info("Cert type provided: {}", certType);
|
||||
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();
|
||||
if (!ks.isKeyEntry(alias)) {
|
||||
throw new IllegalArgumentException("The provided PKCS12 file does not contain a private key.");
|
||||
}
|
||||
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", BouncyCastleProvider.PROVIDER_NAME);
|
||||
if (isPEM(privateKeyFile.getBytes())) {
|
||||
privateKey = keyFactory
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes())));
|
||||
} else {
|
||||
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
|
||||
}
|
||||
if (certType != null) {
|
||||
logger.info("Cert type provided: {}", certType);
|
||||
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();
|
||||
if (!ks.isKeyEntry(alias)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The provided PKCS12 file does not contain a private key.");
|
||||
}
|
||||
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", BouncyCastleProvider.PROVIDER_NAME);
|
||||
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",
|
||||
BouncyCastleProvider.PROVIDER_NAME);
|
||||
if (isPEM(certFile.getBytes())) {
|
||||
cert = (X509Certificate) certFactory
|
||||
.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes())));
|
||||
} else {
|
||||
cert = (X509Certificate) certFactory
|
||||
.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
PDSignature signature = new PDSignature();
|
||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
|
||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_SHA1);
|
||||
signature.setName(name);
|
||||
signature.setLocation(location);
|
||||
signature.setReason(reason);
|
||||
signature.setSignDate(Calendar.getInstance());
|
||||
|
||||
// Load the PDF
|
||||
try (PDDocument document = PDDocument.load(pdf.getBytes())) {
|
||||
logger.info("Successfully loaded the provided PDF");
|
||||
SignatureOptions signatureOptions = new SignatureOptions();
|
||||
// Load certificate
|
||||
CertificateFactory certFactory =
|
||||
CertificateFactory.getInstance(
|
||||
"X.509", BouncyCastleProvider.PROVIDER_NAME);
|
||||
if (isPEM(certFile.getBytes())) {
|
||||
cert =
|
||||
(X509Certificate)
|
||||
certFactory.generateCertificate(
|
||||
new ByteArrayInputStream(
|
||||
parsePEM(certFile.getBytes())));
|
||||
} else {
|
||||
cert =
|
||||
(X509Certificate)
|
||||
certFactory.generateCertificate(
|
||||
new ByteArrayInputStream(certFile.getBytes()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
PDSignature signature = new PDSignature();
|
||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
|
||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_SHA1);
|
||||
signature.setName(name);
|
||||
signature.setLocation(location);
|
||||
signature.setReason(reason);
|
||||
signature.setSignDate(Calendar.getInstance());
|
||||
|
||||
// If you want to show the signature
|
||||
// Load the PDF
|
||||
try (PDDocument document = PDDocument.load(pdf.getBytes())) {
|
||||
logger.info("Successfully loaded the provided PDF");
|
||||
SignatureOptions signatureOptions = new SignatureOptions();
|
||||
|
||||
// ATTEMPT 2
|
||||
if (showSignature != null && showSignature) {
|
||||
PDPage page = document.getPage(pageNumber - 1);
|
||||
// If you want to show the signature
|
||||
|
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||
if (acroForm == null) {
|
||||
acroForm = new PDAcroForm(document);
|
||||
document.getDocumentCatalog().setAcroForm(acroForm);
|
||||
}
|
||||
// ATTEMPT 2
|
||||
if (showSignature != null && showSignature) {
|
||||
PDPage page = document.getPage(pageNumber - 1);
|
||||
|
||||
// Create a new signature field and widget
|
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||
if (acroForm == null) {
|
||||
acroForm = new PDAcroForm(document);
|
||||
document.getDocumentCatalog().setAcroForm(acroForm);
|
||||
}
|
||||
|
||||
PDSignatureField signatureField = new PDSignatureField(acroForm);
|
||||
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
|
||||
PDRectangle rect = new PDRectangle(100, 100, 200, 50); // Define the rectangle size here
|
||||
widget.setRectangle(rect);
|
||||
page.getAnnotations().add(widget);
|
||||
// Create a new signature field and widget
|
||||
|
||||
// Set the appearance for the signature field
|
||||
PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary();
|
||||
PDAppearanceStream appearanceStream = new PDAppearanceStream(document);
|
||||
appearanceStream.setResources(new PDResources());
|
||||
appearanceStream.setBBox(rect);
|
||||
appearanceDict.setNormalAppearance(appearanceStream);
|
||||
widget.setAppearance(appearanceDict);
|
||||
PDSignatureField signatureField = new PDSignatureField(acroForm);
|
||||
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
|
||||
PDRectangle rect =
|
||||
new PDRectangle(100, 100, 200, 50); // Define the rectangle size here
|
||||
widget.setRectangle(rect);
|
||||
page.getAnnotations().add(widget);
|
||||
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(document, appearanceStream)) {
|
||||
contentStream.beginText();
|
||||
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
|
||||
contentStream.newLineAtOffset(110, 130);
|
||||
contentStream.showText("Digitally signed by: " + (name != null ? name : "Unknown"));
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
contentStream.showText("Date: " + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z").format(new Date()));
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
if (reason != null && !reason.isEmpty()) {
|
||||
contentStream.showText("Reason: " + reason);
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
}
|
||||
if (location != null && !location.isEmpty()) {
|
||||
contentStream.showText("Location: " + location);
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
}
|
||||
contentStream.endText();
|
||||
}
|
||||
// Set the appearance for the signature field
|
||||
PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary();
|
||||
PDAppearanceStream appearanceStream = new PDAppearanceStream(document);
|
||||
appearanceStream.setResources(new PDResources());
|
||||
appearanceStream.setBBox(rect);
|
||||
appearanceDict.setNormalAppearance(appearanceStream);
|
||||
widget.setAppearance(appearanceDict);
|
||||
|
||||
// Add the widget annotation to the page
|
||||
page.getAnnotations().add(widget);
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(document, appearanceStream)) {
|
||||
contentStream.beginText();
|
||||
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
|
||||
contentStream.newLineAtOffset(110, 130);
|
||||
contentStream.showText(
|
||||
"Digitally signed by: " + (name != null ? name : "Unknown"));
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
contentStream.showText(
|
||||
"Date: "
|
||||
+ new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z")
|
||||
.format(new Date()));
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
if (reason != null && !reason.isEmpty()) {
|
||||
contentStream.showText("Reason: " + reason);
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
}
|
||||
if (location != null && !location.isEmpty()) {
|
||||
contentStream.showText("Location: " + location);
|
||||
contentStream.newLineAtOffset(0, -15);
|
||||
}
|
||||
contentStream.endText();
|
||||
}
|
||||
|
||||
// Add the signature field to the acroform
|
||||
acroForm.getFields().add(signatureField);
|
||||
// Add the widget annotation to the page
|
||||
page.getAnnotations().add(widget);
|
||||
|
||||
// Handle multiple signatures by ensuring a unique field name
|
||||
String baseFieldName = "Signature";
|
||||
String signatureFieldName = baseFieldName;
|
||||
int suffix = 1;
|
||||
while (acroForm.getField(signatureFieldName) != null) {
|
||||
suffix++;
|
||||
signatureFieldName = baseFieldName + suffix;
|
||||
}
|
||||
signatureField.setPartialName(signatureFieldName);
|
||||
}
|
||||
|
||||
document.addSignature(signature, signatureOptions);
|
||||
logger.info("Signature added to the PDF document");
|
||||
// External signing
|
||||
ExternalSigningSupport externalSigning = document
|
||||
.saveIncrementalForExternalSigning(new ByteArrayOutputStream());
|
||||
// Add the signature field to the acroform
|
||||
acroForm.getFields().add(signatureField);
|
||||
|
||||
byte[] content = IOUtils.toByteArray(externalSigning.getContent());
|
||||
// Handle multiple signatures by ensuring a unique field name
|
||||
String baseFieldName = "Signature";
|
||||
String signatureFieldName = baseFieldName;
|
||||
int suffix = 1;
|
||||
while (acroForm.getField(signatureFieldName) != null) {
|
||||
suffix++;
|
||||
signatureFieldName = baseFieldName + suffix;
|
||||
}
|
||||
signatureField.setPartialName(signatureFieldName);
|
||||
}
|
||||
|
||||
// Using BouncyCastle to sign
|
||||
CMSTypedData cmsData = new CMSProcessableByteArray(content);
|
||||
document.addSignature(signature, signatureOptions);
|
||||
logger.info("Signature added to the PDF document");
|
||||
// External signing
|
||||
ExternalSigningSupport externalSigning =
|
||||
document.saveIncrementalForExternalSigning(new ByteArrayOutputStream());
|
||||
|
||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey);
|
||||
byte[] content = IOUtils.toByteArray(externalSigning.getContent());
|
||||
|
||||
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
|
||||
new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build())
|
||||
.build(signer, cert));
|
||||
// Using BouncyCastle to sign
|
||||
CMSTypedData cmsData = new CMSProcessableByteArray(content);
|
||||
|
||||
gen.addCertificates(new JcaCertStore(Collections.singletonList(cert)));
|
||||
CMSSignedData signedData = gen.generate(cmsData, false);
|
||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||
ContentSigner signer =
|
||||
new JcaContentSignerBuilder("SHA256withRSA")
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build(privateKey);
|
||||
|
||||
byte[] cmsSignature = signedData.getEncoded();
|
||||
logger.info("About to sign content using BouncyCastle");
|
||||
externalSigning.setSignature(cmsSignature);
|
||||
logger.info("Signature set successfully");
|
||||
gen.addSignerInfoGenerator(
|
||||
new JcaSignerInfoGeneratorBuilder(
|
||||
new JcaDigestCalculatorProviderBuilder()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build())
|
||||
.build(signer, cert));
|
||||
|
||||
// After setting the signature, return the resultant PDF
|
||||
try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) {
|
||||
document.save(signedPdfOutput);
|
||||
return WebResponseUtils.boasToWebResponse(signedPdfOutput,
|
||||
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf");
|
||||
gen.addCertificates(new JcaCertStore(Collections.singletonList(cert)));
|
||||
CMSSignedData signedData = gen.generate(cmsData, false);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
byte[] cmsSignature = signedData.getEncoded();
|
||||
logger.info("About to sign content using BouncyCastle");
|
||||
externalSigning.setSignature(cmsSignature);
|
||||
logger.info("Signature set successfully");
|
||||
|
||||
return null;
|
||||
}
|
||||
// After setting the signature, return the resultant PDF
|
||||
try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) {
|
||||
document.save(signedPdfOutput);
|
||||
return WebResponseUtils.boasToWebResponse(
|
||||
signedPdfOutput,
|
||||
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf");
|
||||
|
||||
private byte[] parsePEM(byte[] content) throws IOException {
|
||||
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
||||
return pemReader.readPemObject().getContent();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
private boolean isPEM(byte[] content) {
|
||||
String contentStr = new String(content);
|
||||
return contentStr.contains("-----BEGIN") && contentStr.contains("-----END");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,23 +72,22 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/security")
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class GetInfoOnPDF {
|
||||
|
||||
static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
||||
static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
||||
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
||||
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request)
|
||||
throws IOException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
try (
|
||||
PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream());
|
||||
) {
|
||||
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
try (PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); ) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
ObjectNode jsonOutput = objectMapper.createObjectNode();
|
||||
|
||||
@@ -100,8 +99,7 @@ public class GetInfoOnPDF {
|
||||
ObjectNode compliancy = objectMapper.createObjectNode();
|
||||
ObjectNode encryption = objectMapper.createObjectNode();
|
||||
ObjectNode other = objectMapper.createObjectNode();
|
||||
|
||||
|
||||
|
||||
metadata.put("Title", info.getTitle());
|
||||
metadata.put("Author", info.getAuthor());
|
||||
metadata.put("Subject", info.getSubject());
|
||||
@@ -111,14 +109,11 @@ public class GetInfoOnPDF {
|
||||
metadata.put("CreationDate", formatDate(info.getCreationDate()));
|
||||
metadata.put("ModificationDate", formatDate(info.getModificationDate()));
|
||||
jsonOutput.set("Metadata", metadata);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Total file size of the PDF
|
||||
long fileSizeInBytes = inputFile.getSize();
|
||||
basicInfo.put("FileSizeInBytes", fileSizeInBytes);
|
||||
|
||||
|
||||
// Number of words, paragraphs, and images in the entire document
|
||||
String fullText = new PDFTextStripper().getText(pdfBoxDoc);
|
||||
String[] words = fullText.split("\\s+");
|
||||
@@ -129,8 +124,7 @@ public class GetInfoOnPDF {
|
||||
// Number of characters in the entire document (including spaces and special characters)
|
||||
int charCount = fullText.length();
|
||||
basicInfo.put("CharacterCount", charCount);
|
||||
|
||||
|
||||
|
||||
// Initialize the flags and types
|
||||
boolean hasCompression = false;
|
||||
String compressionType = "None";
|
||||
@@ -147,26 +141,21 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
}
|
||||
basicInfo.put("Compression", hasCompression);
|
||||
if(hasCompression)
|
||||
basicInfo.put("CompressionType", compressionType);
|
||||
|
||||
if (hasCompression) basicInfo.put("CompressionType", compressionType);
|
||||
|
||||
String language = pdfBoxDoc.getDocumentCatalog().getLanguage();
|
||||
basicInfo.put("Language", language);
|
||||
basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages());
|
||||
|
||||
|
||||
|
||||
PDDocumentCatalog catalog = pdfBoxDoc.getDocumentCatalog();
|
||||
String pageMode = catalog.getPageMode().name();
|
||||
|
||||
|
||||
// Document Information using PDFBox
|
||||
docInfoNode.put("PDF version", pdfBoxDoc.getVersion());
|
||||
docInfoNode.put("Trapped", info.getTrapped());
|
||||
docInfoNode.put("Page Mode", getPageModeDescription(pageMode));;
|
||||
|
||||
docInfoNode.put("Page Mode", getPageModeDescription(pageMode));
|
||||
;
|
||||
|
||||
|
||||
|
||||
|
||||
PDAcroForm acroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm();
|
||||
|
||||
ObjectNode formFieldsNode = objectMapper.createObjectNode();
|
||||
@@ -177,41 +166,37 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
jsonOutput.set("FormFields", formFieldsNode);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//embeed files TODO size
|
||||
if(catalog.getNames() != null) {
|
||||
PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
|
||||
|
||||
ArrayNode embeddedFilesArray = objectMapper.createArrayNode();
|
||||
if (efTree != null) {
|
||||
Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
|
||||
if (efMap != null) {
|
||||
for (Map.Entry<String, PDComplexFileSpecification> entry : efMap.entrySet()) {
|
||||
ObjectNode embeddedFileNode = objectMapper.createObjectNode();
|
||||
embeddedFileNode.put("Name", entry.getKey());
|
||||
PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile();
|
||||
if (embeddedFile != null) {
|
||||
embeddedFileNode.put("FileSize", embeddedFile.getLength()); // size in bytes
|
||||
}
|
||||
embeddedFilesArray.add(embeddedFileNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
other.set("EmbeddedFiles", embeddedFilesArray);
|
||||
// embeed files TODO size
|
||||
if (catalog.getNames() != null) {
|
||||
PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
|
||||
|
||||
ArrayNode embeddedFilesArray = objectMapper.createArrayNode();
|
||||
if (efTree != null) {
|
||||
Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
|
||||
if (efMap != null) {
|
||||
for (Map.Entry<String, PDComplexFileSpecification> entry :
|
||||
efMap.entrySet()) {
|
||||
ObjectNode embeddedFileNode = objectMapper.createObjectNode();
|
||||
embeddedFileNode.put("Name", entry.getKey());
|
||||
PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile();
|
||||
if (embeddedFile != null) {
|
||||
embeddedFileNode.put(
|
||||
"FileSize", embeddedFile.getLength()); // size in bytes
|
||||
}
|
||||
embeddedFilesArray.add(embeddedFileNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
other.set("EmbeddedFiles", embeddedFilesArray);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//attachments TODO size
|
||||
// attachments TODO size
|
||||
ArrayNode attachmentsArray = objectMapper.createArrayNode();
|
||||
for (PDPage page : pdfBoxDoc.getPages()) {
|
||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||
if (annotation instanceof PDAnnotationFileAttachment) {
|
||||
PDAnnotationFileAttachment fileAttachmentAnnotation = (PDAnnotationFileAttachment) annotation;
|
||||
PDAnnotationFileAttachment fileAttachmentAnnotation =
|
||||
(PDAnnotationFileAttachment) annotation;
|
||||
|
||||
ObjectNode attachmentNode = objectMapper.createObjectNode();
|
||||
attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName());
|
||||
@@ -223,7 +208,7 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
other.set("Attachments", attachmentsArray);
|
||||
|
||||
//Javascript
|
||||
// Javascript
|
||||
PDDocumentNameDictionary namesDict = catalog.getNames();
|
||||
ArrayNode javascriptArray = objectMapper.createArrayNode();
|
||||
|
||||
@@ -254,9 +239,9 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
other.set("JavaScript", javascriptArray);
|
||||
|
||||
|
||||
//TODO size
|
||||
PDOptionalContentProperties ocProperties = pdfBoxDoc.getDocumentCatalog().getOCProperties();
|
||||
// TODO size
|
||||
PDOptionalContentProperties ocProperties =
|
||||
pdfBoxDoc.getDocumentCatalog().getOCProperties();
|
||||
ArrayNode layersArray = objectMapper.createArrayNode();
|
||||
|
||||
if (ocProperties != null) {
|
||||
@@ -268,34 +253,38 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
|
||||
other.set("Layers", layersArray);
|
||||
|
||||
//TODO Security
|
||||
|
||||
|
||||
// TODO Security
|
||||
|
||||
|
||||
|
||||
PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot();
|
||||
PDStructureTreeRoot structureTreeRoot =
|
||||
pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot();
|
||||
ArrayNode structureTreeArray;
|
||||
try {
|
||||
if(structureTreeRoot != null) {
|
||||
structureTreeArray = exploreStructureTree(structureTreeRoot.getKids());
|
||||
other.set("StructureTree", structureTreeArray);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
if (structureTreeRoot != null) {
|
||||
structureTreeArray = exploreStructureTree(structureTreeRoot.getKids());
|
||||
other.set("StructureTree", structureTreeArray);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
|
||||
boolean isPdfXCompliant = checkForStandard(pdfBoxDoc, "PDF/X");
|
||||
boolean isPdfECompliant = checkForStandard(pdfBoxDoc, "PDF/E");
|
||||
boolean isPdfVTCompliant = checkForStandard(pdfBoxDoc, "PDF/VT");
|
||||
boolean isPdfUACompliant = checkForStandard(pdfBoxDoc, "PDF/UA");
|
||||
boolean isPdfBCompliant = checkForStandard(pdfBoxDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard.
|
||||
boolean isPdfSECCompliant = checkForStandard(pdfBoxDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021.
|
||||
|
||||
boolean isPdfBCompliant =
|
||||
checkForStandard(
|
||||
pdfBoxDoc,
|
||||
"PDF/B"); // If you want to check for PDF/Broadcast, though this isn't
|
||||
// an official ISO standard.
|
||||
boolean isPdfSECCompliant =
|
||||
checkForStandard(
|
||||
pdfBoxDoc,
|
||||
"PDF/SEC"); // This might not be effective since PDF/SEC was under
|
||||
// development in 2021.
|
||||
|
||||
compliancy.put("IsPDF/ACompliant", isPdfACompliant);
|
||||
compliancy.put("IsPDF/XCompliant", isPdfXCompliant);
|
||||
compliancy.put("IsPDF/ECompliant", isPdfECompliant);
|
||||
@@ -304,10 +293,6 @@ public class GetInfoOnPDF {
|
||||
compliancy.put("IsPDF/BCompliant", isPdfBCompliant);
|
||||
compliancy.put("IsPDF/SECCompliant", isPdfSECCompliant);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PDOutlineNode root = pdfBoxDoc.getDocumentCatalog().getDocumentOutline();
|
||||
ArrayNode bookmarksArray = objectMapper.createArrayNode();
|
||||
|
||||
@@ -318,33 +303,29 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
|
||||
other.set("Bookmarks/Outline/TOC", bookmarksArray);
|
||||
|
||||
|
||||
|
||||
PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata();
|
||||
|
||||
String xmpString = null;
|
||||
|
||||
if (pdMetadata != null) {
|
||||
try {
|
||||
COSInputStream is = pdMetadata.createInputStream();
|
||||
DomXmpParser domXmpParser = new DomXmpParser();
|
||||
XMPMetadata xmpMeta = domXmpParser.parse(is);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMeta, os, true);
|
||||
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
|
||||
} catch (XmpParsingException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
other.put("XMPMetadata", xmpString);
|
||||
PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata();
|
||||
|
||||
String xmpString = null;
|
||||
|
||||
if (pdMetadata != null) {
|
||||
try {
|
||||
COSInputStream is = pdMetadata.createInputStream();
|
||||
DomXmpParser domXmpParser = new DomXmpParser();
|
||||
XMPMetadata xmpMeta = domXmpParser.parse(is);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMeta, os, true);
|
||||
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
|
||||
} catch (XmpParsingException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
other.put("XMPMetadata", xmpString);
|
||||
|
||||
|
||||
|
||||
if (pdfBoxDoc.isEncrypted()) {
|
||||
encryption.put("IsEncrypted", true);
|
||||
encryption.put("IsEncrypted", true);
|
||||
|
||||
// Retrieve encryption details using getEncryption()
|
||||
PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();
|
||||
@@ -353,31 +334,30 @@ public class GetInfoOnPDF {
|
||||
AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission();
|
||||
if (ap != null) {
|
||||
ObjectNode permissionsNode = objectMapper.createObjectNode();
|
||||
|
||||
|
||||
permissionsNode.put("CanAssembleDocument", ap.canAssembleDocument());
|
||||
permissionsNode.put("CanExtractContent", ap.canExtractContent());
|
||||
permissionsNode.put("CanExtractForAccessibility", ap.canExtractForAccessibility());
|
||||
permissionsNode.put(
|
||||
"CanExtractForAccessibility", ap.canExtractForAccessibility());
|
||||
permissionsNode.put("CanFillInForm", ap.canFillInForm());
|
||||
permissionsNode.put("CanModify", ap.canModify());
|
||||
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
|
||||
permissionsNode.put("CanPrint", ap.canPrint());
|
||||
permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded());
|
||||
|
||||
encryption.set("Permissions", permissionsNode); // set the node under "Permissions"
|
||||
}
|
||||
encryption.set(
|
||||
"Permissions", permissionsNode); // set the node under "Permissions"
|
||||
}
|
||||
// Add other encryption-related properties as needed
|
||||
} else {
|
||||
encryption.put("IsEncrypted", false);
|
||||
encryption.put("IsEncrypted", false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
ObjectNode pageInfoParent = objectMapper.createObjectNode();
|
||||
for (int pageNum = 0; pageNum < pdfBoxDoc.getNumberOfPages(); pageNum++) {
|
||||
ObjectNode pageInfo = objectMapper.createObjectNode();
|
||||
|
||||
// Retrieve the page
|
||||
// Retrieve the page
|
||||
PDPage page = pdfBoxDoc.getPage(pageNum);
|
||||
|
||||
// Page-level Information
|
||||
@@ -387,20 +367,20 @@ public class GetInfoOnPDF {
|
||||
float height = mediaBox.getHeight();
|
||||
|
||||
ObjectNode sizeInfo = objectMapper.createObjectNode();
|
||||
|
||||
|
||||
getDimensionInfo(sizeInfo, width, height);
|
||||
|
||||
|
||||
sizeInfo.put("Standard Page", getPageSize(width, height));
|
||||
pageInfo.set("Size", sizeInfo);
|
||||
|
||||
|
||||
pageInfo.put("Rotation", page.getRotation());
|
||||
pageInfo.put("Page Orientation", getPageOrientation(width, height));
|
||||
|
||||
|
||||
// Boxes
|
||||
pageInfo.put("MediaBox", mediaBox.toString());
|
||||
|
||||
// Assuming the following boxes are defined for your document; if not, you may get null values.
|
||||
// Assuming the following boxes are defined for your document; if not, you may get
|
||||
// null values.
|
||||
PDRectangle cropBox = page.getCropBox();
|
||||
pageInfo.put("CropBox", cropBox == null ? "Undefined" : cropBox.toString());
|
||||
|
||||
@@ -416,13 +396,13 @@ public class GetInfoOnPDF {
|
||||
// Content Extraction
|
||||
PDFTextStripper textStripper = new PDFTextStripper();
|
||||
textStripper.setStartPage(pageNum + 1);
|
||||
textStripper.setEndPage(pageNum +1);
|
||||
textStripper.setEndPage(pageNum + 1);
|
||||
String pageText = textStripper.getText(pdfBoxDoc);
|
||||
|
||||
|
||||
pageInfo.put("Text Characters Count", pageText.length()); //
|
||||
|
||||
// Annotations
|
||||
|
||||
|
||||
List<PDAnnotation> annotations = page.getAnnotations();
|
||||
|
||||
int subtypeCount = 0;
|
||||
@@ -430,10 +410,10 @@ public class GetInfoOnPDF {
|
||||
|
||||
for (PDAnnotation annotation : annotations) {
|
||||
if (annotation.getSubtype() != null) {
|
||||
subtypeCount++; // Increase subtype count
|
||||
subtypeCount++; // Increase subtype count
|
||||
}
|
||||
if (annotation.getContents() != null) {
|
||||
contentsCount++; // Increase contents count
|
||||
contentsCount++; // Increase contents count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,26 +422,25 @@ public class GetInfoOnPDF {
|
||||
annotationsObject.put("SubtypeCount", subtypeCount);
|
||||
annotationsObject.put("ContentsCount", contentsCount);
|
||||
pageInfo.set("Annotations", annotationsObject);
|
||||
|
||||
|
||||
|
||||
|
||||
// Images (simplified)
|
||||
// This part is non-trivial as images can be embedded in multiple ways in a PDF.
|
||||
// Here is a basic structure to recognize image XObjects on a page.
|
||||
ArrayNode imagesArray = objectMapper.createArrayNode();
|
||||
PDResources resources = page.getResources();
|
||||
|
||||
|
||||
for (COSName name : resources.getXObjectNames()) {
|
||||
PDXObject xObject = resources.getXObject(name);
|
||||
if (xObject instanceof PDImageXObject) {
|
||||
PDImageXObject image = (PDImageXObject) xObject;
|
||||
|
||||
|
||||
ObjectNode imageNode = objectMapper.createObjectNode();
|
||||
imageNode.put("Width", image.getWidth());
|
||||
imageNode.put("Height", image.getHeight());
|
||||
if(image.getMetadata() != null && image.getMetadata().getFile() != null && image.getMetadata().getFile().getFile() != null) {
|
||||
imageNode.put("Name", image.getMetadata().getFile().getFile());
|
||||
if (image.getMetadata() != null
|
||||
&& image.getMetadata().getFile() != null
|
||||
&& image.getMetadata().getFile().getFile() != null) {
|
||||
imageNode.put("Name", image.getMetadata().getFile().getFile());
|
||||
}
|
||||
if (image.getColorSpace() != null) {
|
||||
imageNode.put("ColorSpace", image.getColorSpace().getName());
|
||||
@@ -472,10 +451,9 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
pageInfo.set("Images", imagesArray);
|
||||
|
||||
|
||||
// Links
|
||||
ArrayNode linksArray = objectMapper.createArrayNode();
|
||||
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
||||
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
||||
|
||||
for (PDAnnotation annotation : annotations) {
|
||||
if (annotation instanceof PDAnnotationLink) {
|
||||
@@ -483,7 +461,7 @@ public class GetInfoOnPDF {
|
||||
if (linkAnnotation.getAction() instanceof PDActionURI) {
|
||||
PDActionURI uriAction = (PDActionURI) linkAnnotation.getAction();
|
||||
String uri = uriAction.getURI();
|
||||
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
||||
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,8 +473,7 @@ public class GetInfoOnPDF {
|
||||
linksArray.add(linkNode);
|
||||
}
|
||||
pageInfo.set("Links", linksArray);
|
||||
|
||||
|
||||
|
||||
// Fonts
|
||||
ArrayNode fontsArray = objectMapper.createArrayNode();
|
||||
Map<String, ObjectNode> uniqueFontsMap = new HashMap<>();
|
||||
@@ -526,13 +503,13 @@ public class GetInfoOnPDF {
|
||||
fontNode.put("IsNonsymbolic", (flags & 32) != 0);
|
||||
|
||||
fontNode.put("FontFamily", fontDescriptor.getFontFamily());
|
||||
// Font stretch and BBox are not directly available in PDFBox's API, so these are omitted for simplicity
|
||||
// Font stretch and BBox are not directly available in PDFBox's API, so
|
||||
// these are omitted for simplicity
|
||||
fontNode.put("FontWeight", fontDescriptor.getFontWeight());
|
||||
}
|
||||
|
||||
|
||||
// Create a unique key for this font node based on its attributes
|
||||
String uniqueKey = fontNode.toString();
|
||||
String uniqueKey = fontNode.toString();
|
||||
|
||||
// Increment count if this font exists, or initialize it if new
|
||||
if (uniqueFontsMap.containsKey(uniqueKey)) {
|
||||
@@ -551,17 +528,7 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
|
||||
pageInfo.set("Fonts", fontsArray);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Access resources dictionary
|
||||
ArrayNode colorSpacesArray = objectMapper.createArrayNode();
|
||||
|
||||
@@ -572,7 +539,7 @@ public class GetInfoOnPDF {
|
||||
PDICCBased iccBased = (PDICCBased) colorSpace;
|
||||
PDStream iccData = iccBased.getPDStream();
|
||||
byte[] iccBytes = iccData.toByteArray();
|
||||
|
||||
|
||||
// TODO: Further decode and analyze the ICC data if needed
|
||||
ObjectNode iccProfileNode = objectMapper.createObjectNode();
|
||||
iccProfileNode.put("ICC Profile Length", iccBytes.length);
|
||||
@@ -580,14 +547,14 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
}
|
||||
pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray);
|
||||
|
||||
|
||||
// Other XObjects
|
||||
Map<String, Integer> xObjectCountMap = new HashMap<>(); // To store the count for each type
|
||||
Map<String, Integer> xObjectCountMap =
|
||||
new HashMap<>(); // To store the count for each type
|
||||
for (COSName name : resources.getXObjectNames()) {
|
||||
PDXObject xObject = resources.getXObject(name);
|
||||
String xObjectType;
|
||||
|
||||
|
||||
if (xObject instanceof PDImageXObject) {
|
||||
xObjectType = "Image";
|
||||
} else if (xObject instanceof PDFormXObject) {
|
||||
@@ -597,7 +564,8 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
|
||||
// Increment the count for this type in the map
|
||||
xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);
|
||||
xObjectCountMap.put(
|
||||
xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);
|
||||
}
|
||||
|
||||
// Add the count map to pageInfo (or wherever you want to store it)
|
||||
@@ -606,14 +574,11 @@ public class GetInfoOnPDF {
|
||||
xObjectCountNode.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
pageInfo.set("XObjectCounts", xObjectCountNode);
|
||||
|
||||
|
||||
|
||||
|
||||
ArrayNode multimediaArray = objectMapper.createArrayNode();
|
||||
|
||||
for (PDAnnotation annotation : annotations) {
|
||||
if ("RichMedia".equals(annotation.getSubtype())) {
|
||||
if ("RichMedia".equals(annotation.getSubtype())) {
|
||||
ObjectNode multimediaNode = objectMapper.createObjectNode();
|
||||
// Extract details from the annotation as needed
|
||||
multimediaArray.add(multimediaNode);
|
||||
@@ -622,32 +587,29 @@ public class GetInfoOnPDF {
|
||||
|
||||
pageInfo.set("Multimedia", multimediaArray);
|
||||
|
||||
|
||||
|
||||
pageInfoParent.set("Page " + (pageNum+1), pageInfo);
|
||||
pageInfoParent.set("Page " + (pageNum + 1), pageInfo);
|
||||
}
|
||||
|
||||
|
||||
jsonOutput.set("BasicInfo", basicInfo);
|
||||
jsonOutput.set("DocumentInfo", docInfoNode);
|
||||
jsonOutput.set("Compliancy", compliancy);
|
||||
jsonOutput.set("Encryption", encryption);
|
||||
jsonOutput.set("Other", other);
|
||||
jsonOutput.set("PerPageInfo", pageInfoParent);
|
||||
|
||||
|
||||
|
||||
|
||||
// Save JSON to file
|
||||
String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput);
|
||||
|
||||
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(jsonString.getBytes(StandardCharsets.UTF_8), "response.json", MediaType.APPLICATION_JSON);
|
||||
|
||||
String jsonString =
|
||||
objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput);
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
jsonString.getBytes(StandardCharsets.UTF_8),
|
||||
"response.json",
|
||||
MediaType.APPLICATION_JSON);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
||||
@@ -665,7 +627,7 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
}
|
||||
|
||||
public String getPageOrientation(double width, double height) {
|
||||
public String getPageOrientation(double width, double height) {
|
||||
if (width > height) {
|
||||
return "Landscape";
|
||||
} else if (height > width) {
|
||||
@@ -674,6 +636,7 @@ public class GetInfoOnPDF {
|
||||
return "Square";
|
||||
}
|
||||
}
|
||||
|
||||
public String getPageSize(float width, float height) {
|
||||
// Define standard page sizes
|
||||
Map<String, PDRectangle> standardSizes = new HashMap<>();
|
||||
@@ -696,21 +659,22 @@ public class GetInfoOnPDF {
|
||||
return "Custom";
|
||||
}
|
||||
|
||||
private boolean isCloseToSize(float width, float height, float standardWidth, float standardHeight) {
|
||||
private boolean isCloseToSize(
|
||||
float width, float height, float standardWidth, float standardHeight) {
|
||||
float tolerance = 1.0f; // You can adjust the tolerance as needed
|
||||
return Math.abs(width - standardWidth) <= tolerance && Math.abs(height - standardHeight) <= tolerance;
|
||||
return Math.abs(width - standardWidth) <= tolerance
|
||||
&& Math.abs(height - standardHeight) <= tolerance;
|
||||
}
|
||||
|
||||
|
||||
public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) {
|
||||
public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) {
|
||||
float ppi = 72; // Points Per Inch
|
||||
|
||||
|
||||
float widthInInches = width / ppi;
|
||||
float heightInInches = height / ppi;
|
||||
|
||||
|
||||
float widthInCm = widthInInches * 2.54f;
|
||||
float heightInCm = heightInInches * 2.54f;
|
||||
|
||||
|
||||
dimensionInfo.put("Width (px)", String.format("%.2f", width));
|
||||
dimensionInfo.put("Height (px)", String.format("%.2f", height));
|
||||
dimensionInfo.put("Width (in)", String.format("%.2f", widthInInches));
|
||||
@@ -720,33 +684,33 @@ public class GetInfoOnPDF {
|
||||
return dimensionInfo;
|
||||
}
|
||||
|
||||
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
||||
// Check XMP Metadata
|
||||
try {
|
||||
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
||||
if (pdMetadata != null) {
|
||||
COSInputStream metaStream = pdMetadata.createInputStream();
|
||||
DomXmpParser domXmpParser = new DomXmpParser();
|
||||
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMeta, baos, true);
|
||||
String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||
|
||||
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
||||
// Check XMP Metadata
|
||||
try {
|
||||
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
||||
if (pdMetadata != null) {
|
||||
COSInputStream metaStream = pdMetadata.createInputStream();
|
||||
DomXmpParser domXmpParser = new DomXmpParser();
|
||||
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMeta, baos, true);
|
||||
String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||
|
||||
if (xmpString.contains(standardKeyword)) {
|
||||
return true;
|
||||
if (xmpString.contains(standardKeyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (
|
||||
Exception
|
||||
e) { // Catching general exception for brevity, ideally you'd catch specific
|
||||
// exceptions.
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) { // Catching general exception for brevity, ideally you'd catch specific exceptions.
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ArrayNode exploreStructureTree(List<Object> nodes) {
|
||||
ArrayNode elementsArray = objectMapper.createArrayNode();
|
||||
if (nodes != null) {
|
||||
@@ -773,7 +737,6 @@ public static boolean checkForStandard(PDDocument document, String standardKeywo
|
||||
return elementsArray;
|
||||
}
|
||||
|
||||
|
||||
public String getContent(PDStructureElement structureElement) {
|
||||
StringBuilder contentBuilder = new StringBuilder();
|
||||
|
||||
@@ -790,8 +753,7 @@ public static boolean checkForStandard(PDDocument document, String standardKeywo
|
||||
|
||||
return contentBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String formatDate(Calendar calendar) {
|
||||
if (calendar != null) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@@ -16,9 +16,11 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
|
||||
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/security")
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
@@ -26,29 +28,31 @@ public class PasswordController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
||||
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||
@Operation(
|
||||
summary = "Remove password from a PDF file",
|
||||
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
|
||||
)
|
||||
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request) throws IOException {
|
||||
summary = "Remove password from a PDF file",
|
||||
description =
|
||||
"This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
||||
throws IOException {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String password = request.getPassword();
|
||||
|
||||
|
||||
|
||||
|
||||
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
||||
document.setAllSecurityToBeRemoved(true);
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||
+ "_password_removed.pdf");
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||
@Operation(
|
||||
summary = "Add password to a PDF file",
|
||||
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF"
|
||||
)
|
||||
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request) throws IOException {
|
||||
summary = "Add password to a PDF file",
|
||||
description =
|
||||
"This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
|
||||
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
||||
throws IOException {
|
||||
MultipartFile fileInput = request.getFileInput();
|
||||
String ownerPassword = request.getOwnerPassword();
|
||||
String password = request.getPassword();
|
||||
@@ -74,16 +78,19 @@ public class PasswordController {
|
||||
ap.setCanPrintFaithful(!canPrintFaithful);
|
||||
StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap);
|
||||
|
||||
if(!"".equals(ownerPassword) || !"".equals(password)) {
|
||||
spp.setEncryptionKeyLength(keyLength);
|
||||
if (!"".equals(ownerPassword) || !"".equals(password)) {
|
||||
spp.setEncryptionKeyLength(keyLength);
|
||||
}
|
||||
spp.setPermissions(ap);
|
||||
document.protect(spp);
|
||||
|
||||
if("".equals(ownerPassword) && "".equals(password))
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_permissions.pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
|
||||
if ("".equals(ownerPassword) && "".equals(password))
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||
+ "_permissions.pdf");
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -26,10 +26,12 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.PDFText;
|
||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||
import stirling.software.SPDF.pdf.TextFinder;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/security")
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
@@ -37,11 +39,13 @@ public class RedactController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
||||
|
||||
|
||||
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
||||
@Operation(summary = "Redacts listOfText in a PDF document",
|
||||
description = "This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO")
|
||||
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request) throws Exception {
|
||||
@Operation(
|
||||
summary = "Redacts listOfText in a PDF document",
|
||||
description =
|
||||
"This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO")
|
||||
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request)
|
||||
throws Exception {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String listOfTextString = request.getListOfText();
|
||||
boolean useRegex = request.isUseRegex();
|
||||
@@ -49,15 +53,15 @@ public class RedactController {
|
||||
String colorString = request.getRedactColor();
|
||||
float customPadding = request.getCustomPadding();
|
||||
boolean convertPDFToImage = request.isConvertPDFToImage();
|
||||
|
||||
System.out.println(listOfTextString);
|
||||
String[] listOfText = listOfTextString.split("\n");
|
||||
|
||||
System.out.println(listOfTextString);
|
||||
String[] listOfText = listOfTextString.split("\n");
|
||||
byte[] bytes = file.getBytes();
|
||||
PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes));
|
||||
|
||||
|
||||
Color redactColor;
|
||||
try {
|
||||
if (!colorString.startsWith("#")) {
|
||||
if (!colorString.startsWith("#")) {
|
||||
colorString = "#" + colorString;
|
||||
}
|
||||
redactColor = Color.decode(colorString);
|
||||
@@ -66,18 +70,14 @@ public class RedactController {
|
||||
redactColor = Color.BLACK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (String text : listOfText) {
|
||||
text = text.trim();
|
||||
System.out.println(text);
|
||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||
text = text.trim();
|
||||
System.out.println(text);
|
||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||
redactFoundText(document, foundTexts, customPadding,redactColor);
|
||||
redactFoundText(document, foundTexts, customPadding, redactColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (convertPDFToImage) {
|
||||
PDDocument imageDocument = new PDDocument();
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
@@ -97,27 +97,33 @@ public class RedactController {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
document.close();
|
||||
|
||||
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
return WebResponseUtils.bytesToWebResponse(pdfContent,
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfContent,
|
||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf");
|
||||
}
|
||||
|
||||
|
||||
private void redactFoundText(PDDocument document, List<PDFText> blocks, float customPadding, Color redactColor) throws IOException {
|
||||
private void redactFoundText(
|
||||
PDDocument document, List<PDFText> blocks, float customPadding, Color redactColor)
|
||||
throws IOException {
|
||||
var allPages = document.getDocumentCatalog().getPages();
|
||||
|
||||
for (PDFText block : blocks) {
|
||||
var page = allPages.get(block.getPageIndex());
|
||||
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
contentStream.setNonStrokingColor(redactColor);
|
||||
float padding = (block.getY2() - block.getY1()) * 0.3f + customPadding;
|
||||
PDRectangle pageBox = page.getBBox();
|
||||
contentStream.addRect(block.getX1(), pageBox.getHeight() - block.getY1() - padding, block.getX2() - block.getX1(), block.getY2() - block.getY1() + 2 * padding);
|
||||
contentStream.addRect(
|
||||
block.getX1(),
|
||||
pageBox.getHeight() - block.getY1() - padding,
|
||||
block.getX2() - block.getX1(),
|
||||
block.getY2() - block.getY1() + 2 * padding);
|
||||
contentStream.fill();
|
||||
contentStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.pdfbox.cos.COSDictionary;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -36,59 +38,68 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class SanitizeController {
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||
@Operation(summary = "Sanitize a PDF file",
|
||||
description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request) throws IOException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
boolean removeJavaScript = request.isRemoveJavaScript();
|
||||
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
||||
boolean removeMetadata = request.isRemoveMetadata();
|
||||
boolean removeLinks = request.isRemoveLinks();
|
||||
boolean removeFonts = request.isRemoveFonts();
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||
@Operation(
|
||||
summary = "Sanitize a PDF file",
|
||||
description =
|
||||
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
||||
throws IOException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
boolean removeJavaScript = request.isRemoveJavaScript();
|
||||
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
||||
boolean removeMetadata = request.isRemoveMetadata();
|
||||
boolean removeLinks = request.isRemoveLinks();
|
||||
boolean removeFonts = request.isRemoveFonts();
|
||||
|
||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
||||
if (removeJavaScript) {
|
||||
sanitizeJavaScript(document);
|
||||
}
|
||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
||||
if (removeJavaScript) {
|
||||
sanitizeJavaScript(document);
|
||||
}
|
||||
|
||||
if (removeEmbeddedFiles) {
|
||||
sanitizeEmbeddedFiles(document);
|
||||
}
|
||||
if (removeEmbeddedFiles) {
|
||||
sanitizeEmbeddedFiles(document);
|
||||
}
|
||||
|
||||
if (removeMetadata) {
|
||||
sanitizeMetadata(document);
|
||||
}
|
||||
if (removeMetadata) {
|
||||
sanitizeMetadata(document);
|
||||
}
|
||||
|
||||
if (removeLinks) {
|
||||
sanitizeLinks(document);
|
||||
}
|
||||
if (removeLinks) {
|
||||
sanitizeLinks(document);
|
||||
}
|
||||
|
||||
if (removeFonts) {
|
||||
sanitizeFonts(document);
|
||||
}
|
||||
if (removeFonts) {
|
||||
sanitizeFonts(document);
|
||||
}
|
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse(document, inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_sanitized.pdf");
|
||||
}
|
||||
}
|
||||
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
||||
// Get the root dictionary (catalog) of the PDF
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||
+ "_sanitized.pdf");
|
||||
}
|
||||
}
|
||||
|
||||
// Get the Names dictionary
|
||||
COSDictionary namesDict = (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
||||
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
||||
// Get the root dictionary (catalog) of the PDF
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
|
||||
if (namesDict != null) {
|
||||
// Get the JavaScript dictionary
|
||||
COSDictionary javaScriptDict = (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
||||
// Get the Names dictionary
|
||||
COSDictionary namesDict =
|
||||
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
||||
|
||||
if (javaScriptDict != null) {
|
||||
// Remove the JavaScript dictionary
|
||||
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
||||
}
|
||||
}
|
||||
|
||||
for (PDPage page : document.getPages()) {
|
||||
if (namesDict != null) {
|
||||
// Get the JavaScript dictionary
|
||||
COSDictionary javaScriptDict =
|
||||
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
||||
|
||||
if (javaScriptDict != null) {
|
||||
// Remove the JavaScript dictionary
|
||||
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
||||
}
|
||||
}
|
||||
|
||||
for (PDPage page : document.getPages()) {
|
||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||
if (annotation instanceof PDAnnotationWidget) {
|
||||
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
||||
@@ -96,33 +107,30 @@ public class SanitizeController {
|
||||
if (action instanceof PDActionJavaScript) {
|
||||
widget.setAction(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||
if (acroForm != null) {
|
||||
for (PDField field : acroForm.getFields()) {
|
||||
PDFormFieldAdditionalActions actions = field.getActions();
|
||||
if(actions != null) {
|
||||
if (actions.getC() instanceof PDActionJavaScript) {
|
||||
actions.setC(null);
|
||||
}
|
||||
if (actions.getF() instanceof PDActionJavaScript) {
|
||||
actions.setF(null);
|
||||
}
|
||||
if (actions.getK() instanceof PDActionJavaScript) {
|
||||
actions.setK(null);
|
||||
}
|
||||
if (actions.getV() instanceof PDActionJavaScript) {
|
||||
actions.setV(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||
if (acroForm != null) {
|
||||
for (PDField field : acroForm.getFields()) {
|
||||
PDFormFieldAdditionalActions actions = field.getActions();
|
||||
if (actions != null) {
|
||||
if (actions.getC() instanceof PDActionJavaScript) {
|
||||
actions.setC(null);
|
||||
}
|
||||
if (actions.getF() instanceof PDActionJavaScript) {
|
||||
actions.setF(null);
|
||||
}
|
||||
if (actions.getK() instanceof PDActionJavaScript) {
|
||||
actions.setK(null);
|
||||
}
|
||||
if (actions.getV() instanceof PDActionJavaScript) {
|
||||
actions.setV(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sanitizeEmbeddedFiles(PDDocument document) {
|
||||
PDPageTree allPages = document.getPages();
|
||||
@@ -134,7 +142,6 @@ public class SanitizeController {
|
||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sanitizeMetadata(PDDocument document) {
|
||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
||||
@@ -143,8 +150,6 @@ public class SanitizeController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void sanitizeLinks(PDDocument document) throws IOException {
|
||||
for (PDPage page : document.getPages()) {
|
||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||
@@ -163,5 +168,4 @@ public class SanitizeController {
|
||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@@ -38,154 +39,198 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class WatermarkController {
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||
@Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> addWatermark(@ModelAttribute AddWatermarkRequest request) throws IOException, Exception {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String watermarkType = request.getWatermarkType();
|
||||
String watermarkText = request.getWatermarkText();
|
||||
MultipartFile watermarkImage = request.getWatermarkImage();
|
||||
String alphabet = request.getAlphabet();
|
||||
float fontSize = request.getFontSize();
|
||||
float rotation = request.getRotation();
|
||||
float opacity = request.getOpacity();
|
||||
int widthSpacer = request.getWidthSpacer();
|
||||
int heightSpacer = request.getHeightSpacer();
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||
@Operation(
|
||||
summary = "Add watermark to a PDF file",
|
||||
description =
|
||||
"This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> addWatermark(@ModelAttribute AddWatermarkRequest request)
|
||||
throws IOException, Exception {
|
||||
MultipartFile pdfFile = request.getFileInput();
|
||||
String watermarkType = request.getWatermarkType();
|
||||
String watermarkText = request.getWatermarkText();
|
||||
MultipartFile watermarkImage = request.getWatermarkImage();
|
||||
String alphabet = request.getAlphabet();
|
||||
float fontSize = request.getFontSize();
|
||||
float rotation = request.getRotation();
|
||||
float opacity = request.getOpacity();
|
||||
int widthSpacer = request.getWidthSpacer();
|
||||
int heightSpacer = request.getHeightSpacer();
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||
// Load the input PDF
|
||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||
|
||||
// Create a page in the document
|
||||
for (PDPage page : document.getPages()) {
|
||||
// Create a page in the document
|
||||
for (PDPage page : document.getPages()) {
|
||||
|
||||
// Get the page's content stream
|
||||
PDPageContentStream contentStream = new PDPageContentStream(document, page,
|
||||
PDPageContentStream.AppendMode.APPEND, true);
|
||||
// Get the page's content stream
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||
|
||||
// Set transparency
|
||||
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||
contentStream.setGraphicsStateParameters(graphicsState);
|
||||
// Set transparency
|
||||
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||
contentStream.setGraphicsStateParameters(graphicsState);
|
||||
|
||||
if (watermarkType.equalsIgnoreCase("text")) {
|
||||
addTextWatermark(contentStream, watermarkText, document, page, rotation, widthSpacer, heightSpacer,
|
||||
fontSize, alphabet);
|
||||
} else if (watermarkType.equalsIgnoreCase("image")) {
|
||||
addImageWatermark(contentStream, watermarkImage, document, page, rotation, widthSpacer, heightSpacer,
|
||||
fontSize);
|
||||
}
|
||||
if (watermarkType.equalsIgnoreCase("text")) {
|
||||
addTextWatermark(
|
||||
contentStream,
|
||||
watermarkText,
|
||||
document,
|
||||
page,
|
||||
rotation,
|
||||
widthSpacer,
|
||||
heightSpacer,
|
||||
fontSize,
|
||||
alphabet);
|
||||
} else if (watermarkType.equalsIgnoreCase("image")) {
|
||||
addImageWatermark(
|
||||
contentStream,
|
||||
watermarkImage,
|
||||
document,
|
||||
page,
|
||||
rotation,
|
||||
widthSpacer,
|
||||
heightSpacer,
|
||||
fontSize);
|
||||
}
|
||||
|
||||
// Close the content stream
|
||||
contentStream.close();
|
||||
}
|
||||
// Close the content stream
|
||||
contentStream.close();
|
||||
}
|
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse(document,
|
||||
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
||||
}
|
||||
return WebResponseUtils.pdfDocToWebResponse(
|
||||
document,
|
||||
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
||||
}
|
||||
|
||||
private void addTextWatermark(PDPageContentStream contentStream, String watermarkText, PDDocument document,
|
||||
PDPage page, float rotation, int widthSpacer, int heightSpacer, float fontSize, String alphabet) throws IOException {
|
||||
String resourceDir = "";
|
||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
||||
switch (alphabet) {
|
||||
case "arabic":
|
||||
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
||||
break;
|
||||
case "japanese":
|
||||
resourceDir = "static/fonts/Meiryo.ttf";
|
||||
break;
|
||||
case "korean":
|
||||
resourceDir = "static/fonts/malgun.ttf";
|
||||
break;
|
||||
case "chinese":
|
||||
resourceDir = "static/fonts/SimSun.ttf";
|
||||
break;
|
||||
case "roman":
|
||||
default:
|
||||
resourceDir = "static/fonts/NotoSans-Regular.ttf";
|
||||
break;
|
||||
}
|
||||
private void addTextWatermark(
|
||||
PDPageContentStream contentStream,
|
||||
String watermarkText,
|
||||
PDDocument document,
|
||||
PDPage page,
|
||||
float rotation,
|
||||
int widthSpacer,
|
||||
int heightSpacer,
|
||||
float fontSize,
|
||||
String alphabet)
|
||||
throws IOException {
|
||||
String resourceDir = "";
|
||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
||||
switch (alphabet) {
|
||||
case "arabic":
|
||||
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
||||
break;
|
||||
case "japanese":
|
||||
resourceDir = "static/fonts/Meiryo.ttf";
|
||||
break;
|
||||
case "korean":
|
||||
resourceDir = "static/fonts/malgun.ttf";
|
||||
break;
|
||||
case "chinese":
|
||||
resourceDir = "static/fonts/SimSun.ttf";
|
||||
break;
|
||||
case "roman":
|
||||
default:
|
||||
resourceDir = "static/fonts/NotoSans-Regular.ttf";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(!resourceDir.equals("")) {
|
||||
if (!resourceDir.equals("")) {
|
||||
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
|
||||
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
|
||||
File tempFile = File.createTempFile("NotoSansFont", fileExtension);
|
||||
try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
try (InputStream is = classPathResource.getInputStream();
|
||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
IOUtils.copy(is, os);
|
||||
}
|
||||
|
||||
|
||||
font = PDType0Font.load(document, tempFile);
|
||||
tempFile.deleteOnExit();
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
||||
|
||||
// Set size and location of text watermark
|
||||
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
||||
float watermarkHeight = heightSpacer + fontSize;
|
||||
float pageWidth = page.getMediaBox().getWidth();
|
||||
float pageHeight = page.getMediaBox().getHeight();
|
||||
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
||||
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
||||
contentStream.setFont(font, fontSize);
|
||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
||||
|
||||
// Add the text watermark
|
||||
for (int i = 0; i < watermarkRows; i++) {
|
||||
for (int j = 0; j < watermarkCols; j++) {
|
||||
contentStream.beginText();
|
||||
contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation),
|
||||
j * watermarkWidth, i * watermarkHeight));
|
||||
contentStream.showText(watermarkText);
|
||||
contentStream.endText();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set size and location of text watermark
|
||||
float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
|
||||
float watermarkHeight = heightSpacer + fontSize;
|
||||
float pageWidth = page.getMediaBox().getWidth();
|
||||
float pageHeight = page.getMediaBox().getHeight();
|
||||
int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
|
||||
int watermarkCols = (int) (pageWidth / watermarkWidth + 1);
|
||||
|
||||
private void addImageWatermark(PDPageContentStream contentStream, MultipartFile watermarkImage, PDDocument document, PDPage page, float rotation,
|
||||
int widthSpacer, int heightSpacer, float fontSize) throws IOException {
|
||||
// Add the text watermark
|
||||
for (int i = 0; i < watermarkRows; i++) {
|
||||
for (int j = 0; j < watermarkCols; j++) {
|
||||
contentStream.beginText();
|
||||
contentStream.setTextMatrix(
|
||||
Matrix.getRotateInstance(
|
||||
(float) Math.toRadians(rotation),
|
||||
j * watermarkWidth,
|
||||
i * watermarkHeight));
|
||||
contentStream.showText(watermarkText);
|
||||
contentStream.endText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the watermark image
|
||||
BufferedImage image = ImageIO.read(watermarkImage.getInputStream());
|
||||
private void addImageWatermark(
|
||||
PDPageContentStream contentStream,
|
||||
MultipartFile watermarkImage,
|
||||
PDDocument document,
|
||||
PDPage page,
|
||||
float rotation,
|
||||
int widthSpacer,
|
||||
int heightSpacer,
|
||||
float fontSize)
|
||||
throws IOException {
|
||||
|
||||
// Compute width based on original aspect ratio
|
||||
float aspectRatio = (float) image.getWidth() / (float) image.getHeight();
|
||||
// Load the watermark image
|
||||
BufferedImage image = ImageIO.read(watermarkImage.getInputStream());
|
||||
|
||||
// Desired physical height (in PDF points)
|
||||
float desiredPhysicalHeight = fontSize ;
|
||||
// Compute width based on original aspect ratio
|
||||
float aspectRatio = (float) image.getWidth() / (float) image.getHeight();
|
||||
|
||||
// Desired physical width based on the aspect ratio
|
||||
float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;
|
||||
// Desired physical height (in PDF points)
|
||||
float desiredPhysicalHeight = fontSize;
|
||||
|
||||
// Convert the BufferedImage to PDImageXObject
|
||||
PDImageXObject xobject = LosslessFactory.createFromImage(document, image);
|
||||
// Desired physical width based on the aspect ratio
|
||||
float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;
|
||||
|
||||
// Calculate the number of rows and columns for watermarks
|
||||
float pageWidth = page.getMediaBox().getWidth();
|
||||
float pageHeight = page.getMediaBox().getHeight();
|
||||
int watermarkRows = (int) ((pageHeight + heightSpacer) / (desiredPhysicalHeight + heightSpacer));
|
||||
int watermarkCols = (int) ((pageWidth + widthSpacer) / (desiredPhysicalWidth + widthSpacer));
|
||||
// Convert the BufferedImage to PDImageXObject
|
||||
PDImageXObject xobject = LosslessFactory.createFromImage(document, image);
|
||||
|
||||
for (int i = 0; i < watermarkRows; i++) {
|
||||
for (int j = 0; j < watermarkCols; j++) {
|
||||
float x = j * (desiredPhysicalWidth + widthSpacer);
|
||||
float y = i * (desiredPhysicalHeight + heightSpacer);
|
||||
// Calculate the number of rows and columns for watermarks
|
||||
float pageWidth = page.getMediaBox().getWidth();
|
||||
float pageHeight = page.getMediaBox().getHeight();
|
||||
int watermarkRows =
|
||||
(int) ((pageHeight + heightSpacer) / (desiredPhysicalHeight + heightSpacer));
|
||||
int watermarkCols =
|
||||
(int) ((pageWidth + widthSpacer) / (desiredPhysicalWidth + widthSpacer));
|
||||
|
||||
// Save the graphics state
|
||||
contentStream.saveGraphicsState();
|
||||
for (int i = 0; i < watermarkRows; i++) {
|
||||
for (int j = 0; j < watermarkCols; j++) {
|
||||
float x = j * (desiredPhysicalWidth + widthSpacer);
|
||||
float y = i * (desiredPhysicalHeight + heightSpacer);
|
||||
|
||||
// Create rotation matrix and rotate
|
||||
contentStream.transform(Matrix.getTranslateInstance(x + desiredPhysicalWidth / 2, y + desiredPhysicalHeight / 2));
|
||||
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
|
||||
contentStream.transform(Matrix.getTranslateInstance(-desiredPhysicalWidth / 2, -desiredPhysicalHeight / 2));
|
||||
|
||||
// Draw the image and restore the graphics state
|
||||
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
|
||||
contentStream.restoreGraphicsState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// Save the graphics state
|
||||
contentStream.saveGraphicsState();
|
||||
|
||||
// Create rotation matrix and rotate
|
||||
contentStream.transform(
|
||||
Matrix.getTranslateInstance(
|
||||
x + desiredPhysicalWidth / 2, y + desiredPhysicalHeight / 2));
|
||||
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
|
||||
contentStream.transform(
|
||||
Matrix.getTranslateInstance(
|
||||
-desiredPhysicalWidth / 2, -desiredPhysicalHeight / 2));
|
||||
|
||||
// Draw the image and restore the graphics state
|
||||
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
|
||||
contentStream.restoreGraphicsState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user