Merge branch 'main' into fix-imgtopdf-typo

This commit is contained in:
Anthony Stirling
2023-06-05 19:39:14 +01:00
committed by GitHub
31 changed files with 1016 additions and 353 deletions

View File

@@ -1,5 +1,9 @@
package stirling.software.SPDF.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -10,13 +14,23 @@ import io.swagger.v3.oas.models.info.Info;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion();
version = (version != null) ? version : "1.0.0";
return new OpenAPI().components(new Components()).info(
new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
}
@Bean
public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion();
if (version == null) {
Properties props = new Properties();
try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) {
props.load(input);
version = props.getProperty("version");
} catch (IOException ex) {
ex.printStackTrace();
version = "1.0.0"; // default version if all else fails
}
}
return new OpenAPI().components(new Components()).info(
new Info().title("Stirling PDF API").version(version).description("API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
}
}

View File

@@ -1,9 +1,6 @@
package stirling.software.SPDF.controller.api;
import java.io.IOException;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.WebResponseUtils;
import java.util.ArrayList;
import java.util.List;
@@ -20,232 +17,191 @@ import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
@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.")
public ResponseEntity<byte[]> deletePages(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)
throws IOException {
@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.")
public ResponseEntity<byte[]> deletePages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file from which pages will be removed")
MultipartFile pdfFile,
@RequestParam("pagesToDelete")
@Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'")
String pagesToDelete) throws IOException {
PDDocument document = PDDocument.load(pdfFile.getBytes());
PDDocument document = PDDocument.load(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(",");
// 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());
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i);
document.removePage(pageIndex);
}
return WebResponseUtils.pdfDocToWebResponse(document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
int pageIndex = pagesToRemove.get(i);
document.removePage(pageIndex);
}
return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
}
}
private enum CustomMode {
REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST,
}
private List<Integer> pageOrderToString(String[] pageOrderArr, int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
// loop through the page order array
for (String element : pageOrderArr) {
// check if the element contains a range of pages
if (element.contains("-")) {
// split the range into start and end page
String[] range = element.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages
if (end > totalPages) {
end = totalPages;
}
// loop through the range of pages
for (int j = start; j <= end; j++) {
// print the current index
newPageOrder.add(j - 1);
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1);
}
}
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;
}
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 enum CustomMode {
REVERSE_ORDER,
DUPLEX_SORT,
BOOKLET_SORT,
ODD_EVEN_SPLIT,
REMOVE_FIRST,
REMOVE_LAST,
REMOVE_FIRST_AND_LAST,
}
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> 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> reverseOrder(int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
for (int i = totalPages; i >= 1; 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> 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> 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> 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> reverseOrder(int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
for (int i = totalPages; i >= 1; i--) {
newPageOrder.add(i - 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;
}
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> processCustomMode(String customMode, int totalPages) {
try {
CustomMode mode = CustomMode.valueOf(customMode.toUpperCase());
switch (mode) {
case REVERSE_ORDER:
return reverseOrder(totalPages);
case DUPLEX_SORT:
return duplexSort(totalPages);
case BOOKLET_SORT:
return bookletSort(totalPages);
case ODD_EVEN_SPLIT:
return oddEvenSplit(totalPages);
case REMOVE_FIRST:
return removeFirst(totalPages);
case REMOVE_LAST:
return removeLast(totalPages);
case REMOVE_FIRST_AND_LAST:
return removeFirstAndLast(totalPages);
default:
throw new IllegalArgumentException("Unsupported custom mode");
}
} catch (IllegalArgumentException e) {
logger.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.")
public ResponseEntity<byte[]> rearrangePages(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile,
@RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder,
@RequestParam(required = false, value = "customMode") @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. "
+ "Valid values are:\n" + "REVERSE_ORDER: Reverses the order of all pages.\n"
+ "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). "
+ "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n"
+ "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n"
+ "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n"
+ "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) String customMode) {
try {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
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;
}
// 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();
System.out.println("pageOrder=" + pageOrder);
System.out.println("customMode length =" + customMode.length());
List<Integer> newPageOrder;
if (customMode != null && customMode.length() > 0) {
newPageOrder = processCustomMode(customMode, totalPages);
} else {
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
}
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;
}
// 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)));
}
private List<Integer> processCustomMode(String customMode, int totalPages) {
try {
CustomMode mode = CustomMode.valueOf(customMode.toUpperCase());
switch (mode) {
case REVERSE_ORDER:
return reverseOrder(totalPages);
case DUPLEX_SORT:
return duplexSort(totalPages);
case BOOKLET_SORT:
return bookletSort(totalPages);
case ODD_EVEN_SPLIT:
return oddEvenSplit(totalPages);
case REMOVE_FIRST:
return removeFirst(totalPages);
case REMOVE_LAST:
return removeLast(totalPages);
case REMOVE_FIRST_AND_LAST:
return removeFirstAndLast(totalPages);
default:
throw new IllegalArgumentException("Unsupported custom mode");
}
} catch (IllegalArgumentException e) {
logger.error("Unsupported custom mode", e);
return null;
}
}
// Remove all the pages from the original document
for (int i = document.getNumberOfPages() - 1; i >= 0; i--) {
document.removePage(i);
}
@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.")
public ResponseEntity<byte[]> rearrangePages(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to rearrange pages")
MultipartFile pdfFile,
@RequestParam(required = false, value = "pageOrder")
@Parameter(description = "The new page order as a comma-separated list of page numbers or page ranges (e.g., '1,3,5-7')")
String pageOrder,
@RequestParam(required = false, value = "customMode")
@Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. " +
"Valid values are:\n" +
"REVERSE_ORDER: Reverses the order of all pages.\n" +
"DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " +
"BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" +
"ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" +
"REMOVE_FIRST: Removes the first page.\n" +
"REMOVE_LAST: Removes the last page.\n" +
"REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n"))
String customMode) {
try {
// Load the input PDF
PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Add the pages in the new order
for (PDPage page : newPages) {
document.addPage(page);
}
// 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();
System.out.println("pageOrder=" + pageOrder);
System.out.println("customMode length =" + customMode.length());
List<Integer> newPageOrder;
if(customMode != null && customMode.length() > 0) {
newPageOrder = processCustomMode(customMode, totalPages);
} else {
newPageOrder = pageOrderToString(pageOrderArr, 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, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
return null;
}
}
return WebResponseUtils.pdfDocToWebResponse(document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
return null;
}
}
}

View File

@@ -3,10 +3,18 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -20,14 +28,19 @@ import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.WebResponseUtils;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ScalePagesController {
@@ -36,48 +49,51 @@ public class ScalePagesController {
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file.")
public ResponseEntity<byte[]> scalePages(
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = { "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL", "EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize,
@Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor)
throws IOException {
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "String", allowableValues = {
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4",
"B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL",
"EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize,
@Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor)
throws IOException {
Map<String, PageSize> sizeMap = new HashMap<>();
// Add A0 - A10
sizeMap.put("A0", PageSize.A0);
sizeMap.put("A1", PageSize.A1);
sizeMap.put("A2", PageSize.A2);
sizeMap.put("A3", PageSize.A3);
sizeMap.put("A4", PageSize.A4);
sizeMap.put("A5", PageSize.A5);
sizeMap.put("A6", PageSize.A6);
sizeMap.put("A7", PageSize.A7);
sizeMap.put("A8", PageSize.A8);
sizeMap.put("A9", PageSize.A9);
sizeMap.put("A10", PageSize.A10);
// Add B0 - B9
sizeMap.put("B0", PageSize.B0);
sizeMap.put("B1", PageSize.B1);
sizeMap.put("B2", PageSize.B2);
sizeMap.put("B3", PageSize.B3);
sizeMap.put("B4", PageSize.B4);
sizeMap.put("B5", PageSize.B5);
sizeMap.put("B6", PageSize.B6);
sizeMap.put("B7", PageSize.B7);
sizeMap.put("B8", PageSize.B8);
sizeMap.put("B9", PageSize.B9);
// Add other sizes
sizeMap.put("LETTER", PageSize.LETTER);
sizeMap.put("TABLOID", PageSize.TABLOID);
sizeMap.put("LEDGER", PageSize.LEDGER);
sizeMap.put("LEGAL", PageSize.LEGAL);
sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE);
if (!sizeMap.containsKey(targetPageSize)) {
throw new IllegalArgumentException("Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
}
// Add A0 - A10
sizeMap.put("A0", PageSize.A0);
sizeMap.put("A1", PageSize.A1);
sizeMap.put("A2", PageSize.A2);
sizeMap.put("A3", PageSize.A3);
sizeMap.put("A4", PageSize.A4);
sizeMap.put("A5", PageSize.A5);
sizeMap.put("A6", PageSize.A6);
sizeMap.put("A7", PageSize.A7);
sizeMap.put("A8", PageSize.A8);
sizeMap.put("A9", PageSize.A9);
sizeMap.put("A10", PageSize.A10);
// Add B0 - B9
sizeMap.put("B0", PageSize.B0);
sizeMap.put("B1", PageSize.B1);
sizeMap.put("B2", PageSize.B2);
sizeMap.put("B3", PageSize.B3);
sizeMap.put("B4", PageSize.B4);
sizeMap.put("B5", PageSize.B5);
sizeMap.put("B6", PageSize.B6);
sizeMap.put("B7", PageSize.B7);
sizeMap.put("B8", PageSize.B8);
sizeMap.put("B9", PageSize.B9);
// Add other sizes
sizeMap.put("LETTER", PageSize.LETTER);
sizeMap.put("TABLOID", PageSize.TABLOID);
sizeMap.put("LEDGER", PageSize.LEDGER);
sizeMap.put("LEGAL", PageSize.LEGAL);
sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE);
if (!sizeMap.containsKey(targetPageSize)) {
throw new IllegalArgumentException(
"Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
}
PageSize pageSize = sizeMap.get(targetPageSize);
byte[] bytes = file.getBytes();
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
@@ -86,9 +102,7 @@ public class ScalePagesController {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(baos);
PdfDocument outputPdf = new PdfDocument(writer);
int totalPages = pdfDoc.getNumberOfPages();
for (int i = 1; i <= totalPages; i++) {
@@ -100,7 +114,7 @@ public class ScalePagesController {
float scaleWidth = pageSize.getWidth() / rect.getWidth();
float scaleHeight = pageSize.getHeight() / rect.getHeight();
float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;
System.out.println("Scale: " + scale);
System.out.println("Scale: " + scale);
PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf);
float x = (pageSize.getWidth() - rect.getWidth() * scale) / 2; // Center Page
@@ -117,6 +131,111 @@ public class ScalePagesController {
outputPdf.close();
byte[] pdfContent = baos.toByteArray();
pdfDoc.close();
return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
return WebResponseUtils.bytesToWebResponse(pdfContent,
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
}
//TODO
@Hidden
@PostMapping(value = "/auto-crop", consumes = "multipart/form-data")
public ResponseEntity<byte[]> cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException {
byte[] bytes = file.getBytes();
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
PdfDocument pdfDoc = new PdfDocument(reader);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(baos);
PdfDocument outputPdf = new PdfDocument(writer);
int totalPages = pdfDoc.getNumberOfPages();
for (int i = 1; i <= totalPages; i++) {
PdfPage page = pdfDoc.getPage(i);
Rectangle originalMediaBox = page.getMediaBox();
Rectangle contentBox = determineContentBox(page);
// Make sure we don't go outside the original media box.
Rectangle intersection = originalMediaBox.getIntersection(contentBox);
page.setCropBox(intersection);
// Copy page to the new document
outputPdf.addPage(page.copyTo(outputPdf));
}
outputPdf.close();
byte[] pdfContent = baos.toByteArray();
pdfDoc.close();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""
+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"")
.contentType(MediaType.APPLICATION_PDF).body(pdfContent);
}
private Rectangle determineContentBox(PdfPage page) {
// Extract the text from the page and find the bounding box.
TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder();
PdfCanvasProcessor processor = new PdfCanvasProcessor(finder);
processor.processPageContent(page);
return finder.getBoundingBox();
}
private static class TextBoundingRectangleFinder implements IEventListener {
private List<Rectangle> allTextBoxes = new ArrayList<>();
public Rectangle getBoundingBox() {
// Sort the text boxes based on their vertical position
allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop));
// Consider a box an outlier if its top is more than 1.5 times the IQR above the
// third quartile.
int q1Index = allTextBoxes.size() / 4;
int q3Index = 3 * allTextBoxes.size() / 4;
double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop();
double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr;
// Initialize boundingBox to the first non-outlier box
int i = 0;
while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) {
i++;
}
if (i == allTextBoxes.size()) {
// If all boxes are outliers, just return the first one
return allTextBoxes.get(0);
}
Rectangle boundingBox = allTextBoxes.get(i);
// Extend the bounding box to include all non-outlier boxes
for (; i < allTextBoxes.size(); i++) {
Rectangle textBoundingBox = allTextBoxes.get(i);
if (textBoundingBox.getTop() > threshold) {
// This box is an outlier, skip it
continue;
}
float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft());
float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom());
float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight());
float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop());
// Add a small padding around the bounding box
float padding = 10;
boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding,
top - bottom + 2 * padding);
}
return boundingBox;
}
@Override
public void eventOccurred(IEventData data, EventType type) {
if (type == EventType.RENDER_TEXT) {
TextRenderInfo renderInfo = (TextRenderInfo) data;
allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle());
}
}
@Override
public Set<EventType> getSupportedEvents() {
return Collections.singleton(EventType.RENDER_TEXT);
}
}
}

