mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	Split by sections #506
This commit is contained in:
		
							parent
							
								
									12dccab460
								
							
						
					
					
						commit
						c28a40ffe8
					
				| @ -0,0 +1,153 @@ | ||||
| package stirling.software.SPDF.controller.api; | ||||
| import java.awt.geom.Rectangle2D; | ||||
| import java.awt.image.BufferedImage; | ||||
| import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; | ||||
| import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; | ||||
| import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; | ||||
| 
 | ||||
| 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.multipdf.LayerUtility; | ||||
| import org.apache.pdfbox.pdmodel.PDDocument; | ||||
| import org.apache.pdfbox.pdmodel.PDPage; | ||||
| import org.apache.pdfbox.pdmodel.common.PDRectangle; | ||||
| import org.apache.pdfbox.rendering.PDFRenderer; | ||||
| import org.apache.pdfbox.util.Matrix; | ||||
| 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.SplitPdfBySectionsRequest; | ||||
| 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; | ||||
| import org.apache.pdfbox.pdmodel.PDPageContentStream; | ||||
| @RestController | ||||
| @RequestMapping("/api/v1/general") | ||||
| @Tag(name = "Misc", description = "Miscellaneous APIs") | ||||
| public class SplitPdfBySectionsController { | ||||
| 
 | ||||
| 
 | ||||
| 	@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") | ||||
|     @Operation(summary = "Split PDF pages into smaller sections", description = "Split each page of a PDF into smaller sections based on the user's choice (halves, thirds, quarters, etc.), both vertically and horizontally. Input: PDF, Split Parameters. Output: ZIP containing split documents.") | ||||
|     public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request) throws Exception { | ||||
|         List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); | ||||
| 
 | ||||
|         MultipartFile file = request.getFileInput(); | ||||
|         PDDocument sourceDocument = PDDocument.load(file.getInputStream()); | ||||
| 
 | ||||
|         // Process the PDF based on split parameters | ||||
|         int horiz = request.getHorizontalDivisions(); | ||||
|         int verti = request.getVerticalDivisions(); | ||||
| 
 | ||||
|         List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, horiz, verti); | ||||
|         for (PDDocument doc : splitDocuments) { | ||||
|             ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||||
|             doc.save(baos); | ||||
|             doc.close(); | ||||
|             splitDocumentsBoas.add(baos); | ||||
|         } | ||||
| 
 | ||||
|         sourceDocument.close(); | ||||
| 
 | ||||
|         Path zipFile = Files.createTempFile("split_documents", ".zip"); | ||||
|         String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", ""); | ||||
|         byte[] data; | ||||
| 
 | ||||
| 
 | ||||
