mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2024-11-16 12:20:12 +01:00
merge stuff #318
This commit is contained in:
parent
53e7dbe12f
commit
b666aa3f26
@ -1,7 +1,13 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -11,10 +17,11 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
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.RequestPart;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
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.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@ -26,55 +33,93 @@ public class MergeController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||||
|
|
||||||
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
|
||||||
// Create a new empty document
|
|
||||||
PDDocument mergedDoc = new PDDocument();
|
|
||||||
|
|
||||||
// Iterate over the list of documents and add their pages to the merged document
|
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
||||||
for (PDDocument doc : documents) {
|
PDDocument mergedDoc = new PDDocument();
|
||||||
// Get all pages from the current document
|
for (PDDocument doc : documents) {
|
||||||
PDPageTree pages = doc.getPages();
|
for (PDPage page : doc.getPages()) {
|
||||||
// Iterate over the pages and add them to the merged document
|
mergedDoc.addPage(page);
|
||||||
for (PDPage page : pages) {
|
|
||||||
mergedDoc.addPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return mergedDoc;
|
||||||
|
}
|
||||||
|
|
||||||
// Return the merged document
|
private Comparator<MultipartFile> getSortComparator(String sortType) {
|
||||||
return mergedDoc;
|
switch (sortType) {
|
||||||
|
case "byFileName":
|
||||||
|
return Comparator.comparing(MultipartFile::getOriginalFilename);
|
||||||
|
case "byDateModified":
|
||||||
|
return (file1, file2) -> {
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0; // If there's an error, treat them as equal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case "byDateCreated":
|
||||||
|
return (file1, file2) -> {
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
return attr1.creationTime().compareTo(attr2.creationTime());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0; // If there's an error, treat them as equal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case "byPDFTitle":
|
||||||
|
return (file1, file2) -> {
|
||||||
|
try (PDDocument doc1 = PDDocument.load(file1.getInputStream());
|
||||||
|
PDDocument doc2 = PDDocument.load(file2.getInputStream())) {
|
||||||
|
String title1 = doc1.getDocumentInformation().getTitle();
|
||||||
|
String title2 = doc2.getDocumentInformation().getTitle();
|
||||||
|
return title1.compareTo(title2);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case "orderProvided":
|
||||||
|
default:
|
||||||
|
return (file1, file2) -> 0; // Default is the order provided
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
||||||
|
@Operation(summary = "Merge multiple PDF files into one",
|
||||||
|
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO")
|
||||||
|
public ResponseEntity<byte[]> mergePdfs(
|
||||||
|
@RequestPart(required = true, value = "fileInput") MultipartFile[] files,
|
||||||
|
@RequestParam(value = "sortType", defaultValue = "orderProvided")
|
||||||
|
@Parameter(schema = @Schema(description = "The type of sorting to be applied on the input files before merging.",
|
||||||
|
allowableValues = {
|
||||||
|
"orderProvided",
|
||||||
|
"byFileName",
|
||||||
|
"byDateModified",
|
||||||
|
"byDateCreated",
|
||||||
|
"byPDFTitle"
|
||||||
|
}))
|
||||||
|
String sortType) throws IOException {
|
||||||
|
|
||||||
|
Arrays.sort(files, getSortComparator(sortType));
|
||||||
|
|
||||||
|
List<PDDocument> documents = new ArrayList<>();
|
||||||
|
for (MultipartFile file : files) {
|
||||||
|
try (InputStream is = file.getInputStream()) {
|
||||||
|
documents.add(PDDocument.load(is));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
try (PDDocument mergedDoc = mergeDocuments(documents)) {
|
||||||
@Operation(
|
|
||||||
summary = "Merge multiple PDF files into one",
|
|
||||||
description = "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO"
|
|
||||||
)
|
|
||||||
public ResponseEntity<byte[]> mergePdfs(
|
|
||||||
@RequestPart(required = true, value = "fileInput")
|
|
||||||
@Parameter(description = "The input PDF files to be merged into a single file", required = true)
|
|
||||||
MultipartFile[] files) throws IOException {
|
|
||||||
// Read the input PDF files into PDDocument objects
|
|
||||||
List<PDDocument> documents = new ArrayList<>();
|
|
||||||
|
|
||||||
// Loop through the files array and read each file into a PDDocument
|
|
||||||
for (MultipartFile file : files) {
|
|
||||||
documents.add(PDDocument.load(file.getInputStream()));
|
|
||||||
}
|
|
||||||
|
|
||||||
PDDocument mergedDoc = mergeDocuments(documents);
|
|
||||||
|
|
||||||
|
|
||||||
// Return the merged PDF as a response
|
|
||||||
ResponseEntity<byte[]> response = WebResponseUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
ResponseEntity<byte[]> response = WebResponseUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
||||||
|
|
||||||
for (PDDocument doc : documents) {
|
|
||||||
// Close the document after processing
|
|
||||||
doc.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
} finally {
|
||||||
|
for (PDDocument doc : documents) {
|
||||||
|
if (doc != null) {
|
||||||
|
doc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,63 +1,113 @@
|
|||||||
|
let currentSort = {
|
||||||
|
field: null,
|
||||||
|
descending: false
|
||||||
|
};
|
||||||
|
|
||||||
document.getElementById("fileInput-input").addEventListener("change", function() {
|
document.getElementById("fileInput-input").addEventListener("change", function() {
|
||||||
var files = this.files;
|
var files = this.files;
|
||||||
var list = document.getElementById("selectedFiles");
|
displayFiles(files);
|
||||||
list.innerHTML = "";
|
});
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
var item = document.createElement("li");
|
|
||||||
item.className = "list-group-item";
|
|
||||||
item.innerHTML = `
|
|
||||||
<div class="d-flex justify-content-between align-items-center w-100">
|
|
||||||
<div class="filename">${files[i].name}</div>
|
|
||||||
<div class="arrows d-flex">
|
|
||||||
<button class="btn btn-secondary move-up"><span>↑</span></button>
|
|
||||||
<button class="btn btn-secondary move-down"><span>↓</span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
list.appendChild(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
var moveUpButtons = document.querySelectorAll(".move-up");
|
function displayFiles(files) {
|
||||||
for (var i = 0; i < moveUpButtons.length; i++) {
|
var list = document.getElementById("selectedFiles");
|
||||||
moveUpButtons[i].addEventListener("click", function(event) {
|
list.innerHTML = "";
|
||||||
event.preventDefault();
|
|
||||||
var parent = this.closest(".list-group-item");
|
|
||||||
var grandParent = parent.parentNode;
|
|
||||||
if (parent.previousElementSibling) {
|
|
||||||
grandParent.insertBefore(parent, parent.previousElementSibling);
|
|
||||||
updateFiles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var moveDownButtons = document.querySelectorAll(".move-down");
|
for (var i = 0; i < files.length; i++) {
|
||||||
for (var i = 0; i < moveDownButtons.length; i++) {
|
var item = document.createElement("li");
|
||||||
moveDownButtons[i].addEventListener("click", function(event) {
|
item.className = "list-group-item";
|
||||||
event.preventDefault();
|
item.innerHTML = `
|
||||||
var parent = this.closest(".list-group-item");
|
<div class="d-flex justify-content-between align-items-center w-100">
|
||||||
var grandParent = parent.parentNode;
|
<div class="filename">${files[i].name}</div>
|
||||||
if (parent.nextElementSibling) {
|
<div class="arrows d-flex">
|
||||||
grandParent.insertBefore(parent.nextElementSibling, parent);
|
<button class="btn btn-secondary move-up"><span>↑</span></button>
|
||||||
updateFiles();
|
<button class="btn btn-secondary move-down"><span>↓</span></button>
|
||||||
}
|
</div>
|
||||||
});
|
</div>
|
||||||
}
|
`;
|
||||||
|
list.appendChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
function updateFiles() {
|
attachMoveButtons();
|
||||||
var dataTransfer = new DataTransfer();
|
}
|
||||||
var liElements = document.querySelectorAll("#selectedFiles li");
|
|
||||||
|
|
||||||
for (var i = 0; i < liElements.length; i++) {
|
function attachMoveButtons() {
|
||||||
var fileNameFromList = liElements[i].querySelector(".filename").innerText;
|
var moveUpButtons = document.querySelectorAll(".move-up");
|
||||||
var fileFromFiles;
|
for (var i = 0; i < moveUpButtons.length; i++) {
|
||||||
for (var j = 0; j < files.length; j++) {
|
moveUpButtons[i].addEventListener("click", function(event) {
|
||||||
var file = files[j];
|
event.preventDefault();
|
||||||
if (file.name === fileNameFromList) {
|
var parent = this.closest(".list-group-item");
|
||||||
dataTransfer.items.add(file);
|
var grandParent = parent.parentNode;
|
||||||
break;
|
if (parent.previousElementSibling) {
|
||||||
}
|
grandParent.insertBefore(parent, parent.previousElementSibling);
|
||||||
}
|
updateFiles();
|
||||||
}
|
}
|
||||||
document.getElementById("fileInput-input").files = dataTransfer.files;
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
var moveDownButtons = document.querySelectorAll(".move-down");
|
||||||
|
for (var i = 0; i < moveDownButtons.length; i++) {
|
||||||
|
moveDownButtons[i].addEventListener("click", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var parent = this.closest(".list-group-item");
|
||||||
|
var grandParent = parent.parentNode;
|
||||||
|
if (parent.nextElementSibling) {
|
||||||
|
grandParent.insertBefore(parent.nextElementSibling, parent);
|
||||||
|
updateFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("sortByNameBtn").addEventListener("click", function() {
|
||||||
|
if (currentSort.field === "name" && !currentSort.descending) {
|
||||||
|
currentSort.descending = true;
|
||||||
|
sortFiles((a, b) => b.name.localeCompare(a.name));
|
||||||
|
} else {
|
||||||
|
currentSort.field = "name";
|
||||||
|
currentSort.descending = false;
|
||||||
|
sortFiles((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("sortByDateBtn").addEventListener("click", function() {
|
||||||
|
if (currentSort.field === "lastModified" && !currentSort.descending) {
|
||||||
|
currentSort.descending = true;
|
||||||
|
sortFiles((a, b) => b.lastModified - a.lastModified);
|
||||||
|
} else {
|
||||||
|
currentSort.field = "lastModified";
|
||||||
|
currentSort.descending = false;
|
||||||
|
sortFiles((a, b) => a.lastModified - b.lastModified);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function sortFiles(comparator) {
|
||||||
|
// Convert FileList to array and sort
|
||||||
|
const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator);
|
||||||
|
|
||||||
|
// Refresh displayed list
|
||||||
|
displayFiles(sortedFilesArray);
|
||||||
|
|
||||||
|
// Update the files property
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
sortedFilesArray.forEach(file => dataTransfer.items.add(file));
|
||||||
|
document.getElementById("fileInput-input").files = dataTransfer.files;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFiles() {
|
||||||
|
var dataTransfer = new DataTransfer();
|
||||||
|
var liElements = document.querySelectorAll("#selectedFiles li");
|
||||||
|
const files = document.getElementById("fileInput-input").files;
|
||||||
|
|
||||||
|
for (var i = 0; i < liElements.length; i++) {
|
||||||
|
var fileNameFromList = liElements[i].querySelector(".filename").innerText;
|
||||||
|
var fileFromFiles;
|
||||||
|
for (var j = 0; j < files.length; j++) {
|
||||||
|
var file = files[j];
|
||||||
|
if (file.name === fileNameFromList) {
|
||||||
|
dataTransfer.items.add(file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("fileInput-input").files = dataTransfer.files;
|
||||||
|
}
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
<ul id="selectedFiles" class="list-group"></ul>
|
<ul id="selectedFiles" class="list-group"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-center">
|
<div class="form-group text-center">
|
||||||
|
<button type="button" id="sortByNameBtn" class="btn btn-info" th:text="#{merge.sortByName}"></button>
|
||||||
|
<button type="button" id="sortByDateBtn" class="btn btn-info" th:text="#{merge.sortByDate}"></button>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{merge.submit}"></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user