View File

@@ -6,7 +6,6 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
@@ -29,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.GeneralUtils;
@RestController
public class SplitPDFController {
@@ -58,39 +58,28 @@ public class SplitPDFController {
pageNumbers.add(i);
}
} else {
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
String lastpage = String.valueOf(document.getNumberOfPages());
pageNumbersStr.add(lastpage);
}
for (String page : pageNumbersStr) {
if (page.contains("-")) {
String[] range = page.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
for (int i = start; i <= end; i++) {
pageNumbers.add(i);
}
} else {
pageNumbers.add(Integer.parseInt(page));
}
String[] splitPoints = pages.split(",");
for (String splitPoint : splitPoints) {
List<Integer> orderedPages = GeneralUtils.parsePageList(new String[] {splitPoint}, document.getNumberOfPages());
pageNumbers.addAll(orderedPages);
}
// Add the last page as a split point
pageNumbers.add(document.getNumberOfPages() - 1);
}
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int currentPage = 0;
for (int pageNumber : pageNumbers) {
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument = new PDDocument()) {
for (int i = currentPage; i < pageNumber; i++) {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.debug("Adding page {} to split document", i);
}
currentPage = pageNumber;
logger.debug("Setting current page to {}", currentPage);
previousPageNumber = splitPoint + 1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
@@ -102,6 +91,7 @@ public class SplitPDFController {
}
}
// closing the original document
document.close();

