diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java index 90a176df4..8dc548952 100644 --- a/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/misc/AttachmentsController.java @@ -1,6 +1,5 @@ package stirling.software.SPDF.controller.api.misc; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; @@ -8,8 +7,6 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.PageMode; -import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; -import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -21,22 +18,26 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.service.PDFAttachmentServiceInterface; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.WebResponseUtils; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/misc") @Tag(name = "Misc", description = "Miscellaneous APIs") -@Slf4j public class AttachmentsController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final PDFAttachmentServiceInterface pdfAttachmentService; + @PostMapping(consumes = "multipart/form-data", value = "/add-attachments") @Operation( summary = "Add attachments to PDF", - description = "This endpoint adds embedded files (attachments) to a PDF and sets the PageMode to UseAttachments to make them visible. Input:PDF + Files Output:PDF Type:MISO") + description = + "This endpoint adds embedded files (attachments) to a PDF and sets the PageMode to UseAttachments to make them visible. Input:PDF + Files Output:PDF Type:MISO") public ResponseEntity addAttachments( @RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("attachments") List attachments) @@ -44,23 +45,20 @@ public class AttachmentsController { // Load the PDF document PDDocument document = pdfDocumentFactory.load(pdfFile, true); - + // Get or create the document catalog PDDocumentCatalog catalog = document.getDocumentCatalog(); - + // Create embedded files name tree if it doesn't exist PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles(); + if (efTree == null) { efTree = new PDEmbeddedFilesNameTreeNode(); catalog.getNames().setEmbeddedFiles(efTree); } - // Add each attachment - for (MultipartFile attachment : attachments) { - if (attachment != null && !attachment.isEmpty()) { - addEmbeddedFile(document, efTree, attachment); - } - } + // Add attachments + pdfAttachmentService.addAttachment(document, efTree, attachments); // Set PageMode to UseAttachments to show the attachments panel catalog.setPageMode(PageMode.USE_ATTACHMENTS); @@ -69,23 +67,24 @@ public class AttachmentsController { return WebResponseUtils.pdfDocToWebResponse( document, Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") + "_with_attachments.pdf"); + .replaceFirst("[.][^.]+$", "") + + "_with_attachments.pdf"); } @PostMapping(consumes = "multipart/form-data", value = "/remove-attachments") @Operation( summary = "Remove attachments from PDF", - description = "This endpoint removes all embedded files (attachments) from a PDF. Input:PDF Output:PDF Type:SISO") + description = + "This endpoint removes all embedded files (attachments) from a PDF. Input:PDF Output:PDF Type:SISO") public ResponseEntity removeAttachments( - @RequestParam("fileInput") MultipartFile pdfFile) - throws IOException { + @RequestParam("fileInput") MultipartFile pdfFile) throws IOException { // Load the PDF document PDDocument document = pdfDocumentFactory.load(pdfFile, true); - + // Get the document catalog PDDocumentCatalog catalog = document.getDocumentCatalog(); - + // Remove embedded files if (catalog.getNames() != null) { catalog.getNames().setEmbeddedFiles(null); @@ -98,35 +97,7 @@ public class AttachmentsController { return WebResponseUtils.pdfDocToWebResponse( document, Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") + "_attachments_removed.pdf"); - } - - private void addEmbeddedFile(PDDocument document, PDEmbeddedFilesNameTreeNode efTree, MultipartFile file) - throws IOException { - - // Create file specification - PDComplexFileSpecification fs = new PDComplexFileSpecification(); - fs.setFile(file.getOriginalFilename()); - fs.setFileDescription("Embedded file: " + file.getOriginalFilename()); - - // Create embedded file - PDEmbeddedFile ef = new PDEmbeddedFile(document, new ByteArrayInputStream(file.getBytes())); - ef.setSize((int) file.getSize()); - ef.setCreationDate(new java.util.GregorianCalendar()); - ef.setModDate(new java.util.GregorianCalendar()); - - // Set MIME type if available - String contentType = file.getContentType(); - if (contentType != null && !contentType.isEmpty()) { - ef.setSubtype(contentType); - } - - // Associate embedded file with file specification - fs.setEmbeddedFile(ef); - - // Add to the name tree - efTree.setNames(java.util.Collections.singletonMap(file.getOriginalFilename(), fs)); - - log.info("Added embedded file: {} ({} bytes)", file.getOriginalFilename(), file.getSize()); + .replaceFirst("[.][^.]+$", "") + + "_attachments_removed.pdf"); } } diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java new file mode 100644 index 000000000..dae172337 --- /dev/null +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentService.java @@ -0,0 +1,81 @@ +package stirling.software.SPDF.service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class PDFAttachmentService implements PDFAttachmentServiceInterface { + + @Override + public void addAttachment( + PDDocument document, + PDEmbeddedFilesNameTreeNode efTree, + List attachments) + throws IOException { + // Get existing names or create new map + Map existingNames = new java.util.HashMap<>(); + try { + existingNames = efTree.getNames(); + } catch (IOException e) { + log.warn("Could not retrieve existing embedded files, starting with empty map", e); + } + + Map finalExistingNames = existingNames; + attachments.forEach( + attachment -> { + // Create attachments specification + PDComplexFileSpecification fileSpecification = new PDComplexFileSpecification(); + fileSpecification.setFile(attachment.getOriginalFilename()); + fileSpecification.setFileDescription( + "Embedded attachment: " + attachment.getOriginalFilename()); + + try { + // Create embedded attachment + PDEmbeddedFile embeddedFile = + new PDEmbeddedFile( + document, new ByteArrayInputStream(attachment.getBytes())); + embeddedFile.setSize((int) attachment.getSize()); + embeddedFile.setCreationDate(new java.util.GregorianCalendar()); + embeddedFile.setModDate(new java.util.GregorianCalendar()); + + // Set MIME type if available + String contentType = attachment.getContentType(); + if (StringUtils.isNotBlank(contentType)) { + embeddedFile.setSubtype(contentType); + } + + // Associate embedded attachment with file specification + fileSpecification.setEmbeddedFile(embeddedFile); + + // Add to the existing names map + finalExistingNames.put(attachment.getOriginalFilename(), fileSpecification); + + log.info( + "Added embedded attachment: {} ({} bytes)", + attachment.getOriginalFilename(), + attachment.getSize()); + } catch (IOException e) { + log.error( + "Failed to create embedded file for attachment: {}", + attachment.getOriginalFilename(), + e); + } + }); + + // Update the name tree with all names + efTree.setNames(existingNames); + } +} diff --git a/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java new file mode 100644 index 000000000..90ae33013 --- /dev/null +++ b/stirling-pdf/src/main/java/stirling/software/SPDF/service/PDFAttachmentServiceInterface.java @@ -0,0 +1,17 @@ +package stirling.software.SPDF.service; + +import java.io.IOException; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.springframework.web.multipart.MultipartFile; + +public interface PDFAttachmentServiceInterface { + + void addAttachment( + PDDocument document, + PDEmbeddedFilesNameTreeNode efTree, + List attachments) + throws IOException; +} diff --git a/stirling-pdf/src/main/resources/messages_en_GB.properties b/stirling-pdf/src/main/resources/messages_en_GB.properties index 7dc6cb571..102ea6381 100644 --- a/stirling-pdf/src/main/resources/messages_en_GB.properties +++ b/stirling-pdf/src/main/resources/messages_en_GB.properties @@ -513,6 +513,10 @@ home.addImage.title=Add image home.addImage.desc=Adds a image onto a set location on the PDF addImage.tags=img,jpg,picture,photo +home.attachments.title=Attachments +home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF +attachments.tags=embed,attach,file,attachment,attachments + home.watermark.title=Add Watermark home.watermark.desc=Add a custom watermark to your PDF document. watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo @@ -1193,6 +1197,16 @@ addImage.everyPage=Every Page? addImage.upload=Add image addImage.submit=Add image +#attachments +attachments.title=Attachments +attachments.header=Add attachments to PDF +attachments.removeHeader=Remove attachments from PDF +attachments.selectFiles=Select files to attach +attachments.description=Allows you to add attachments to the PDF +attachments.descriptionPlaceholder=Enter a description for the attachments... +attachments.addButton=Add Attachments +attachments.removeDescription=This will remove all embedded files from the PDF. +attachments.removeButton=Remove All Attachments #merge merge.title=Merge diff --git a/stirling-pdf/src/main/resources/messages_en_US.properties b/stirling-pdf/src/main/resources/messages_en_US.properties index fb814f8e1..9506ff80b 100644 --- a/stirling-pdf/src/main/resources/messages_en_US.properties +++ b/stirling-pdf/src/main/resources/messages_en_US.properties @@ -474,7 +474,7 @@ addImage.tags=img,jpg,picture,photo home.attachments.title=Attachments home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF -attachments.tags=embed,attach,file,attachment +attachments.tags=embed,attach,file,attachment,attachments home.watermark.title=Add Watermark home.watermark.desc=Add a custom watermark to your PDF document. @@ -484,7 +484,6 @@ home.permissions.title=Change Permissions home.permissions.desc=Change the permissions of your PDF document permissions.tags=read,write,edit,print - home.removePages.title=Remove home.removePages.desc=Delete unwanted pages from your PDF document. removePages.tags=Remove pages,delete pages diff --git a/stirling-pdf/src/main/resources/templates/fragments/navElements.html b/stirling-pdf/src/main/resources/templates/fragments/navElements.html index cd7fae74b..f26abd0a4 100644 --- a/stirling-pdf/src/main/resources/templates/fragments/navElements.html +++ b/stirling-pdf/src/main/resources/templates/fragments/navElements.html @@ -236,7 +236,10 @@
-
+ +
+
diff --git a/stirling-pdf/src/main/resources/templates/misc/attachments.html b/stirling-pdf/src/main/resources/templates/misc/attachments.html new file mode 100644 index 000000000..60bb16c96 --- /dev/null +++ b/stirling-pdf/src/main/resources/templates/misc/attachments.html @@ -0,0 +1,42 @@ + + + + + + + + +
+
+ +

+
+
+
+
+ attachment + +
+ +
+ +
+
+ + +
+
+ + + +
+
+
+
+
+ +
+ + + \ No newline at end of file