init overlay and auto split
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
import org.apache.pdfbox.multipdf.Overlay;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.springframework.http.MediaType;
|
||||
import java.io.File;
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/general")
|
||||
@Tag(name = "General", description = "General APIs")
|
||||
public class PdfOverlayController {
|
||||
|
||||
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
|
||||
@Operation(summary = "Overlay PDF files in various modes", description = "Overlay PDF files onto a base PDF with different modes: Sequential, Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO")
|
||||
public ResponseEntity<byte[]> overlayPdfs(@ModelAttribute OverlayPdfsRequest request) throws IOException {
|
||||
MultipartFile baseFile = request.getFileInput();
|
||||
int overlayPos = request.getOverlayPosition();
|
||||
|
||||
MultipartFile[] overlayFiles = request.getOverlayFiles();
|
||||
File[] overlayPdfFiles = new File[overlayFiles.length];
|
||||
try{
|
||||
for (int i = 0; i < overlayFiles.length; i++) {
|
||||
overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]);
|
||||
}
|
||||
|
||||
String mode = request.getOverlayMode(); // "SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay"
|
||||
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
|
||||
|
||||
try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream());
|
||||
Overlay overlay = new Overlay()) {
|
||||
Map<Integer, String> overlayGuide = prepareOverlayGuide(basePdf.getNumberOfPages(), overlayPdfFiles, mode, counts);
|
||||
|
||||
overlay.setInputPDF(basePdf);
|
||||
if(overlayPos == 0) {
|
||||
overlay.setOverlayPosition(Overlay.Position.FOREGROUND);
|
||||
} else {
|
||||
overlay.setOverlayPosition(Overlay.Position.BACKGROUND);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
overlay.overlay(overlayGuide).save(outputStream);
|
||||
byte[] data = outputStream.toByteArray();
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(data, "overlaid.pdf", MediaType.APPLICATION_PDF);
|
||||
}
|
||||
} finally {
|
||||
for (File overlayPdfFile : overlayPdfFiles) {
|
||||
if (overlayPdfFile != null) overlayPdfFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Integer, String> prepareOverlayGuide(int basePageCount, File[] overlayFiles, String mode, int[] counts) throws IOException {
|
||||
Map<Integer, String> overlayGuide = new HashMap<>();
|
||||
switch (mode) {
|
||||
case "SequentialOverlay":
|
||||
sequentialOverlay(overlayGuide, overlayFiles, basePageCount);
|
||||
break;
|
||||
case "InterleavedOverlay":
|
||||
interleavedOverlay(overlayGuide, overlayFiles, basePageCount);
|
||||
break;
|
||||
case "FixedRepeatOverlay":
|
||||
fixedRepeatOverlay(overlayGuide, overlayFiles, counts, basePageCount);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid overlay mode");
|
||||
}
|
||||
return overlayGuide;
|
||||
}
|
||||
|
||||
private void sequentialOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int basePageCount) throws IOException {
|
||||
int currentPage = 1;
|
||||
for (File overlayFile : overlayFiles) {
|
||||
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
|
||||
for (int i = 0; i < overlayPdf.getNumberOfPages(); i++) {
|
||||
if (currentPage > basePageCount) break;
|
||||
overlayGuide.put(currentPage++, overlayFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void interleavedOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int basePageCount) throws IOException {
|
||||
for (int i = 0; i < basePageCount; i++) {
|
||||
File overlayFile = overlayFiles[i % overlayFiles.length];
|
||||
overlayGuide.put(i + 1, overlayFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private void fixedRepeatOverlay(Map<Integer, String> overlayGuide, File[] overlayFiles, int[] counts, int basePageCount) throws IOException {
|
||||
if (overlayFiles.length != counts.length) {
|
||||
throw new IllegalArgumentException("Counts array length must match the number of overlay files");
|
||||
}
|
||||
int currentPage = 1;
|
||||
for (int i = 0; i < overlayFiles.length; i++) {
|
||||
File overlayFile = overlayFiles[i];
|
||||
int repeatCount = counts[i];
|
||||
for (int j = 0; j < repeatCount; j++) {
|
||||
if (currentPage > basePageCount) break;
|
||||
overlayGuide.put(currentPage++, overlayFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined elsewhere.
|
||||
@@ -0,0 +1,167 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.LuminanceSource;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
|
||||
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/general")
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class SplitPdfBySizeController {
|
||||
|
||||
|
||||
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
||||
@Operation(summary = "Auto split PDF pages into separate documents based on size or count", description = "split PDF into multiple paged documents based on size/count, ie if 20 pages and split into 5, it does 5 documents each 4 pages\r\n"
|
||||
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP Type:SIMO")
|
||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request) throws Exception {
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
|
||||
|
||||
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
PDDocument sourceDocument = PDDocument.load(file.getInputStream());
|
||||
|
||||
//0 = size, 1 = page count, 2 = doc count
|
||||
int type = request.getSplitType();
|
||||
String value = request.getSplitValue();
|
||||
|
||||
if (type == 0) { // Split by size
|
||||
long maxBytes = GeneralUtils.convertSizeToBytes(value);
|
||||
long currentSize = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
ByteArrayOutputStream pageOutputStream = new ByteArrayOutputStream();
|
||||
PDDocument tempDoc = new PDDocument();
|
||||
tempDoc.addPage(page);
|
||||
tempDoc.save(pageOutputStream);
|
||||
tempDoc.close();
|
||||
|
||||
long pageSize = pageOutputStream.size();
|
||||
if (currentSize + pageSize > maxBytes) {
|
||||
// Save and reset current document
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
currentDoc = new PDDocument();
|
||||
currentSize = 0;
|
||||
}
|
||||
|
||||
currentDoc.addPage(page);
|
||||
currentSize += pageSize;
|
||||
}
|
||||
// Add the last document if it contains any pages
|
||||
if (currentDoc.getPages().getCount() != 0) {
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
}
|
||||
} else if (type == 1) { // Split by page count
|
||||
int pageCount = Integer.parseInt(value);
|
||||
int currentPageCount = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
currentDoc.addPage(page);
|
||||
currentPageCount++;
|
||||
|
||||
if (currentPageCount == pageCount) {
|
||||
// Save and reset current document
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
currentDoc = new PDDocument();
|
||||
currentPageCount = 0;
|
||||
}
|
||||
}
|
||||
// Add the last document if it contains any pages
|
||||
if (currentDoc.getPages().getCount() != 0) {
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
}
|
||||
} else if (type == 2) { // Split by doc count
|
||||
int documentCount = Integer.parseInt(value);
|
||||
int totalPageCount = sourceDocument.getNumberOfPages();
|
||||
int pagesPerDocument = totalPageCount / documentCount;
|
||||
int extraPages = totalPageCount % documentCount;
|
||||
int currentPageIndex = 0;
|
||||
|
||||
for (int i = 0; i < documentCount; i++) {
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
||||
|
||||
for (int j = 0; j < pagesToAdd; j++) {
|
||||
currentDoc.addPage(sourceDocument.getPage(currentPageIndex++));
|
||||
}
|
||||
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid argument for split type");
|
||||
}
|
||||
|
||||
sourceDocument.close();
|
||||
|
||||
|
||||
|
||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
|
||||
byte[] data;
|
||||
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||
byte[] pdf = baos.toByteArray();
|
||||
|
||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdf);
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
private ByteArrayOutputStream currentDocToByteArray(PDDocument document) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
document.close();
|
||||
return baos;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -155,6 +155,20 @@ public class GeneralWebController {
|
||||
return "scale-pages";
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/split-by-size-or-count")
|
||||
@Hidden
|
||||
public String splitBySizeOrCount(Model model) {
|
||||
model.addAttribute("currentPage", "split-by-size-or-count");
|
||||
return "split-by-size-or-count";
|
||||
}
|
||||
|
||||
@GetMapping("/overlay-pdf")
|
||||
@Hidden
|
||||
public String overlayPdf(Model model) {
|
||||
model.addAttribute("currentPage", "overlay-pdf");
|
||||
return "overlay-pdf";
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -98,7 +98,7 @@ public class OtherWebController {
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping("/add-image")
|
||||
@Hidden
|
||||
public String overlayImage(Model model) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package stirling.software.SPDF.model.api.general;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OverlayPdfsRequest extends PDFFile {
|
||||
|
||||
@Schema(description = "An array of PDF files to be used as overlays on the base PDF. The order in these files is applied based on the selected mode.")
|
||||
private MultipartFile[] overlayFiles;
|
||||
|
||||
@Schema(description = "The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts", required = true)
|
||||
private String overlayMode;
|
||||
|
||||
@Schema(description = "An array of integers specifying the number of times each corresponding overlay file should be applied in the 'FixedRepeatOverlay' mode. This should match the length of the overlayFiles array.", required = false)
|
||||
private int[] counts;
|
||||
|
||||
@Schema(description = "Overlay position 0 is Foregound, 1 is Background")
|
||||
private int overlayPosition;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package stirling.software.SPDF.model.api.general;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper=true)
|
||||
public class SplitPdfBySizeOrCountRequest extends PDFFile {
|
||||
|
||||
@Schema(description = "Determines the type of split: 0 for size, 1 for page count, 2 for document count", required = false, defaultValue = "0")
|
||||
private int splitType;
|
||||
|
||||
|
||||
@Schema(description = "Value for split: size in MB (e.g., '10MB') or number of pages (e.g., '5')", required = false, defaultValue = "10MB")
|
||||
private String splitValue;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileVisitResult;
|
||||
@@ -12,6 +14,8 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.io.FileOutputStream;
|
||||
public class GeneralUtils {
|
||||
|
||||
public static void deleteDirectory(Path path) throws IOException {
|
||||
@@ -48,6 +52,18 @@ public class GeneralUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static File multipartToFile(MultipartFile multipart) throws IOException {
|
||||
Path tempFile = Files.createTempFile("overlay-", ".pdf");
|
||||
try (InputStream in = multipart.getInputStream();
|
||||
FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
return tempFile.toFile();
|
||||
}
|
||||
|
||||
public static Long convertSizeToBytes(String sizeStr) {
|
||||
if (sizeStr == null) {
|
||||
|
||||
Reference in New Issue
Block a user