2023-04-28 23:18:10 +01:00
|
|
|
package stirling.software.SPDF.controller.api;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.ArrayList;
|
2024-03-04 21:14:45 +01:00
|
|
|
import java.util.Collections;
|
2023-04-28 23:18:10 +01:00
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
|
import org.apache.pdfbox.pdmodel.PDPage;
|
|
|
|
import org.springframework.http.ResponseEntity;
|
2023-09-09 00:25:27 +01:00
|
|
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
2025-06-02 22:49:46 +01:00
|
|
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
2023-09-11 23:19:50 +01:00
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
2023-04-28 23:18:10 +01:00
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
2024-02-07 21:40:33 -05:00
|
|
|
import io.github.pixee.security.Filenames;
|
2023-05-08 15:20:04 +01:00
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
2023-06-25 09:16:32 +01:00
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2025-04-25 15:35:12 +02:00
|
|
|
import lombok.RequiredArgsConstructor;
|
2024-12-17 10:26:18 +01:00
|
|
|
import lombok.extern.slf4j.Slf4j;
|
2025-02-23 13:36:21 +00:00
|
|
|
|
2023-09-09 00:25:27 +01:00
|
|
|
import stirling.software.SPDF.model.SortTypes;
|
2023-10-05 21:20:05 +01:00
|
|
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
2023-09-09 00:25:27 +01:00
|
|
|
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
2025-05-27 13:01:52 +01:00
|
|
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
|
|
|
import stirling.software.common.util.GeneralUtils;
|
|
|
|
import stirling.software.common.util.WebResponseUtils;
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-04-28 23:18:10 +01:00
|
|
|
@RestController
|
2023-09-11 23:19:50 +01:00
|
|
|
@RequestMapping("/api/v1/general")
|
2024-12-17 10:26:18 +01:00
|
|
|
@Slf4j
|
2023-06-25 09:16:32 +01:00
|
|
|
@Tag(name = "General", description = "General APIs")
|
2025-04-25 15:35:12 +02:00
|
|
|
@RequiredArgsConstructor
|
2023-04-28 23:18:10 +01:00
|
|
|
public class RearrangePagesPDFController {
|
|
|
|
|
2025-03-12 13:13:44 +01:00
|
|
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
2024-09-14 16:29:39 +01:00
|
|
|
|
2025-06-02 22:49:46 +01:00
|
|
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
2023-06-23 23:29:53 +01:00
|
|
|
@Operation(
|
|
|
|
summary = "Remove pages from a PDF file",
|
|
|
|
description =
|
2025-03-12 13:13:44 +01:00
|
|
|
"This endpoint removes specified pages from a given PDF file. Users can provide"
|
|
|
|
+ " a comma-separated list of page numbers or ranges to delete. Input:PDF"
|
|
|
|
+ " Output:PDF Type:SISO")
|
2023-10-05 21:20:05 +01:00
|
|
|
public ResponseEntity<byte[]> deletePages(@ModelAttribute PDFWithPageNums request)
|
2023-06-03 17:21:59 +01:00
|
|
|
throws IOException {
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-10-05 21:20:05 +01:00
|
|
|
MultipartFile pdfFile = request.getFileInput();
|
|
|
|
String pagesToDelete = request.getPageNumbers();
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2024-09-14 16:29:39 +01:00
|
|
|
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 22:56:15 +01:00
|
|
|
// Split the page order string into an array of page numbers or range of numbers
|
2023-06-03 17:21:59 +01:00
|
|
|
String[] pageOrderArr = pagesToDelete.split(",");
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
List<Integer> pagesToRemove =
|
2024-03-10 14:00:00 +00:00
|
|
|
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages(), false);
|
2024-03-04 21:14:45 +01:00
|
|
|
|
|
|
|
Collections.sort(pagesToRemove);
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
|
|
|
int pageIndex = pagesToRemove.get(i);
|
|
|
|
document.removePage(pageIndex);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
return WebResponseUtils.pdfDocToWebResponse(
|
2023-12-30 19:11:27 +00:00
|
|
|
document,
|
2024-02-07 21:40:33 -05:00
|
|
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
|
|
|
.replaceFirst("[.][^.]+$", "")
|
|
|
|
+ "_removed_pages.pdf");
|
2023-06-03 17:21:59 +01:00
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> removeFirst(int totalPages) {
|
|
|
|
if (totalPages <= 1) return new ArrayList<>();
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = 2; i <= totalPages; i++) {
|
|
|
|
newPageOrder.add(i - 1);
|
|
|
|
}
|
|
|
|
return newPageOrder;
|
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> removeLast(int totalPages) {
|
|
|
|
if (totalPages <= 1) return new ArrayList<>();
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = 1; i < totalPages; i++) {
|
|
|
|
newPageOrder.add(i - 1);
|
|
|
|
}
|
|
|
|
return newPageOrder;
|
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> removeFirstAndLast(int totalPages) {
|
|
|
|
if (totalPages <= 2) return new ArrayList<>();
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = 2; i < totalPages; i++) {
|
|
|
|
newPageOrder.add(i - 1);
|
|
|
|
}
|
|
|
|
return newPageOrder;
|
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-09-05 20:05:33 +02:00
|
|
|
private List<Integer> reverseOrder(int totalPages) {
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = totalPages; i >= 1; i--) {
|
|
|
|
newPageOrder.add(i - 1);
|
|
|
|
}
|
|
|
|
return newPageOrder;
|
2023-09-05 19:48:16 +02:00
|
|
|
}
|
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> duplexSort(int totalPages) {
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
2023-09-09 00:25:27 +01:00
|
|
|
int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages
|
2023-06-03 17:21:59 +01:00
|
|
|
for (int i = 1; i <= half; i++) {
|
2023-06-21 21:19:52 +01:00
|
|
|
newPageOrder.add(i - 1);
|
|
|
|
if (i <= totalPages - half) { // Avoid going out of bounds
|
2023-09-09 00:25:27 +01:00
|
|
|
newPageOrder.add(totalPages - i);
|
2023-06-03 17:21:59 +01:00
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
return newPageOrder;
|
|
|
|
}
|
2023-05-26 23:53:11 +01:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> bookletSort(int totalPages) {
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = 0; i < totalPages / 2; i++) {
|
|
|
|
newPageOrder.add(i);
|
2023-09-05 20:05:33 +02:00
|
|
|
newPageOrder.add(totalPages - i - 1);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
return newPageOrder;
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> sideStitchBooklet(int totalPages) {
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = 0; i < (totalPages + 3) / 4; i++) {
|
2023-09-05 20:05:33 +02:00
|
|
|
int begin = i * 4;
|
|
|
|
newPageOrder.add(Math.min(begin + 3, totalPages - 1));
|
|
|
|
newPageOrder.add(Math.min(begin, totalPages - 1));
|
|
|
|
newPageOrder.add(Math.min(begin + 1, totalPages - 1));
|
|
|
|
newPageOrder.add(Math.min(begin + 2, totalPages - 1));
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
return newPageOrder;
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
private List<Integer> oddEvenSplit(int totalPages) {
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
for (int i = 1; i <= totalPages; i += 2) {
|
|
|
|
newPageOrder.add(i - 1);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
for (int i = 2; i <= totalPages; i += 2) {
|
|
|
|
newPageOrder.add(i - 1);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
return newPageOrder;
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
|
2024-06-12 23:21:02 +02:00
|
|
|
/**
|
|
|
|
* Rearrange pages in a PDF file by merging odd and even pages. The first half of the pages will
|
|
|
|
* be the odd pages, and the second half will be the even pages as input. <br>
|
|
|
|
* This method is visible for testing purposes only.
|
|
|
|
*
|
|
|
|
* @param totalPages Total number of pages in the PDF file.
|
|
|
|
* @return List of page numbers in the new order. The first page is 0.
|
|
|
|
*/
|
|
|
|
List<Integer> oddEvenMerge(int totalPages) {
|
|
|
|
List<Integer> newPageOrderZeroBased = new ArrayList<>();
|
|
|
|
int numberOfOddPages = (totalPages + 1) / 2;
|
|
|
|
|
|
|
|
for (int oneBasedIndex = 1; oneBasedIndex < (numberOfOddPages + 1); oneBasedIndex++) {
|
|
|
|
newPageOrderZeroBased.add((oneBasedIndex - 1));
|
|
|
|
if (numberOfOddPages + oneBasedIndex <= totalPages) {
|
|
|
|
newPageOrderZeroBased.add((numberOfOddPages + oneBasedIndex - 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newPageOrderZeroBased;
|
|
|
|
}
|
|
|
|
|
2025-02-18 11:57:56 +00:00
|
|
|
private List<Integer> duplicate(int totalPages, String pageOrder) {
|
|
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
|
|
int duplicateCount;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Parse the duplicate count from pageOrder
|
|
|
|
duplicateCount =
|
|
|
|
pageOrder != null && !pageOrder.isEmpty()
|
|
|
|
? Integer.parseInt(pageOrder.trim())
|
|
|
|
: 2; // Default to 2 if not specified
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
log.error("Invalid duplicate count specified", e);
|
|
|
|
duplicateCount = 2; // Default to 2 if invalid input
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate duplicate count
|
|
|
|
if (duplicateCount < 1) {
|
|
|
|
duplicateCount = 2; // Default to 2 if invalid input
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each page in the document
|
|
|
|
for (int pageNum = 0; pageNum < totalPages; pageNum++) {
|
|
|
|
// Add the current page index duplicateCount times
|
|
|
|
for (int dupCount = 0; dupCount < duplicateCount; dupCount++) {
|
|
|
|
newPageOrder.add(pageNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newPageOrder;
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<Integer> processSortTypes(String sortTypes, int totalPages, String pageOrder) {
|
2023-12-30 19:11:27 +00:00
|
|
|
try {
|
2023-09-09 00:25:27 +01:00
|
|
|
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
|
2023-06-03 17:21:59 +01:00
|
|
|
switch (mode) {
|
|
|
|
case REVERSE_ORDER:
|
|
|
|
return reverseOrder(totalPages);
|
|
|
|
case DUPLEX_SORT:
|
|
|
|
return duplexSort(totalPages);
|
|
|
|
case BOOKLET_SORT:
|
|
|
|
return bookletSort(totalPages);
|
2023-09-05 20:05:33 +02:00
|
|
|
case SIDE_STITCH_BOOKLET_SORT:
|
|
|
|
return sideStitchBooklet(totalPages);
|
2023-06-03 17:21:59 +01:00
|
|
|
case ODD_EVEN_SPLIT:
|
|
|
|
return oddEvenSplit(totalPages);
|
2024-06-12 23:21:02 +02:00
|
|
|
case ODD_EVEN_MERGE:
|
|
|
|
return oddEvenMerge(totalPages);
|
2023-06-03 17:21:59 +01:00
|
|
|
case REMOVE_FIRST:
|
|
|
|
return removeFirst(totalPages);
|
|
|
|
case REMOVE_LAST:
|
|
|
|
return removeLast(totalPages);
|
|
|
|
case REMOVE_FIRST_AND_LAST:
|
|
|
|
return removeFirstAndLast(totalPages);
|
2025-02-18 11:57:56 +00:00
|
|
|
case DUPLICATE:
|
|
|
|
return duplicate(totalPages, pageOrder);
|
2023-12-30 19:11:27 +00:00
|
|
|
default:
|
2023-06-03 17:21:59 +01:00
|
|
|
throw new IllegalArgumentException("Unsupported custom mode");
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2023-06-03 17:21:59 +01:00
|
|
|
} catch (IllegalArgumentException e) {
|
2024-12-17 10:26:18 +01:00
|
|
|
log.error("Unsupported custom mode", e);
|
2023-06-03 17:21:59 +01:00
|
|
|
return null;
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-02 22:49:46 +01:00
|
|
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
2023-06-21 21:19:52 +01:00
|
|
|
@Operation(
|
|
|
|
summary = "Rearrange pages in a PDF file",
|
|
|
|
description =
|
2025-03-12 13:13:44 +01:00
|
|
|
"This endpoint rearranges pages in a given PDF file based on the specified page"
|
|
|
|
+ " order or custom mode. Users can provide a page order as a"
|
|
|
|
+ " comma-separated list of page numbers or page ranges, or a custom mode."
|
|
|
|
+ " Input:PDF Output:PDF")
|
2023-09-09 00:25:27 +01:00
|
|
|
public ResponseEntity<byte[]> rearrangePages(@ModelAttribute RearrangePagesRequest request)
|
|
|
|
throws IOException {
|
|
|
|
MultipartFile pdfFile = request.getFileInput();
|
|
|
|
String pageOrder = request.getPageNumbers();
|
|
|
|
String sortType = request.getCustomMode();
|
2023-12-30 19:11:27 +00:00
|
|
|
try {
|
2023-06-03 17:21:59 +01:00
|
|
|
// Load the input PDF
|
2025-03-10 20:17:45 +00:00
|
|
|
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
// Split the page order string into an array of page numbers or range of numbers
|
|
|
|
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
|
|
|
|
int totalPages = document.getNumberOfPages();
|
|
|
|
List<Integer> newPageOrder;
|
2025-02-18 11:57:56 +00:00
|
|
|
if (sortType != null
|
|
|
|
&& sortType.length() > 0
|
|
|
|
&& !"custom".equals(sortType.toLowerCase())) {
|
|
|
|
newPageOrder = processSortTypes(sortType, totalPages, pageOrder);
|
2023-12-30 19:11:27 +00:00
|
|
|
} else {
|
2024-03-10 14:00:00 +00:00
|
|
|
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2024-12-17 10:26:18 +01:00
|
|
|
log.info("newPageOrder = " + newPageOrder);
|
|
|
|
log.info("totalPages = " + totalPages);
|
2023-06-03 17:21:59 +01:00
|
|
|
// Create a new list to hold the pages in the new order
|
|
|
|
List<PDPage> newPages = new ArrayList<>();
|
|
|
|
for (int i = 0; i < newPageOrder.size(); i++) {
|
|
|
|
newPages.add(document.getPage(newPageOrder.get(i)));
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
// Remove all the pages from the original document
|
|
|
|
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
|
|
|
document.removePage(i);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
// Add the pages in the new order
|
|
|
|
for (PDPage page : newPages) {
|
|
|
|
document.addPage(page);
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 17:21:59 +01:00
|
|
|
return WebResponseUtils.pdfDocToWebResponse(
|
2023-12-30 19:11:27 +00:00
|
|
|
document,
|
2024-02-07 21:40:33 -05:00
|
|
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
|
|
|
.replaceFirst("[.][^.]+$", "")
|
2023-06-03 17:21:59 +01:00
|
|
|
+ "_rearranged.pdf");
|
|
|
|
} catch (IOException e) {
|
2024-12-17 10:26:18 +01:00
|
|
|
log.error("Failed rearranging documents", e);
|
2023-06-03 17:21:59 +01:00
|
|
|
return null;
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-28 23:18:10 +01:00
|
|
|
}
|