|          | ||||
|         try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { | ||||
|             int pageNum = 1; | ||||
|             for (int i = 0; i < splitDocumentsBoas.size(); i++) { | ||||
|                 ByteArrayOutputStream baos = splitDocumentsBoas.get(i); | ||||
|                 int sectionNum = (i % (horiz * verti)) + 1; | ||||
|                 String fileName = filename + "_" + pageNum + "_" + sectionNum + ".pdf"; | ||||
|                 byte[] pdf = baos.toByteArray(); | ||||
|                 ZipEntry pdfEntry = new ZipEntry(fileName); | ||||
|                 zipOut.putNextEntry(pdfEntry); | ||||
|                 zipOut.write(pdf); | ||||
|                 zipOut.closeEntry(); | ||||
| 
 | ||||
|                 if (sectionNum == horiz * verti) pageNum++; | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } finally { | ||||
|             data = Files.readAllBytes(zipFile); | ||||
|             Files.delete(zipFile); | ||||
|         } | ||||
| 
 | ||||
|         return WebResponseUtils.bytesToWebResponse(data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM); | ||||
|     } | ||||
|      | ||||
|     public List<PDDocument> splitPdfPages(PDDocument document, int horizontalDivisions, int verticalDivisions) throws IOException { | ||||
|         List<PDDocument> splitDocuments = new ArrayList<>(); | ||||
| 
 | ||||
|         for (PDPage originalPage : document.getPages()) { | ||||
|             PDRectangle originalMediaBox = originalPage.getMediaBox(); | ||||
|             float width = originalMediaBox.getWidth(); | ||||
|             float height = originalMediaBox.getHeight(); | ||||
|             float subPageWidth = width / horizontalDivisions; | ||||
|             float subPageHeight = height / verticalDivisions; | ||||
| 
 | ||||
|             LayerUtility layerUtility = new LayerUtility(document); | ||||
| 
 | ||||
|             for (int i = 0; i < horizontalDivisions; i++) { | ||||
|                 for (int j = 0; j < verticalDivisions; j++) { | ||||
|                     PDDocument subDoc = new PDDocument(); | ||||
|                     PDPage subPage = new PDPage(new PDRectangle(subPageWidth, subPageHeight)); | ||||
|                     subDoc.addPage(subPage); | ||||
| 
 | ||||
|                     PDFormXObject form = layerUtility.importPageAsForm(document, document.getPages().indexOf(originalPage)); | ||||
| 
 | ||||
|                     try (PDPageContentStream contentStream = new PDPageContentStream(subDoc, subPage)) { | ||||
|                         // Set clipping area and position | ||||
|                         float translateX = -subPageWidth * i; | ||||
|                         float translateY = height - subPageHeight * (j + 1) - subPageHeight; | ||||
| 
 | ||||
|                         contentStream.saveGraphicsState(); | ||||
|                         contentStream.addRect(0, 0, subPageWidth, subPageHeight); | ||||
|                         contentStream.clip(); | ||||
|                         contentStream.transform(new Matrix(1, 0, 0, 1, translateX, translateY)); | ||||
| 
 | ||||
|                         // Draw the form | ||||
|                         contentStream.drawForm(form); | ||||
|                         contentStream.restoreGraphicsState(); | ||||
|                     } | ||||
| 
 | ||||
|                     splitDocuments.add(subDoc); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return splitDocuments; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| } | ||||
| @ -74,6 +74,13 @@ public class GeneralWebController { | ||||
|         model.addAttribute("currentPage", "merge-pdfs"); | ||||
|         return "merge-pdfs"; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/split-pdf-by-sections") | ||||
|     @Hidden | ||||
|     public String splitPdfBySections(Model model) { | ||||
|         model.addAttribute("currentPage", "split-pdf-by-sections"); | ||||
|         return "split-pdf-by-sections"; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/view-pdf") | ||||
|     @Hidden | ||||
|  | ||||
| @ -0,0 +1,18 @@ | ||||
| package stirling.software.SPDF.model.api; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
| 
 | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @EqualsAndHashCode(callSuper=true) | ||||
| public class SplitPdfBySectionsRequest extends PDFFile { | ||||
| 	@Schema(description = "Number of horizontal divisions for each PDF page", example = "2") | ||||
|     private int horizontalDivisions; | ||||
| 
 | ||||
|     @Schema(description = "Number of vertical divisions for each PDF page", example = "2") | ||||
|     private int verticalDivisions; | ||||
| } | ||||
| @ -37,7 +37,7 @@ | ||||
| 
 | ||||
| 
 | ||||
|     <!-- Root Logger --> | ||||
|     <root level="${LOG_TYPE}"> | ||||
|     <root level="INFO"> | ||||
|         <appender-ref ref="CONSOLE"/> | ||||
|         <appender-ref ref="GENERAL"/> | ||||
|     </root> | ||||
|  | ||||
| @ -350,6 +350,10 @@ home.overlay-pdfs.title=Overlay PDFs | ||||
| home.overlay-pdfs.desc=Overlays PDFs on-top of another PDF | ||||
| overlay-pdfs.tags=Overlay | ||||
| 
 | ||||
| home.split-by-sections.title=Split PDF by Sections | ||||
| home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections | ||||
| split-by-sections.tags=Section Split, Divide, Customize | ||||
| 
 | ||||
| ########################### | ||||
| #                         # | ||||
| #       WEB PAGES         # | ||||
| @ -870,3 +874,12 @@ overlay-pdfs.position.foreground=Foreground | ||||
| overlay-pdfs.position.background=Background | ||||
| overlay-pdfs.submit=Submit | ||||
| 
 | ||||
| 
 | ||||
| #split-by-sections | ||||
| split-by-sections.title=Split PDF by Sections | ||||
| split-by-sections.header=Split PDF into Sections | ||||
| split-by-sections.horizontal.label=Horizontal Divisions | ||||
| split-by-sections.vertical.label=Vertical Divisions | ||||
| split-by-sections.horizontal.placeholder=Enter number of horizontal divisions | ||||
| split-by-sections.vertical.placeholder=Enter number of vertical divisions | ||||
| split-by-sections.submit=Split PDF | ||||
|  | ||||
| @ -61,8 +61,7 @@ | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-single-page', 'images/single-page.svg', 'home.PdfToSinglePage.title', 'home.PdfToSinglePage.desc', 'PdfToSinglePage.tags')}"></div> | ||||
| 							<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'split-by-size-or-count', 'images/layout-split.svg', 'home.autoSizeSplitPDF.title', 'home.autoSizeSplitPDF.desc', 'autoSizeSplitPDF.tags')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'overlay-pdf', 'images/overlay.svg', 'home.overlay-pdfs.title', 'home.overlay-pdfs.desc', 'overlay-pdfs.tags')}"></div> | ||||
|                              | ||||
| 
 | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'split-pdf-by-sections', 'images/layout-split.svg', 'home.split-by-sections.title', 'home.split-by-sections.desc', 'split-by-sections.tags')}"></div> | ||||
