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.PDDocumentCatalog;
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.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
@ -124,6 +126,7 @@ public class MergeController {
PDDocument mergedDocument = null;
boolean removeCertSign = form.isRemoveCertSign();
boolean generateToc = form.isGenerateToc();
try {
MultipartFile[] files = form.getFileInput();
@ -169,6 +172,11 @@ public class MergeController {
}
}
}
// Generate table of contents if requested
if (generateToc) {
generateTableOfContents(mergedDocument, files);
}
// Save the modified document to a new ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@ -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.",
example = "true")
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.sortByDate=Sort by date
merge.removeCertSign=Remove digital signature in the merged file?
merge.generateToc=Generate table of contents in the merged file?
merge.submit=Merge

View File

@ -32,6 +32,10 @@
<label for="removeCertSign" th:text="#{merge.removeCertSign}">Remove digital signature in the merged
file?</label>
</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">
<ul id="selectedFiles" class="list-group"></ul>
</div>