diff --git a/build.gradle b/build.gradle index 5342c96d..0afc366b 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-core' + implementation group: 'com.google.zxing', name: 'core', version: '3.5.1' + developmentOnly("org.springframework.boot:spring-boot-devtools") } diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/other/AutoSplitPdfController.java new file mode 100644 index 00000000..c706cba7 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/other/AutoSplitPdfController.java @@ -0,0 +1,132 @@ +package stirling.software.SPDF.controller.api.other; +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.rendering.PDFRenderer; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.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 stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +public class AutoSplitPdfController { + + private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF"; + + @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") + public ResponseEntity autoSplitPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { + InputStream inputStream = file.getInputStream(); + PDDocument document = PDDocument.load(inputStream); + PDFRenderer pdfRenderer = new PDFRenderer(document); + + List splitDocuments = new ArrayList<>(); + List splitDocumentsBoas = new ArrayList<>(); // create this list to store ByteArrayOutputStreams for zipping + + for (int page = 0; page < document.getNumberOfPages(); ++page) { + BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); + String result = decodeQRCode(bim); + + if(QR_CONTENT.equals(result) && page != 0) { + splitDocuments.add(new PDDocument()); + } + + if (!splitDocuments.isEmpty() && !QR_CONTENT.equals(result)) { + splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); + } else if (page == 0) { + PDDocument firstDocument = new PDDocument(); + firstDocument.addPage(document.getPage(page)); + splitDocuments.add(firstDocument); + } + } + + // After all pages are added to splitDocuments, convert each to ByteArrayOutputStream and add to splitDocumentsBoas + for (PDDocument splitDocument : splitDocuments) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + splitDocument.save(baos); + splitDocumentsBoas.add(baos); + splitDocument.close(); + } + + document.close(); + + // After this line, you can find your zip logic integrated + Path zipFile = Files.createTempFile("split_documents", ".zip"); + String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); + byte[] data; + try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { + // loop through the split documents and write them to the zip file + for (int i = 0; i < splitDocumentsBoas.size(); i++) { + String fileName = filename + "_" + (i + 1) + ".pdf"; // You should replace "originalFileName" with the real file name + ByteArrayOutputStream baos = splitDocumentsBoas.get(i); + byte[] pdf = baos.toByteArray(); + + // Add PDF file to the zip + 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 the Resource in the response + return WebResponseUtils.bytesToWebResponse(data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM); + } + + + private static String decodeQRCode(BufferedImage bufferedImage) { + LuminanceSource source; + + if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte) { + byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData(); + source = new PlanarYUVLuminanceSource(pixels, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), false); + } else if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferInt) { + int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData(); + byte[] newPixels = new byte[pixels.length]; + for (int i = 0; i < pixels.length; i++) { + newPixels[i] = (byte) (pixels[i] & 0xff); + } + source = new PlanarYUVLuminanceSource(newPixels, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), false); + } else { + throw new IllegalArgumentException("BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed int), byte gray, or 3-byte/4-byte RGB image data"); + } + + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + + try { + Result result = new MultiFormatReader().decode(bitmap); + return result.getText(); + } catch (NotFoundException e) { + return null; // there is no QR code in the image + } + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 31687299..dcf953a5 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -143,4 +143,12 @@ public class GeneralWebController { model.addAttribute("currentPage", "crop"); return "crop"; } + + + @GetMapping("/auto-split-pdf") + @Hidden + public String autoSPlitPDFForm(Model model) { + model.addAttribute("currentPage", "auto-split-pdf"); + return "auto-split-pdf"; + } } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 2a6941f6..0da26ea7 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -149,6 +149,9 @@ home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF home.crop.title=Crop PDF home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.autoSplitPDF.title=Auto Split Pages +home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code + error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect downloadPdf=Download PDF diff --git a/src/main/resources/templates/auto-split-pdf.html b/src/main/resources/templates/auto-split-pdf.html new file mode 100644 index 00000000..faf20168 --- /dev/null +++ b/src/main/resources/templates/auto-split-pdf.html @@ -0,0 +1,31 @@ + + + + + + + + +
+
+
+

+
+
+
+

+
+
+ + +
+ +
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 7b3902cc..18425f11 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -54,6 +54,8 @@
+
+ diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 012070c6..fc3d7f7f 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -77,7 +77,7 @@
- +