|                         </div> | ||||
|                     </li> | ||||
|                     <li class="nav-item nav-item-separator"></li> | ||||
|  | ||||
| @ -96,8 +96,9 @@ | ||||
|                 <div th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', svgPath='images/pdf-csv.svg')}"></div> | ||||
| 				<div th:replace="~{fragments/card :: card(id='split-by-size-or-count', cardTitle=#{home.autoSizeSplitPDF.title}, cardText=#{home.autoSizeSplitPDF.desc}, cardLink='split-by-size-or-count', svgPath='images/layout-split.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(id='overlay-pdf', cardTitle=#{home.overlay-pdfs.title}, cardText=#{home.overlay-pdfs.desc}, cardLink='overlay-pdf', svgPath='images/overlay.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(id='split-pdf-by-sections', cardTitle=#{home.split-by-sections.title}, cardText=#{home.split-by-sections.desc}, cardLink='split-pdf-by-sections', svgPath='images/layout-split.svg')}"></div> | ||||
|                  | ||||
| 
 | ||||
| 				 | ||||
| 
 | ||||
|             </div> | ||||
|         </div> </div> | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/main/resources/templates/split-pdf-by-sections.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/main/resources/templates/split-pdf-by-sections.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| <!DOCTYPE html> | ||||
| <html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> | ||||
| 
 | ||||
| <th:block th:insert="~{fragments/common :: head(title=#{split-by-sections.title})}"></th:block> | ||||
| 
 | ||||
| <body> | ||||
|     <th:block th:insert="~{fragments/common :: game}"></th:block> | ||||
|     <div id="page-container"> | ||||
|         <div id="content-wrap"> | ||||
|             <div th:insert="~{fragments/navbar.html :: navbar}"></div> | ||||
|             <br> <br> | ||||
|             <div class="container"> | ||||
|                 <div class="row justify-content-center"> | ||||
|                     <div class="col-md-6"> | ||||
|                         <h2 th:text="#{split-by-sections.header}"></h2> | ||||
|                         <form method="post" enctype="multipart/form-data" th:action="@{/api/v1/general/split-pdf-by-sections}"> | ||||
|                             <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> | ||||
|                              | ||||
|                             <label for="horizontalDivisions" th:text="#{split-by-sections.horizontal.label}">Horizontal Divisions</label> | ||||
|                             <input type="number" id="horizontalDivisions" name="horizontalDivisions" class="form-control" min="1" required th:placeholder="#{split-by-sections.horizontal.placeholder}"> | ||||
|                             <br> | ||||
| 
 | ||||
|                             <label for="verticalDivisions" th:text="#{split-by-sections.vertical.label}">Vertical Divisions</label> | ||||
|                             <input type="number" id="verticalDivisions" name="verticalDivisions" class="form-control" min="1" required th:placeholder="#{split-by-sections.vertical.placeholder}"> | ||||
|                             <br> | ||||
|                          | ||||
|                             <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-sections.submit}">Submit</button> | ||||
|                         </form> | ||||
| 
 | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
| 
 | ||||
|         </div> | ||||
|         <div th:insert="~{fragments/footer.html :: footer}"></div> | ||||
|     </div> | ||||
| </body> | ||||
| </html> | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user