View File

@@ -0,0 +1,161 @@
package stirling.software.SPDF.controller.api.other;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.itextpdf.io.source.ByteArrayOutputStream;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.WebResponseUtils;
//Required for PDF manipulation
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
//Required for image manipulation
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.RescaleOp;
import java.awt.image.AffineTransformOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.awt.Color;
import java.awt.geom.AffineTransform;
//Required for image input/output
import javax.imageio.ImageIO;
//Required for file input/output
import java.io.File;
//Other required classes
import java.util.Random;
import io.swagger.v3.oas.annotations.Hidden;
@RestController
public class FakeScanController {
private static final Logger logger = LoggerFactory.getLogger(FakeScanController.class);
//TODO
@Hidden
@PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
@Operation(
summary = "Repair a PDF file",
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response."
)
public ResponseEntity<byte[]> repairPdf(
@RequestPart(required = true, value = "fileInput")
@Parameter(description = "The input PDF file to be repaired", required = true)
MultipartFile inputFile) throws IOException, InterruptedException {
PDDocument document = PDDocument.load(inputFile.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document);
for (int page = 0; page < document.getNumberOfPages(); ++page)
{
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
ImageIO.write(image, "png", new File("scanned-" + (page+1) + ".png"));
}
document.close();
// Constants
int scannedness = 90; // Value between 0 and 100
int dirtiness = 0; // Value between 0 and 100
// Load the source image
BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png"));
// Create the destination image
BufferedImage destinationImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType());
// Apply a brightness and contrast effect based on the "scanned-ness"
float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5
float offset = scannedness * 1.5f; // Between 0 and 150
BufferedImageOp op = new RescaleOp(scaleFactor, offset, null);
op.filter(sourceImage, destinationImage);
// Apply a rotation effect
double rotationRequired = Math.toRadians((new Random().nextInt(3 - 1) + 1)); // Random angle between 1 and 3 degrees
double locationX = destinationImage.getWidth() / 2;
double locationY = destinationImage.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
destinationImage = rotateOp.filter(destinationImage, null);
// Apply a blur effect based on the "scanned-ness"
float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2
float[] matrix = {
blurIntensity, blurIntensity, blurIntensity,
blurIntensity, blurIntensity, blurIntensity,
blurIntensity, blurIntensity, blurIntensity
};
BufferedImageOp blurOp = new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null);
destinationImage = blurOp.filter(destinationImage, null);
// Add noise to the image based on the "dirtiness"
Random random = new Random();
for (int y = 0; y < destinationImage.getHeight(); y++) {
for (int x = 0; x < destinationImage.getWidth(); x++) {
if (random.nextInt(100) < dirtiness) {
// Change the pixel color to black randomly based on the "dirtiness"
destinationImage.setRGB(x, y, Color.BLACK.getRGB());
}
}
}
// Save the image
ImageIO.write(destinationImage, "PNG", new File("scanned-1.png"));
PDDocument documentOut = new PDDocument();
for (int page = 1; page <= document.getNumberOfPages(); ++page)
{
BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png"));
// Adjust the dimensions of the page
PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1));
documentOut.addPage(pdPage);
PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim);
PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage);
// Draw the image with a slight offset and enlarged dimensions
contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2);
contentStream.close();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
documentOut.save(baos);
documentOut.close();
// Return the optimized PDF as a response
String outputFilename = inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf";
return WebResponseUtils.boasToWebResponse(baos, outputFilename);
}
}

