1
0
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:
Anthony Stirling 2023-08-17 22:03:36 +01:00
parent 53e7dbe12f
commit b666aa3f26
3 changed files with 198 additions and 101 deletions

View File

@ -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();
}
}
} }
}
} }

View File

@ -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>&uarr;</span></button>
<button class="btn btn-secondary move-down"><span>&darr;</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>&uarr;</span></button>
updateFiles(); <button class="btn btn-secondary move-down"><span>&darr;</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;
}

View File

@ -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>