Split pages support n function and other stuff
This commit is contained in:
parent
4594765cbd
commit
c526e18992
16 changed files with 2682 additions and 119 deletions
45
.github/workflows/swagger.yml
vendored
Normal file
45
.github/workflows/swagger.yml
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
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: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||||
|
|
||||||
|
- name: Commit and push if it changed
|
||||||
|
run: |
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
git add -A
|
||||||
|
git diff --quiet && git diff --staged --quiet || git commit -m "Updated Swagger documentation"
|
||||||
|
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||||
|
git push
|
||||||
|
|
||||||
|
- name: Upload Swagger Documentation to SwaggerHub
|
||||||
|
run: |
|
||||||
|
curl -X POST -H "Authorization: ${SWAGGERHUB_API_KEY}" -H "Content-Type: application/json" -d @SwaggerDoc.json "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}?isPrivate=false&force=true"
|
||||||
|
env:
|
||||||
|
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,6 +14,7 @@ local.properties
|
||||||
.recommenders
|
.recommenders
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
version.properties
|
||||||
|
|
||||||
# Gradle
|
# Gradle
|
||||||
.gradle
|
.gradle
|
||||||
|
|
2329
SwaggerDoc.json
Normal file
2329
SwaggerDoc.json
Normal file
File diff suppressed because it is too large
Load diff
19
build.gradle
19
build.gradle
|
@ -2,6 +2,7 @@ plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.1.0'
|
id 'org.springframework.boot' version '3.1.0'
|
||||||
id 'io.spring.dependency-management' version '1.1.0'
|
id 'io.spring.dependency-management' version '1.1.0'
|
||||||
|
id 'org.springdoc.openapi-gradle-plugin' version '1.6.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
|
@ -12,6 +13,12 @@ repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openApi {
|
||||||
|
apiDocsUrl = "http://localhost:8080/v3/api-docs"
|
||||||
|
outputDir = file("/")
|
||||||
|
outputFileName = "SwaggerDoc.json"
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
|
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.0'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.0'
|
||||||
|
@ -34,6 +41,18 @@ dependencies {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task writeVersion {
|
||||||
|
def propsFile = file('src/main/resources/version.properties')
|
||||||
|
def props = new Properties()
|
||||||
|
props.setProperty('version', version)
|
||||||
|
props.store(propsFile.newWriter(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.matching { it.name == 'generateOpenApiDocs' }.all {
|
||||||
|
dependsOn writeVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
enabled = false
|
enabled = false
|
||||||
manifest {
|
manifest {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package stirling.software.SPDF.config;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@ -10,13 +14,23 @@ import io.swagger.v3.oas.models.info.Info;
|
||||||
@Configuration
|
@Configuration
|
||||||
public class OpenApiConfig {
|
public class OpenApiConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
String version = getClass().getPackage().getImplementationVersion();
|
||||||
version = (version != null) ? version : "1.0.0";
|
if (version == null) {
|
||||||
|
Properties props = new Properties();
|
||||||
return new OpenAPI().components(new Components()).info(
|
try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) {
|
||||||
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."));
|
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."));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.script.ScriptEngineManager;
|
|
||||||
import javax.script.ScriptEngine;
|
|
||||||
import javax.script.ScriptException;
|
|
||||||
|
|
||||||
import javax.script.ScriptEngine;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
@ -25,6 +17,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
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
|
@RestController
|
||||||
public class RearrangePagesPDFController {
|
public class RearrangePagesPDFController {
|
||||||
|
@ -43,7 +38,7 @@ public class RearrangePagesPDFController {
|
||||||
// Split the page order string into an array of page numbers or range of numbers
|
// Split the page order string into an array of page numbers or range of numbers
|
||||||
String[] pageOrderArr = pagesToDelete.split(",");
|
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--) {
|
for (int i = pagesToRemove.size() - 1; i >= 0; i--) {
|
||||||
int pageIndex = pagesToRemove.get(i);
|
int pageIndex = pagesToRemove.get(i);
|
||||||
|
@ -180,7 +175,7 @@ public class RearrangePagesPDFController {
|
||||||
if (customMode != null && customMode.length() > 0) {
|
if (customMode != null && customMode.length() > 0) {
|
||||||
newPageOrder = processCustomMode(customMode, totalPages);
|
newPageOrder = processCustomMode(customMode, totalPages);
|
||||||
} else {
|
} else {
|
||||||
newPageOrder = pageOrderToString(pageOrderArr, totalPages);
|
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new list to hold the pages in the new order
|
// Create a new list to hold the pages in the new order
|
||||||
|
@ -207,63 +202,6 @@ public class RearrangePagesPDFController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,18 @@ package stirling.software.SPDF.controller.api;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
@ -20,14 +28,17 @@ import com.itextpdf.kernel.pdf.PdfPage;
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
import com.itextpdf.kernel.pdf.PdfReader;
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
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 com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
@RestController
|
@RestController
|
||||||
public class ScalePagesController {
|
public class ScalePagesController {
|
||||||
|
|
||||||
|
@ -119,4 +130,128 @@ public class ScalePagesController {
|
||||||
pdfDoc.close();
|
pdfDoc.close();
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
|
return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class SplitPDFController {
|
public class SplitPDFController {
|
||||||
|
@ -58,39 +58,28 @@ public class SplitPDFController {
|
||||||
pageNumbers.add(i);
|
pageNumbers.add(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<String> pageNumbersStr = new ArrayList<>(Arrays.asList(pages.split(",")));
|
String[] splitPoints = pages.split(",");
|
||||||
if (!pageNumbersStr.contains(String.valueOf(document.getNumberOfPages()))) {
|
for (String splitPoint : splitPoints) {
|
||||||
String lastpage = String.valueOf(document.getNumberOfPages());
|
List<Integer> orderedPages = GeneralUtils.parsePageList(new String[] {splitPoint}, document.getNumberOfPages());
|
||||||
pageNumbersStr.add(lastpage);
|
pageNumbers.addAll(orderedPages);
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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(",")));
|
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
|
||||||
// split the document
|
// split the document
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
int currentPage = 0;
|
int previousPageNumber = 0;
|
||||||
for (int pageNumber : pageNumbers) {
|
for (int splitPoint : pageNumbers) {
|
||||||
try (PDDocument splitDocument = new PDDocument()) {
|
try (PDDocument splitDocument = new PDDocument()) {
|
||||||
for (int i = currentPage; i < pageNumber; i++) {
|
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||||
PDPage page = document.getPage(i);
|
PDPage page = document.getPage(i);
|
||||||
splitDocument.addPage(page);
|
splitDocument.addPage(page);
|
||||||
logger.debug("Adding page {} to split document", i);
|
logger.debug("Adding page {} to split document", i);
|
||||||
}
|
}
|
||||||
currentPage = pageNumber;
|
previousPageNumber = splitPoint + 1;
|
||||||
logger.debug("Setting current page to {}", currentPage);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
splitDocument.save(baos);
|
splitDocument.save(baos);
|
||||||
|
@ -102,6 +91,7 @@ public class SplitPDFController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// closing the original document
|
// closing the original document
|
||||||
document.close();
|
document.close();
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
public class CertSignController {
|
public class CertSignController {
|
||||||
|
|
||||||
|
|
|
@ -122,4 +122,11 @@ public class OtherWebController {
|
||||||
return "other/scale-pages";
|
return "other/scale-pages";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/auto-crop")
|
||||||
|
@Hidden
|
||||||
|
public String autoCropForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "auto-crop");
|
||||||
|
return "other/auto-crop";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class GeneralUtils {
|
public class GeneralUtils {
|
||||||
|
|
||||||
public static Long convertSizeToBytes(String sizeStr) {
|
public static Long convertSizeToBytes(String sizeStr) {
|
||||||
|
@ -27,4 +30,62 @@ public class GeneralUtils {
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,6 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
|
@ -54,14 +54,11 @@ home.pdfOrganiser.title=Organise
|
||||||
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
home.pdfOrganiser.desc=Remove/Rearrange pages in any order
|
||||||
|
|
||||||
home.addImage.title=Add image
|
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.title=Add Watermark
|
||||||
home.watermark.desc=Add a custom watermark to your PDF document.
|
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.title=Change Permissions
|
||||||
home.permissions.desc=Change the permissions of your PDF document
|
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.title=Multi-Page Layout
|
||||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||||
|
|
||||||
home.scalePages.title=Adjust page-scale
|
home.scalePages.title=Adjust page size/scale
|
||||||
home.scalePages.desc=Change the size of page contents while maintaining a set page-size
|
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
|
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ $(document).ready(function() {
|
||||||
const files = $('#fileInput-input')[0].files;
|
const files = $('#fileInput-input')[0].files;
|
||||||
const formData = new FormData(this);
|
const formData = new FormData(this);
|
||||||
const override = $('#override').val() || '';
|
const override = $('#override').val() || '';
|
||||||
|
const originalButtonText = $('#submitBtn').text();
|
||||||
$('#submitBtn').text('Processing...');
|
$('#submitBtn').text('Processing...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -24,10 +24,10 @@ $(document).ready(function() {
|
||||||
await handleSingleDownload(url, formData);
|
await handleSingleDownload(url, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#submitBtn').text('Submit');
|
$('#submitBtn').text(originalButtonText);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleDownloadError(error);
|
handleDownloadError(error);
|
||||||
$('#submitBtn').text('Submit');
|
$('#submitBtn').text(originalButtonText);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -83,7 +83,7 @@ async function handleJsonResponse(response) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const errorMessage = JSON.stringify(json, null, 2);
|
const errorMessage = JSON.stringify(json, null, 2);
|
||||||
if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) {
|
if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided') || errorMessage.toLowerCase().includes('PDF contains an encryption dictionary')) {
|
||||||
alert('[[#{error.pdfPassword}]]');
|
alert(pdfPasswordPrompt);
|
||||||
} else {
|
} else {
|
||||||
showErrorBanner(json.error + ':' + json.message, json.trace);
|
showErrorBanner(json.error + ':' + json.message, json.trace);
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,9 @@
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true', notRequired=${notRequired} ?: false">
|
<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>
|
<script src="js/downloader.js"></script>
|
||||||
|
|
||||||
<div class="custom-file-chooser">
|
<div class="custom-file-chooser">
|
||||||
|
|
31
src/main/resources/templates/other/auto-crop.html
Normal file
31
src/main/resources/templates/other/auto-crop.html
Normal 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>
|
Loading…
Reference in a new issue