# Description of Changes This pull request includes several changes primarily focused on improving configuration management, removing deprecated methods, and updating paths for external dependencies. The most important changes are summarized below: ### Configuration Management Improvements: * Added a new `RuntimePathConfig` class to manage dynamic paths for operations and pipeline configurations (`src/main/java/stirling/software/SPDF/config/RuntimePathConfig.java`). * Removed the `bookAndHtmlFormatsInstalled` bean and its associated logic from `AppConfig` and `EndpointConfiguration` (`src/main/java/stirling/software/SPDF/config/AppConfig.java`, `src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`). [[1]](diffhunk://#diff-4d774ec79aa55750c0a4739bee971b68877078b73654e863fd40ee924347e143L130-L138) [[2]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL12-L35) [[3]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL275-L280) ### External Dependency Path Updates: * Updated paths for `weasyprint` and `unoconvert` in `ExternalAppDepConfig` to use values from `RuntimePathConfig` (`src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java`). [[1]](diffhunk://#diff-c47af298c07c2622aa98b038b78822c56bdb002de71081e102d344794e7832a6R12-L33) [[2]](diffhunk://#diff-c47af298c07c2622aa98b038b78822c56bdb002de71081e102d344794e7832a6L104-R115) ### Minor Adjustments: * Corrected a typo from "Unoconv" to "Unoconvert" in `EndpointConfiguration` (`src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`). --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
296 lines
12 KiB
Java
296 lines
12 KiB
Java
package stirling.software.SPDF.controller.api;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
import org.apache.pdfbox.Loader;
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
import org.apache.pdfbox.pdmodel.PDPage;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
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 io.github.pixee.security.Filenames;
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import stirling.software.SPDF.model.SortTypes;
|
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
|
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
|
import stirling.software.SPDF.utils.GeneralUtils;
|
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
|
|
@RestController
|
|
@RequestMapping("/api/v1/general")
|
|
@Slf4j
|
|
@Tag(name = "General", description = "General APIs")
|
|
public class RearrangePagesPDFController {
|
|
|
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
|
|
|
@Autowired
|
|
public RearrangePagesPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
|
}
|
|
|
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
|
@Operation(
|
|
summary = "Remove pages from a PDF file",
|
|
description =
|
|
"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")
|
|
public ResponseEntity<byte[]> deletePages(@ModelAttribute PDFWithPageNums request)
|
|
throws IOException {
|
|
|
|
MultipartFile pdfFile = request.getFileInput();
|
|
String pagesToDelete = request.getPageNumbers();
|
|
|
|
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
|
|
|
// Split the page order string into an array of page numbers or range of numbers
|
|
String[] pageOrderArr = pagesToDelete.split(",");
|
|
|
|
List<Integer> pagesToRemove =
|
|
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages(), false);
|
|
|
|
Collections.sort(pagesToRemove);
|
|
|
|
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
|
int pageIndex = pagesToRemove.get(i);
|
|
document.removePage(pageIndex);
|
|
}
|
|
return WebResponseUtils.pdfDocToWebResponse(
|
|
document,
|
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
|
.replaceFirst("[.][^.]+$", "")
|
|
+ "_removed_pages.pdf");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private List<Integer> reverseOrder(int totalPages) {
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
for (int i = totalPages; i >= 1; i--) {
|
|
newPageOrder.add(i - 1);
|
|
}
|
|
return newPageOrder;
|
|
}
|
|
|
|
private List<Integer> duplexSort(int totalPages) {
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages
|
|
for (int i = 1; i <= half; i++) {
|
|
newPageOrder.add(i - 1);
|
|
if (i <= totalPages - half) { // Avoid going out of bounds
|
|
newPageOrder.add(totalPages - i);
|
|
}
|
|
}
|
|
return newPageOrder;
|
|
}
|
|
|
|
private List<Integer> bookletSort(int totalPages) {
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
for (int i = 0; i < totalPages / 2; i++) {
|
|
newPageOrder.add(i);
|
|
newPageOrder.add(totalPages - i - 1);
|
|
}
|
|
return newPageOrder;
|
|
}
|
|
|
|
private List<Integer> sideStitchBooklet(int totalPages) {
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
for (int i = 0; i < (totalPages + 3) / 4; i++) {
|
|
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));
|
|
}
|
|
return newPageOrder;
|
|
}
|
|
|
|
private List<Integer> oddEvenSplit(int totalPages) {
|
|
List<Integer> newPageOrder = new ArrayList<>();
|
|
for (int i = 1; i <= totalPages; i += 2) {
|
|
newPageOrder.add(i - 1);
|
|
}
|
|
for (int i = 2; i <= totalPages; i += 2) {
|
|
newPageOrder.add(i - 1);
|
|
}
|
|
return newPageOrder;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
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) {
|
|
try {
|
|
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
|
|
switch (mode) {
|
|
case REVERSE_ORDER:
|
|
return reverseOrder(totalPages);
|
|
case DUPLEX_SORT:
|
|
return duplexSort(totalPages);
|
|
case BOOKLET_SORT:
|
|
return bookletSort(totalPages);
|
|
case SIDE_STITCH_BOOKLET_SORT:
|
|
return sideStitchBooklet(totalPages);
|
|
case ODD_EVEN_SPLIT:
|
|
return oddEvenSplit(totalPages);
|
|
case ODD_EVEN_MERGE:
|
|
return oddEvenMerge(totalPages);
|
|
case REMOVE_FIRST:
|
|
return removeFirst(totalPages);
|
|
case REMOVE_LAST:
|
|
return removeLast(totalPages);
|
|
case REMOVE_FIRST_AND_LAST:
|
|
return removeFirstAndLast(totalPages);
|
|
case DUPLICATE:
|
|
return duplicate(totalPages, pageOrder);
|
|
default:
|
|
throw new IllegalArgumentException("Unsupported custom mode");
|
|
}
|
|
} catch (IllegalArgumentException e) {
|
|
log.error("Unsupported custom mode", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
|
@Operation(
|
|
summary = "Rearrange pages in a PDF file",
|
|
description =
|
|
"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")
|
|
public ResponseEntity<byte[]> rearrangePages(@ModelAttribute RearrangePagesRequest request)
|
|
throws IOException {
|
|
MultipartFile pdfFile = request.getFileInput();
|
|
String pageOrder = request.getPageNumbers();
|
|
String sortType = request.getCustomMode();
|
|
try {
|
|
// Load the input PDF
|
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
|
|
|
// 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;
|
|
if (sortType != null
|
|
&& sortType.length() > 0
|
|
&& !"custom".equals(sortType.toLowerCase())) {
|
|
newPageOrder = processSortTypes(sortType, totalPages, pageOrder);
|
|
} else {
|
|
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
|
|
}
|
|
log.info("newPageOrder = " + newPageOrder);
|
|
log.info("totalPages = " + totalPages);
|
|
// 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)));
|
|
}
|
|
|
|
// Remove all the pages from the original document
|
|
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
|
|
document.removePage(i);
|
|
}
|
|
|
|
// Add the pages in the new order
|
|
for (PDPage page : newPages) {
|
|
document.addPage(page);
|
|
}
|
|
|
|
return WebResponseUtils.pdfDocToWebResponse(
|
|
document,
|
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
|
.replaceFirst("[.][^.]+$", "")
|
|
+ "_rearranged.pdf");
|
|
} catch (IOException e) {
|
|
log.error("Failed rearranging documents", e);
|
|
return null;
|
|
}
|
|
}
|
|
}
|