mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-08-11 13:48:37 +02:00
Merge 28819f74f1
into 979f302277
This commit is contained in:
commit
ee89c7428d
@ -4,8 +4,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@ -31,8 +30,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.SplitTypes;
|
||||||
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -48,20 +49,30 @@ public class SplitPdfBySectionsController {
|
|||||||
summary = "Split PDF pages into smaller sections",
|
summary = "Split PDF pages into smaller sections",
|
||||||
description =
|
description =
|
||||||
"Split each page of a PDF into smaller sections based on the user's choice"
|
"Split each page of a PDF into smaller sections based on the user's choice"
|
||||||
+ " (halves, thirds, quarters, etc.), both vertically and horizontally."
|
+ " which page to split, and how to split"
|
||||||
|
+ " ( halves, thirds, quarters, etc.), both vertically and horizontally."
|
||||||
+ " Input:PDF Output:ZIP-PDF Type:SISO")
|
+ " Input:PDF Output:ZIP-PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request)
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfBySectionsRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
|
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
|
String pageNumbers = request.getPageNumbers();
|
||||||
|
SplitTypes splitMode =
|
||||||
|
Optional.ofNullable(request.getSplitMode())
|
||||||
|
.map(SplitTypes::valueOf)
|
||||||
|
.orElse(SplitTypes.SPLIT_ALL);
|
||||||
|
|
||||||
PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
PDDocument sourceDocument = pdfDocumentFactory.load(file);
|
||||||
|
|
||||||
|
Set<Integer> pagesToSplit =
|
||||||
|
getPagesToSplit(pageNumbers, splitMode, sourceDocument.getNumberOfPages());
|
||||||
|
|
||||||
// Process the PDF based on split parameters
|
// Process the PDF based on split parameters
|
||||||
int horiz = request.getHorizontalDivisions() + 1;
|
int horiz = request.getHorizontalDivisions() + 1;
|
||||||
int verti = request.getVerticalDivisions() + 1;
|
int verti = request.getVerticalDivisions() + 1;
|
||||||
boolean merge = Boolean.TRUE.equals(request.getMerge());
|
boolean merge = Boolean.TRUE.equals(request.getMerge());
|
||||||
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz);
|
List<PDDocument> splitDocuments = splitPdfPages(sourceDocument, verti, horiz, pagesToSplit);
|
||||||
|
|
||||||
String filename =
|
String filename =
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
@ -109,52 +120,111 @@ public class SplitPdfBySectionsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Based on the mode, get the pages that need to be split and return the pages set
|
||||||
|
private Set<Integer> getPagesToSplit(String pageNumbers, SplitTypes splitMode, int totalPages) {
|
||||||
|
Set<Integer> pagesToSplit = new HashSet<>();
|
||||||
|
|
||||||
|
switch (splitMode) {
|
||||||
|
case CUSTOM:
|
||||||
|
if (pageNumbers == null || pageNumbers.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Custom mode requires page numbers input.");
|
||||||
|
}
|
||||||
|
String[] pageOrderArr = pageNumbers.split(",");
|
||||||
|
List<Integer> pageListToSplit =
|
||||||
|
GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
||||||
|
pagesToSplit.addAll(pageListToSplit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPLIT_ALL:
|
||||||
|
for (int i = 0; i < totalPages; i++) {
|
||||||
|
pagesToSplit.add(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPLIT_ALL_EXCEPT_FIRST:
|
||||||
|
for (int i = 1; i < totalPages; i++) {
|
||||||
|
pagesToSplit.add(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPLIT_ALL_EXCEPT_LAST:
|
||||||
|
for (int i = 0; i < totalPages - 1; i++) {
|
||||||
|
pagesToSplit.add(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SPLIT_ALL_EXCEPT_FIRST_AND_LAST:
|
||||||
|
for (int i = 1; i < totalPages - 1; i++) {
|
||||||
|
pagesToSplit.add(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported split mode: " + splitMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagesToSplit;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PDDocument> splitPdfPages(
|
public List<PDDocument> splitPdfPages(
|
||||||
PDDocument document, int horizontalDivisions, int verticalDivisions)
|
PDDocument document,
|
||||||
|
int horizontalDivisions,
|
||||||
|
int verticalDivisions,
|
||||||
|
Set<Integer> pagesToSplit)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
List<PDDocument> splitDocuments = new ArrayList<>();
|
List<PDDocument> splitDocuments = new ArrayList<>();
|
||||||
|
|
||||||
|
int pageIndex = 0;
|
||||||
for (PDPage originalPage : document.getPages()) {
|
for (PDPage originalPage : document.getPages()) {
|
||||||
PDRectangle originalMediaBox = originalPage.getMediaBox();
|
// If current page is not to split, add it to the splitDocuments directly.
|
||||||
float width = originalMediaBox.getWidth();
|
if (!pagesToSplit.contains(pageIndex)) {
|
||||||
float height = originalMediaBox.getHeight();
|
PDDocument newDoc = pdfDocumentFactory.createNewDocument();
|
||||||
float subPageWidth = width / horizontalDivisions;
|
newDoc.addPage(originalPage);
|
||||||
float subPageHeight = height / verticalDivisions;
|
splitDocuments.add(newDoc);
|
||||||
|
} else {
|
||||||
|
// Otherwise, split current page.
|
||||||
|
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);
|
LayerUtility layerUtility = new LayerUtility(document);
|
||||||
|
|
||||||
for (int i = 0; i < horizontalDivisions; i++) {
|
for (int i = 0; i < horizontalDivisions; i++) {
|
||||||
for (int j = 0; j < verticalDivisions; j++) {
|
for (int j = 0; j < verticalDivisions; j++) {
|
||||||
PDDocument subDoc = new PDDocument();
|
PDDocument subDoc = new PDDocument();
|
||||||
PDPage subPage = new PDPage(new PDRectangle(subPageWidth, subPageHeight));
|
PDPage subPage = new PDPage(new PDRectangle(subPageWidth, subPageHeight));
|
||||||
subDoc.addPage(subPage);
|
subDoc.addPage(subPage);
|
||||||
|
|
||||||
PDFormXObject form =
|
PDFormXObject form =
|
||||||
layerUtility.importPageAsForm(
|
layerUtility.importPageAsForm(
|
||||||
document, document.getPages().indexOf(originalPage));
|
document, document.getPages().indexOf(originalPage));
|
||||||
|
|
||||||
try (PDPageContentStream contentStream =
|
try (PDPageContentStream contentStream =
|
||||||
new PDPageContentStream(
|
new PDPageContentStream(
|
||||||
subDoc, subPage, AppendMode.APPEND, true, true)) {
|
subDoc, subPage, AppendMode.APPEND, true, true)) {
|
||||||
// Set clipping area and position
|
// Set clipping area and position
|
||||||
float translateX = -subPageWidth * i;
|
float translateX = -subPageWidth * i;
|
||||||
|
|
||||||
// float translateY = height - subPageHeight * (verticalDivisions - j);
|
// float translateY = height - subPageHeight * (verticalDivisions - j);
|
||||||
float translateY = -subPageHeight * (verticalDivisions - 1 - j);
|
float translateY = -subPageHeight * (verticalDivisions - 1 - j);
|
||||||
|
|
||||||
contentStream.saveGraphicsState();
|
contentStream.saveGraphicsState();
|
||||||
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
|
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
|
||||||
contentStream.clip();
|
contentStream.clip();
|
||||||
contentStream.transform(new Matrix(1, 0, 0, 1, translateX, translateY));
|
contentStream.transform(new Matrix(1, 0, 0, 1, translateX, translateY));
|
||||||
|
|
||||||
// Draw the form
|
// Draw the form
|
||||||
contentStream.drawForm(form);
|
contentStream.drawForm(form);
|
||||||
contentStream.restoreGraphicsState();
|
contentStream.restoreGraphicsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
splitDocuments.add(subDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
splitDocuments.add(subDoc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return splitDocuments;
|
return splitDocuments;
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
public enum SplitTypes {
|
||||||
|
CUSTOM,
|
||||||
|
SPLIT_ALL_EXCEPT_FIRST_AND_LAST,
|
||||||
|
SPLIT_ALL_EXCEPT_FIRST,
|
||||||
|
SPLIT_ALL_EXCEPT_LAST,
|
||||||
|
SPLIT_ALL
|
||||||
|
}
|
@ -5,11 +5,29 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.SplitTypes;
|
||||||
import stirling.software.common.model.api.PDFFile;
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class SplitPdfBySectionsRequest extends PDFFile {
|
public class SplitPdfBySectionsRequest extends PDFFile {
|
||||||
|
@Schema(
|
||||||
|
description = "Pages to be split by section",
|
||||||
|
defaultValue = "all",
|
||||||
|
requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String pageNumbers;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
implementation = SplitTypes.class,
|
||||||
|
description =
|
||||||
|
"Modes for page split. Valid values are:\n"
|
||||||
|
+ "SPLIT_ALL_EXCEPT_FIRST_AND_LAST: Splits all except the first and the last pages.\n"
|
||||||
|
+ "SPLIT_ALL_EXCEPT_FIRST: Splits all except the first page.\n"
|
||||||
|
+ "SPLIT_ALL_EXCEPT_LAST: Splits all except the last page.\n"
|
||||||
|
+ "SPLIT_ALL: Splits all pages.\n"
|
||||||
|
+ "CUSTOM: Custom split.\n")
|
||||||
|
private String splitMode;
|
||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "Number of horizontal divisions for each PDF page",
|
description = "Number of horizontal divisions for each PDF page",
|
||||||
defaultValue = "0",
|
defaultValue = "0",
|
||||||
|
@ -838,8 +838,8 @@ home.overlay-pdfs.desc=Overlays PDFs on-top of another PDF
|
|||||||
overlay-pdfs.tags=Overlay
|
overlay-pdfs.tags=Overlay
|
||||||
|
|
||||||
home.split-by-sections.title=Split PDF by Sections
|
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
|
home.split-by-sections.desc=Split the content of specified PDF pages into smaller horizontal and vertical sections.
|
||||||
split-by-sections.tags=Section Split, Divide, Customize,Customise
|
split-by-sections.tags=Section Split, Divide, Customize, Customise
|
||||||
|
|
||||||
home.AddStampRequest.title=Add Stamp to PDF
|
home.AddStampRequest.title=Add Stamp to PDF
|
||||||
home.AddStampRequest.desc=Add text or add image stamps at set locations
|
home.AddStampRequest.desc=Add text or add image stamps at set locations
|
||||||
@ -1619,6 +1619,15 @@ split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
|||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
split-by-sections.merge=Merge Into One PDF
|
split-by-sections.merge=Merge Into One PDF
|
||||||
|
split-by-sections.pageToSplit=Pages to split (Enter a comma-separated list of page numbers) :
|
||||||
|
split-by-sections.pageToSplit.placeholder=(e.g. 1,2,6)
|
||||||
|
split-by-sections.mode=Split Mode
|
||||||
|
split-by-sections.mode.1=Split All Except First & Last
|
||||||
|
split-by-sections.mode.2=Split All Except First
|
||||||
|
split-by-sections.mode.3=Split All Except Last
|
||||||
|
split-by-sections.mode.4=Split All Pages
|
||||||
|
split-by-sections.mode.5=Custom: Specify Pages
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#printFile
|
#printFile
|
||||||
|
@ -8,3 +8,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: red; /* Line color */
|
background-color: red; /* Line color */
|
||||||
}
|
}
|
||||||
|
#pageToSplitSection {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@ -42,22 +42,29 @@
|
|||||||
<option value="REMOVE_FIRST_AND_LAST" th:text="#{pdfOrganiser.mode.9}">Remove First and Last</option>
|
<option value="REMOVE_FIRST_AND_LAST" th:text="#{pdfOrganiser.mode.9}">Remove First and Last</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3" id="pageToOrganizeSection">
|
||||||
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
|
||||||
<input type="text" class="form-control" id="pageOrder" name="pageNumbers"
|
<input type="text" class="form-control" id="pageOrder" name="pageNumbers"
|
||||||
th:placeholder="#{pdfOrganiser.placeholder}" required>
|
th:placeholder="#{pdfOrganiser.placeholder}">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('customMode').addEventListener('change', function () {
|
// Only display the page input field when split mode is custom.
|
||||||
var pageOrderInput = document.getElementById('pageOrder');
|
function togglePageInput() {
|
||||||
if (this.value === "" || this.value === "DUPLICATE") {
|
const mode = document.getElementById('customMode').value;
|
||||||
pageOrderInput.disabled = false;
|
const pageInputSection = document.getElementById('pageToOrganizeSection');
|
||||||
|
if (mode === "") {
|
||||||
|
pageInputSection.style.display = "block";
|
||||||
|
document.getElementById('pageOrder').setAttribute("required", "true");
|
||||||
} else {
|
} else {
|
||||||
pageOrderInput.disabled = true;
|
pageInputSection.style.display = "none";
|
||||||
|
document.getElementById('pageOrder').removeAttribute("required");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
document.getElementById('customMode').addEventListener('change', togglePageInput);
|
||||||
|
window.addEventListener('DOMContentLoaded', togglePageInput);
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,6 +20,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/general/split-pdf-by-sections'}">
|
<form method="post" enctype="multipart/form-data" th:action="@{'/api/v1/general/split-pdf-by-sections'}">
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="splitMode" th:text="#{split-by-sections.mode}">Split Mode</label>
|
||||||
|
<select class="form-control" id="splitMode" name="splitMode">
|
||||||
|
<option value="SPLIT_ALL" th:text="#{split-by-sections.mode.4}">Split all</option>
|
||||||
|
<option value="SPLIT_ALL_EXCEPT_FIRST_AND_LAST" th:text="#{split-by-sections.mode.1}">Split all except first and last</option>
|
||||||
|
<option value="SPLIT_ALL_EXCEPT_FIRST" th:text="#{split-by-sections.mode.2}">Split all except first</option>
|
||||||
|
<option value="SPLIT_ALL_EXCEPT_LAST" th:text="#{split-by-sections.mode.3}">Split all except last</option>
|
||||||
|
<option value="CUSTOM" th:text="#{split-by-sections.mode.5}">Specify pages to split</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3" id="pageToSplitSection">
|
||||||
|
<label for="pageToSplit" th:text="#{split-by-sections.pageToSplit}"></label>
|
||||||
|
<input type="text" class="form-control" id="pageToSplit" name="pageNumbers"
|
||||||
|
th:placeholder="#{split-by-sections.pageToSplit.placeholder}">
|
||||||
|
</div>
|
||||||
<label for="horizontalDivisions" th:text="#{split-by-sections.horizontal.label}">Horizontal Divisions</label>
|
<label for="horizontalDivisions" th:text="#{split-by-sections.horizontal.label}">Horizontal Divisions</label>
|
||||||
<input type="number" id="horizontalDivisions" name="horizontalDivisions" class="form-control" min="0" max="300" value="0" required th:placeholder="#{split-by-sections.horizontal.placeholder}">
|
<input type="number" id="horizontalDivisions" name="horizontalDivisions" class="form-control" min="0" max="300" value="0" required th:placeholder="#{split-by-sections.horizontal.placeholder}">
|
||||||
<br>
|
<br>
|
||||||
@ -74,6 +89,28 @@
|
|||||||
|
|
||||||
// Initial draw
|
// Initial draw
|
||||||
updateVisualAid();
|
updateVisualAid();
|
||||||
|
|
||||||
|
// Removes all whitespace characters (spaces, tabs, newlines) as the user types
|
||||||
|
document.getElementById('pageToSplit').addEventListener('input', function () {
|
||||||
|
this.value = this.value.replace(/\s+/g, '');;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only display the page input field when split mode is custom.
|
||||||
|
function togglePageInput() {
|
||||||
|
const mode = document.getElementById('splitMode').value;
|
||||||
|
const pageInputSection = document.getElementById('pageToSplitSection');
|
||||||
|
if (mode === "CUSTOM") {
|
||||||
|
pageInputSection.style.display = "block";
|
||||||
|
document.getElementById('pageToSplit').setAttribute("required", "true");
|
||||||
|
} else {
|
||||||
|
pageInputSection.style.display = "none";
|
||||||
|
document.getElementById('pageToSplit').removeAttribute("required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('splitMode').addEventListener('change', togglePageInput);
|
||||||
|
window.addEventListener('DOMContentLoaded', togglePageInput);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<br>
|
<br>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-sections.submit}">Submit</button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{split-by-sections.submit}">Submit</button>
|
||||||
|
Loading…
Reference in New Issue
Block a user