Merge to generate ToC

This commit is contained in:
Anthony Stirling 2025-05-14 13:16:37 +01:00
parent 704b8bb0c6
commit cab1fa9297
4 changed files with 69 additions and 0 deletions

View File

@ -15,6 +15,8 @@ import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
@ -124,6 +126,7 @@ public class MergeController {
PDDocument mergedDocument = null; PDDocument mergedDocument = null;
boolean removeCertSign = form.isRemoveCertSign(); boolean removeCertSign = form.isRemoveCertSign();
boolean generateToc = form.isGenerateToc();
try { try {
MultipartFile[] files = form.getFileInput(); MultipartFile[] files = form.getFileInput();
@ -170,6 +173,11 @@ public class MergeController {
} }
} }
// Generate table of contents if requested
if (generateToc) {
generateTableOfContents(mergedDocument, files);
}
// Save the modified document to a new ByteArrayOutputStream // Save the modified document to a new ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
mergedDocument.save(baos); mergedDocument.save(baos);
@ -197,4 +205,54 @@ public class MergeController {
} }
} }
} }
/**
* Generates a table of contents for the merged PDF document using file names as chapter titles
*
* @param mergedDocument The merged PDF document
* @param files The original input files that were merged
*/
private void generateTableOfContents(PDDocument mergedDocument, MultipartFile[] files) {
PDDocumentCatalog catalog = mergedDocument.getDocumentCatalog();
PDDocumentOutline outline = new PDDocumentOutline();
catalog.setDocumentOutline(outline);
int currentPageIndex = 0;
for (MultipartFile file : files) {
try {
// Create a temporary file to load document and get page count
File tempFile = GeneralUtils.convertMultipartFileToFile(file);
PDDocument doc = pdfDocumentFactory.load(tempFile);
try {
// Create an outline item for this file
String fileName = file.getOriginalFilename();
if (fileName != null && fileName.toLowerCase().endsWith(".pdf")) {
fileName = fileName.substring(0, fileName.length() - 4);
}
PDOutlineItem bookmark = new PDOutlineItem();
bookmark.setTitle(fileName);
// Set destination to the first page of this file in the merged document
PDPage page = mergedDocument.getPage(currentPageIndex);
bookmark.setDestination(page);
// Add the bookmark to the outline
outline.addLast(bookmark);
// Update current page index for next file
currentPageIndex += doc.getNumberOfPages();
} finally {
doc.close();
Files.deleteIfExists(tempFile.toPath());
}
} catch (IOException e) {
log.error("Error creating bookmark for file: " + file.getOriginalFilename(), e);
// Continue with next file even if one fails
continue;
}
}
}
} }

View File

@ -28,4 +28,10 @@ public class MergePdfsRequest extends MultiplePDFFiles {
"Flag indicating whether to remove certification signatures from the merged PDF. If true, all certification signatures will be removed from the final merged document.", "Flag indicating whether to remove certification signatures from the merged PDF. If true, all certification signatures will be removed from the final merged document.",
example = "true") example = "true")
private boolean isRemoveCertSign; private boolean isRemoveCertSign;
@Schema(
description =
"Flag indicating whether to generate a table of contents for the merged PDF. If true, a table of contents will be created using the input filenames as chapter names.",
example = "true")
private boolean generateToc = false;
} }

View File

@ -1013,6 +1013,7 @@ merge.header=Merge multiple PDFs (2+)
merge.sortByName=Sort by name merge.sortByName=Sort by name
merge.sortByDate=Sort by date merge.sortByDate=Sort by date
merge.removeCertSign=Remove digital signature in the merged file? merge.removeCertSign=Remove digital signature in the merged file?
merge.generateToc=Generate table of contents in the merged file?
merge.submit=Merge merge.submit=Merge

View File

@ -32,6 +32,10 @@
<label for="removeCertSign" th:text="#{merge.removeCertSign}">Remove digital signature in the merged <label for="removeCertSign" th:text="#{merge.removeCertSign}">Remove digital signature in the merged
file?</label> file?</label>
</div> </div>
<div class="mb-3">
<input type="checkbox" name="generateToc" id="generateToc">
<label for="generateToc" th:text="#{merge.generateToc}">Generate table of contents in the merged file?</label>
</div>
<div class="mb-3"> <div class="mb-3">
<ul id="selectedFiles" class="list-group"></ul> <ul id="selectedFiles" class="list-group"></ul>
</div> </div>