Merge branch 'main' into fix-multitool-filemixing

This commit is contained in:
Anthony Stirling 2023-06-05 19:40:00 +01:00 committed by GitHub
commit 20c74dac60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1016 additions and 353 deletions

37
.github/workflows/swagger.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Update Swagger
on:
workflow_dispatch:
push:
branches:
- master
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.2
- name: Set up JDK 17
uses: actions/setup-java@v3.11.0
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs
- name: Upload Swagger Documentation to SwaggerHub
run: ./gradlew swaggerhubUpload
env:
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
- name: Set API version as published and default on SwaggerHub
run: |
curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}"
env:
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ local.properties
.recommenders
.classpath
.project
version.properties
# Gradle
.gradle

View file

@ -2,6 +2,8 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'org.springdoc.openapi-gradle-plugin' version '1.6.0'
id "io.swagger.swaggerhub" version "1.1.0"
}
group = 'stirling.software'
@ -12,6 +14,12 @@ repositories {
mavenCentral()
}
openApi {
apiDocsUrl = "http://localhost:8080/v3/api-docs"
outputDir = file("$projectDir")
outputFileName = "SwaggerDoc.json"
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.0'
@ -34,6 +42,25 @@ dependencies {
}
task writeVersion {
def propsFile = file('src/main/resources/version.properties')
def props = new Properties()
props.setProperty('version', version)
props.store(propsFile.newWriter(), null)
}
swaggerhubUpload {
//dependsOn generateOpenApiDocs // Depends on your task generating Swagger docs
api 'Stirling-PDF' // The name of your API on SwaggerHub
owner 'Frooodle' // Your SwaggerHub username (or organization name)
version project.version // The version of your API
inputFile './SwaggerDoc.json' // The path to your Swagger docs
token "${System.getenv('SWAGGERHUB_API_KEY')}" // Your SwaggerHub API key, passed as an environment variable
oas '3.0.0' // The version of the OpenAPI Specification you're using
}
jar {
enabled = false
manifest {

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;
@ -13,10 +17,20 @@ public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion();
version = (version != null) ? version : "1.0.0";
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,79 +17,45 @@ 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);
@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.")
@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 {
@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());
// Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(",");
List<Integer> pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages());
List<Integer> pagesToRemove = GeneralUtils.parsePageList(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");
return WebResponseUtils.pdfDocToWebResponse(document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
}
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);
}
}
return newPageOrder;
}
private enum CustomMode {
REVERSE_ORDER,
DUPLEX_SORT,
BOOKLET_SORT,
ODD_EVEN_SPLIT,
REMOVE_FIRST,
REMOVE_LAST,
REMOVE_FIRST_AND_LAST,
REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST,
}
private List<Integer> removeFirst(int totalPages) {
if (totalPages <= 1) return new ArrayList<>();
if (totalPages <= 1)
return new ArrayList<>();
List<Integer> newPageOrder = new ArrayList<>();
for (int i = 2; i <= totalPages; i++) {
newPageOrder.add(i - 1);
@ -101,7 +64,8 @@ public class RearrangePagesPDFController {
}
private List<Integer> removeLast(int totalPages) {
if (totalPages <= 1) return new ArrayList<>();
if (totalPages <= 1)
return new ArrayList<>();
List<Integer> newPageOrder = new ArrayList<>();
for (int i = 1; i < totalPages; i++) {
newPageOrder.add(i - 1);
@ -110,7 +74,8 @@ public class RearrangePagesPDFController {
}
private List<Integer> removeFirstAndLast(int totalPages) {
if (totalPages <= 2) return new ArrayList<>();
if (totalPages <= 2)
return new ArrayList<>();
List<Integer> newPageOrder = new ArrayList<>();
for (int i = 2; i < totalPages; i++) {
newPageOrder.add(i - 1);
@ -118,7 +83,6 @@ public class RearrangePagesPDFController {
return newPageOrder;
}
private List<Integer> reverseOrder(int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
for (int i = totalPages; i >= 1; i--) {
@ -139,7 +103,6 @@ public class RearrangePagesPDFController {
return newPageOrder;
}
private List<Integer> bookletSort(int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
for (int i = 0; i < totalPages / 2; i++) {
@ -188,26 +151,17 @@ public class RearrangePagesPDFController {
}
@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.")
@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) {
@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());
@ -221,7 +175,7 @@ public class RearrangePagesPDFController {
if (customMode != null && customMode.length() > 0) {
newPageOrder = processCustomMode(customMode, totalPages);
} else {
newPageOrder = pageOrderToString(pageOrderArr, totalPages);
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
}
// Create a new list to hold the pages in the new order
@ -240,7 +194,8 @@ public class RearrangePagesPDFController {
document.addPage(page);
}
return WebResponseUtils.pdfDocToWebResponse(document, pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
return WebResponseUtils.pdfDocToWebResponse(document,
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
return null;
@ -248,4 +203,5 @@ public class RearrangePagesPDFController {
}
}

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 {
@ -37,7 +50,10 @@ public class ScalePagesController {
@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 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 {
@ -73,12 +89,12 @@ public class ScalePagesController {
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");
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));
PdfDocument pdfDoc = new PdfDocument(reader);
@ -87,8 +103,6 @@ public class ScalePagesController {
PdfWriter writer = new PdfWriter(baos);
PdfDocument outputPdf = new PdfDocument(writer);
int totalPages = pdfDoc.getNumberOfPages();
for (int i = 1; i <= totalPages; i++) {
@ -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;

View file

@ -129,15 +129,37 @@ home.repair.desc = يحاول إصلاح ملف PDF تالف / معطل
home.removeBlanks.title = إزالة الصفحات الفارغة
home.removeBlanks.desc = يكتشف ويزيل الصفحات الفارغة من المستند
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.compare.title = قارن
home.compare.desc = يقارن ويظهر الاختلافات بين 2 من مستندات PDF
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf = تنزيل PDF
text=نص
font=الخط
selectFillter = - حدد -
pageNum = رقم الصفحة
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title = توقيع الشهادة
certSign.header = قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم)
certSign.selectPDF = حدد ملف PDF للتوقيع:
@ -339,6 +361,9 @@ addPassword.selectText.10=منع التعديل
addPassword.selectText.11=منع تعديل التعليقات التوضيحية
addPassword.selectText.12=منع الطباعة
addPassword.selectText.13=منع طباعة تنسيقات مختلفة
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=تشفير
#watermark

View file

@ -122,15 +122,37 @@ home.repair.desc=Intenta reparar un PDF danyat o trencat
home.removeBlanks.title=Elimina les pàgines en blanc
home.removeBlanks.desc=Detecta i elimina les pàgines en blanc d'un document
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.compare.title=Compara
home.compare.desc=Compara i mostra les diferències entre 2 documents PDF
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Descarregueu PDF
text=Text
font=Tipus de lletra
selectFillter=-- Selecciona --
pageNum=Número de pàgina
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Significació del certificat
certSign.header=Firmar un PDF amb el vostre certificat (Treball en curs)
certSign.selectPDF=Seleccioneu un fitxer PDF per signar:
@ -337,6 +359,9 @@ addPassword.selectText.10=Evita modificacions
addPassword.selectText.11=Evita modificacions d'annotacions
addPassword.selectText.12=Evita impressió
addPassword.selectText.13=Evita impressió de diferents formats
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Encripta
#watermark

View file

@ -121,15 +121,37 @@ home.repair.desc=Versucht, ein beschädigtes/kaputtes PDF zu reparieren
home.removeBlanks.title=Leere Seiten entfernen
home.removeBlanks.desc=Erkennt und entfernt leere Seiten aus einem Dokument
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.compare.title=Vergleichen
home.compare.desc=Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=PDF herunterladen
text=Text
font=Schriftart
selectFillter=-- Auswählen --
pageNum=Seitenzahl
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Zertifikatsignierung
certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit)
certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus:
@ -334,6 +356,9 @@ addPassword.selectText.10=Modifizierung verhindern
addPassword.selectText.11=Ändern von Kommentaren verhindern
addPassword.selectText.12=Drucken verhindern
addPassword.selectText.13=Drucken verschiedener Formate verhindern
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Verschlüsseln
#watermark

View file

@ -10,7 +10,7 @@ multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
imgPrompt=Select Image(s)
genericSubmit=Submit
processTimeWarning=Warning: This process can take up to a minute depending on file-size
pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers) :
pageOrderPrompt=Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :
goToPage=Go
true=True
false=False
@ -54,14 +54,11 @@ home.pdfOrganiser.title=Organise
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
home.addImage.title=Add image
home.addImage.desc=Adds a image onto a set location on the PDF (Work in progress)
home.addImage.desc=Adds a image onto a set location on the PDF
home.watermark.title=Add Watermark
home.watermark.desc=Add a custom watermark to your PDF document.
home.remove-watermark.title=Remove Watermark
home.remove-watermark.desc=Remove watermarks from your PDF document.
home.permissions.title=Change Permissions
home.permissions.desc=Change the permissions of your PDF document
@ -131,8 +128,8 @@ home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page-scale
home.scalePages.desc=Change the size of page contents while maintaining a set page-size
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of a page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect

View file

@ -121,15 +121,37 @@ home.repair.desc=Intenta reparar un PDF corrupto/roto
home.removeBlanks.title=Eliminar páginas en blanco
home.removeBlanks.descdetecta y elimina páginas en blanco de un documento
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.compare.title=Comparar
home.compare.desc=Compara y muestra las diferencias entre 2 documentos PDF
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Descargar PDF
text=Texto
font=Fuente
selectFilter=-- Seleccionar --
pageNum=Número de página
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Firma de certificado
certSign.header=Firme un PDF con su certificado (Trabajo en progreso)
certSign.selectPDF=Seleccione un archivo PDF para firmar:
@ -335,6 +357,9 @@ addPassword.selectText.10=Impedir modificación
addPassword.selectText.11=Impedir modificación de anotaciones
addPassword.selectText.12=Impedir imprimir
addPassword.selectText.13=Impedir imprimir diferentes formatos
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Encriptar
#watermark

View file

@ -127,15 +127,37 @@ home.repair.desc=Essaye de réparer un PDF corrompu/cassé
home.removeBlanks.title=Supprimer les pages vierges
home.removeBlanks.desc=Détecte et supprime les pages vierges d'un document
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.compare.title=Comparer
home.compare.desc=Compare et affiche les différences entre 2 documents PDF
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Télécharger le PDF
text=Texte
font=Police
selectFilter=-- Sélectionner --
pageNum=numéro de page
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Signature du certificat
certSign.header=Signer un PDF avec votre certificat (Travail en cours)
certSign.selectPDF=Sélectionnez un fichier PDF à signer :
@ -334,6 +356,9 @@ addPassword.selectText.10=Empêcher la modification
addPassword.selectText.11=Empêcher la modification des annotations
addPassword.selectText.12=Empêcher l'impression
addPassword.selectText.13=Empêcher l'impression de différents formats
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Crypter
#watermark

View file

@ -122,15 +122,37 @@ home.repair.desc=Prova a riparare un PDF corrotto.
home.removeBlanks.title=Rimuovi pagine vuote
home.removeBlanks.desc=Trova e rimuovi pagine vuote da un PDF.
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.compare.title=Compara
home.compare.desc=Vedi e compara le differenze tra due PDF.
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Scarica PDF
text=Testo
font=Font
selectFillter=-- Seleziona --
pageNum=Numero pagina
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Firma del certificato
certSign.header=Firma un PDF con il tuo certificato (Lavoro in corso)
certSign.selectPDF=Seleziona un file PDF per la firma:
@ -337,6 +359,9 @@ addPassword.selectText.10=Previeni modifiche
addPassword.selectText.11=Previeni annotazioni
addPassword.selectText.12=Previeni stampa
addPassword.selectText.13=Previeni stampa in diversi formati
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Crittografa
#watermark

View file

@ -128,6 +128,13 @@ home.compare.desc=2개의 PDF 문서를 비교하고 차이를 표시합니다.
home.certSign.title=인증서로 서명
home.certSign.desc=PDF에 인증서/키로 서명합니다. (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=PDF 다운로드
text=텍스트
@ -135,6 +142,17 @@ font=폰트
selectFillter=-- 선택 --
pageNum=페이지 번호
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=인증서로 서명
certSign.header=PDF에 당신의 인증서로 서명하세요 (개발 중)
certSign.selectPDF=서명할 PDF를 선택하세요:
@ -368,7 +386,10 @@ addPassword.selectText.9=양식 작성 방지
addPassword.selectText.10=수정 방지
addPassword.selectText.11=주석 수정 방지
addPassword.selectText.12=인쇄 방지
addPassword.selectText.13=다른 형식으로 인쇄 방지
addPassword.selectText.13=다른 형ì<C3AC>으로 ì<>¸ì‡„ ë°©ì§
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself€
addPassword.submit=암호화
#watermark

View file

@ -359,6 +359,9 @@ addPassword.selectText.10=Zablokuj modyfikacje
addPassword.selectText.11=Zablokuj modyfikacje adnotacji
addPassword.selectText.12=Zablokuj drukowanie
addPassword.selectText.13=Zablokuj drukowanie różnych formatów
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Zablokuj
#watermark

View file

@ -128,6 +128,13 @@ home.compare.desc=Compara e mostra as diferenças entre 2 documentos PDF
home.certSign.title=Assinar com certificado
home.certSign.desc=Assina um PDF com um Certificado/Chave (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=baixar PDF
text=Texto
@ -135,6 +142,17 @@ font=Fonte
selectFillter=-- Selecione --
pageNum=Número de página
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Assinatura de certificado
certSign.header=Assine um PDF com seu certificado (Trabalho em andamento)
certSign.selectPDF=Selecione um arquivo PDF para assinatura:
@ -345,6 +363,9 @@ addPassword.selectText.10=Impedir modificação
addPassword.selectText.11=Impedir a modificação da anotação
addPassword.selectText.12=Impedir a impressão
addPassword.selectText.13=Impedir a impressão de formatos diferentes
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=criptografar
#watermark

View file

@ -109,12 +109,31 @@ home.compare.desc=Compară și arată diferențele dintre 2 documente PDF.
home.certSign.title=Semnare cu certificat
home.certSign.desc=Semnează un PDF cu un certificat/cheie (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Descarcă PDF
text=Text
font=Font
selectFillter=-- Selectează --
pageNum=Numărul paginii
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title = Semnare certificat
certSign.header = Semnează un fișier PDF cu certificatul tău (În curs de desfășurare)
certSign.selectPDF = Selectează un fișier PDF pentru semnare:
@ -316,6 +335,9 @@ addPassword.selectText.10=Preveniți modificarea
addPassword.selectText.11=Preveniți modificarea adnotărilor
addPassword.selectText.12=Preveniți tipărirea
addPassword.selectText.13=Preveniți tipărirea în formate diferite
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Criptare
#watermark

View file

@ -125,12 +125,34 @@ home.removeBlanks.desc=Обнаруживает и удаляет пустые
home.compare.title=Сравнение
home.compare.desc=Сравнивает и показывает различия между двумя PDF-документами
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Скачать PDF
text=Текст
font=Шрифт
selectFillter=-- Выбрать --
pageNum=номер страницы
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Подписание сертификата
certSign.header=Подпишите PDF своим сертификатом (работа в процессе)
certSign.selectPDF=Выберите файл PDF для подписи:
@ -338,6 +360,9 @@ addPassword.selectText.10=Предотвратить модификацию
addPassword.selectText.11=Запретить модификацию аннотаций
addPassword.selectText.12=Запретить печать
addPassword.selectText.13=Запретить печать разных форматов
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Шифровать
#watermark

View file

@ -125,12 +125,34 @@ home.removeBlanks.desc=Känner av och tar bort tomma sidor från ett dokument
home.compare.title=Jämför
home.compare.desc=Jämför och visar skillnaderna mellan 2 PDF-dokument
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=Ladda ner PDF
text=Text
font=Teckensnitt
selectFillter=-- Välj --
pageNum=Sidnummer
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=Certifikatsignering
certSign.header=Skriv under en PDF med ditt certifikat (Pågående arbete)
certSign.selectPDF=Välj en PDF-fil för signering:
@ -338,6 +360,9 @@ addPassword.selectText.10=Förhindra modifiering
addPassword.selectText.11=Förhindra anteckningsändring
addPassword.selectText.12=Förhindra utskrift
addPassword.selectText.13=Förhindra utskrift av olika format
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself
addPassword.submit=Kryptera
#vattenstämpel

View file

@ -125,12 +125,34 @@ home.removeBlanks.desc=\u68C0\u6D4B\u5E76\u5220\u9664\u6587\u6863\u4E2D\u7684\u7
home.compare.title=\u6BD4\u8F83
home.compare.desc=\u6BD4\u8F83\u5E76\u663E\u793A 2 \u4E2A PDF \u6587\u6863\u4E4B\u95F4\u7684\u5DEE\u5F02
home.certSign.title=Sign with Certificate
home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
home.pageLayout.title=Multi-Page Layout
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
home.scalePages.title=Adjust page size/scale
home.scalePages.desc=Change the size/scale of page and/or its contents.
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
downloadPdf=\u4E0B\u8F7DPDF
text=\u6587\u672C
font=\u5B57\u4F53
selectFillter=-- 选择--
pageNum=页码
pageLayout.title=Multi Page Layout
pageLayout.header=Multi Page Layout
pageLayout.pagesPerSheet=Pages per sheet:
pageLayout.submit=Submit
scalePages.title=Adjust page-scale
scalePages.header=Adjust page-scale
scalePages.pageSize=Size of a page of the document.
scalePages.scaleFactor=Zoom level (crop) of a page.
scalePages.submit=Submit
certSign.title=证书签名
certSign.header=使用您的证书签署 PDF进行中
certSign.selectPDF=选择要签名的 PDF 文件:
@ -336,7 +358,10 @@ addPassword.selectText.9=防止填写表格
addPassword.selectText.10=防止修改
addPassword.selectText.11=防止修改注释
addPassword.selectText.12=防止打印
addPassword.selectText.13=防止打印不同的格式
addPassword.selectText.13=防止打å<EFBFBD>°ä¸<EFBFBD>å<EFBFBD>Œçš„æ ¼å¼
addPassword.selectText.14=Owner Password
addPassword.selectText.15=Restricts what can be done with the document once it is opened (Not supported by all readers)
addPassword.selectText.16=Restricts the opening of the document itself<6C>
addPassword.submit=加密
#watermark

View file

@ -5,73 +5,38 @@ function showErrorBanner(message, stackTrace) {
document.querySelector("#errorContainer p").textContent = message;
document.querySelector("#traceContent").textContent = stackTrace;
}
let firstErrorOccurred = false;
$(document).ready(function() {
$('form').submit(async function(event) {
event.preventDefault();
firstErrorOccurred = false;
const url = this.action;
const files = $('#fileInput-input')[0].files;
const formData = new FormData(this);
const override = $('#override').val() || '';
const originalButtonText = $('#submitBtn').text();
$('#submitBtn').text('Processing...');
try {
if (override === 'multi' || files.length > 1 && override !== 'single') {
// Show the progress bar
$('#progressBarContainer').show();
// Initialize the progress bar
//let progressBar = $('#progressBar');
//progressBar.css('width', '0%');
//progressBar.attr('aria-valuenow', 0);
//progressBar.attr('aria-valuemax', files.length);
await submitMultiPdfForm(url, files);
} else {
const downloadDetails = await handleSingleDownload(url, formData);
const downloadOption = localStorage.getItem('downloadOption');
// Handle the download action according to the selected option
//handleDownloadAction(downloadOption, downloadDetails.blob, downloadDetails.filename);
// Update the progress bar
//updateProgressBar(progressBar, 1);
await handleSingleDownload(url, formData);
}
$('#submitBtn').text('Submit');
$('#submitBtn').text(originalButtonText);
} catch (error) {
handleDownloadError(error);
$('#submitBtn').text('Submit');
$('#submitBtn').text(originalButtonText);
console.error(error);
}
});
});
function handleDownloadAction(downloadOption, blob, filename) {
const url = URL.createObjectURL(blob);
switch (downloadOption) {
case 'sameWindow':
// Open the file in the same window
window.location.href = url;
break;
case 'newWindow':
// Open the file in a new window
window.open(url, '_blank');
break;
default:
// Download the file
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
break;
}
}
async function handleSingleDownload(url, formData) {
async function handleSingleDownload(url, formData, isMulti = false) {
try {
const response = await fetch(url, { method: 'POST', body: formData });
const contentType = response.headers.get('content-type');
@ -90,7 +55,7 @@ async function handleSingleDownload(url, formData) {
const blob = await response.blob();
if (contentType.includes('application/pdf') || contentType.includes('image/')) {
return handleResponse(blob, filename, true);
return handleResponse(blob, filename, !isMulti);
} else {
return handleResponse(blob, filename);
}
@ -118,8 +83,11 @@ function getFilenameFromContentDisposition(contentDisposition) {
async function handleJsonResponse(response) {
const json = await response.json();
const errorMessage = JSON.stringify(json, null, 2);
if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided')) {
alert('[[#{error.pdfPassword}]]');
if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) {
if (!firstErrorOccurred) {
firstErrorOccurred = true;
alert(pdfPasswordPrompt);
}
} else {
showErrorBanner(json.error + ':' + json.message, json.trace);
}
@ -173,10 +141,15 @@ async function submitMultiPdfForm(url, files) {
const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
const zipFiles = files.length > zipThreshold;
let jszip = null;
//let progressBar = $('#progressBar');
//progressBar.css('width', '0%');
//progressBar.attr('aria-valuenow', 0);
//progressBar.attr('aria-valuemax', Array.from(files).length);
// Show the progress bar
$('#progressBarContainer').show();
// Initialize the progress bar
let progressBar = $('#progressBar');
progressBar.css('width', '0%');
progressBar.attr('aria-valuenow', 0);
progressBar.attr('aria-valuemax', files.length);
if (zipFiles) {
jszip = new JSZip();
}
@ -202,20 +175,21 @@ async function submitMultiPdfForm(url, files) {
}
try {
const downloadDetails = await handleSingleDownload(url, fileFormData);
const downloadDetails = await handleSingleDownload(url, fileFormData, true);
console.log(downloadDetails);
if (zipFiles) {
jszip.file(downloadDetails.filename, downloadDetails.blob);
} else {
downloadFile(downloadDetails.blob, downloadDetails.filename);
}
//updateProgressBar(progressBar, Array.from(files).length);
updateProgressBar(progressBar, Array.from(files).length);
} catch (error) {
handleDownloadError(error);
console.error(error);
}
});
await Promise.all(promises);
}
if (zipFiles) {
@ -226,6 +200,8 @@ async function submitMultiPdfForm(url, files) {
console.error('Error generating ZIP file: ' + error);
}
}
progressBar.css('width', '100%');
progressBar.attr('aria-valuenow', Array.from(files).length);
}

View file

@ -1,6 +1,5 @@
document.addEventListener('DOMContentLoaded', function() {
const fileInput = document.getElementById(elementID);
// Prevent default behavior for drag events
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
fileInput.addEventListener(eventName, preventDefaults, false);
@ -22,7 +21,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
$(elementID).on("change", function() {
$("#"+elementID).on("change", function() {
handleFileInputChange(this);
});
@ -34,7 +33,6 @@ function handleFileInputChange(inputElement) {
fileNames.forEach(fileName => {
selectedFilesContainer.append("<div>" + fileName + "</div>");
});
console.log("fileNames.length=" + fileNames.length)
if (fileNames.length === 1) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
} else if (fileNames.length > 1) {

View file

@ -91,6 +91,9 @@
</th:block>
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true', notRequired=${notRequired} ?: false">
<script th:inline="javascript">
const pdfPasswordPrompt =/*[[#{error.pdfPassword}]]*/ '';
</script>
<script src="js/downloader.js"></script>
<div class="custom-file-chooser">

View file

@ -0,0 +1,31 @@
<!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=#{autoCrop.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="#{autoCrop.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{auto-crop}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{autoCrop.submit}"></button>
</form>
<p class="mt-3" th:text="#{autoCrop.credit}"></p>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

View file

@ -31,7 +31,7 @@
</div>
<div class="form-group">
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
<input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>
<input type="text" class="form-control" id="pageOrder" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)" required>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfOrganiser.submit}"></button>