View File

@@ -1,9 +1,6 @@
package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayInputStream;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.WebResponseUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -53,6 +50,8 @@ import com.itextpdf.signatures.SignatureUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
public class CertSignController {

View File

@@ -122,4 +122,11 @@ public class OtherWebController {
return "other/scale-pages";
}
@GetMapping("/auto-crop")
@Hidden
public String autoCropForm(Model model) {
model.addAttribute("currentPage", "auto-crop");
return "other/auto-crop";
}
}

View File

@@ -1,5 +1,8 @@
package stirling.software.SPDF.utils;
import java.util.ArrayList;
import java.util.List;
public class GeneralUtils {
public static Long convertSizeToBytes(String sizeStr) {
@@ -27,4 +30,62 @@ public class GeneralUtils {
return null;
}
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
// loop through the page order array
for (String element : pageOrderArr) {
// check if the element contains a range of pages
if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
// Handle page order as a function
int coefficient = 0;
int constant = 0;
boolean coefficientExists = false;
boolean constantExists = false;
if (element.contains("n")) {
String[] parts = element.split("n");
if (!parts[0].equals("") && parts[0] != null) {
coefficient = Integer.parseInt(parts[0]);
coefficientExists = true;
}
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) {
constant = Integer.parseInt(parts[1]);
constantExists = true;
}
} else if (element.contains("+")) {
constant = Integer.parseInt(element.replace("+", ""));
constantExists = true;
}
for (int i = 1; i <= totalPages; i++) {
int pageNum = coefficientExists ? coefficient * i : i;
pageNum += constantExists ? constant : 0;
if (pageNum <= totalPages && pageNum > 0) {
newPageOrder.add(pageNum - 1);
}
}
} else if (element.contains("-")) {
// split the range into start and end page
String[] range = element.split("-");
int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages
if (end > totalPages) {
end = totalPages;
}
// loop through the range of pages
for (int j = start; j <= end; j++) {
// print the current index
newPageOrder.add(j - 1);
}
} else {
// if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1);
}
}
return newPageOrder;
}
}

View File

@@ -9,12 +9,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;