Java/SpringBoot项目中利用OpenPdf/PdfBox自动生成PDF目录的技术咨询
Hey David, great question! Let's break down how you can generate a PDF with an auto-generated table of contents (TOC), plus paragraphs, images, and tables, using OpenPdf or Apache PdfBox—both free, open-source tools that work seamlessly in your Spring Boot project.
OpenPdf is a maintained open-source fork of the older (and free) iText 4.x codebase, so it shares many familiar APIs with iText but stays license-compliant for commercial use. Generating a TOC here involves two main steps: tracking your content sections as you add them, then backfilling the TOC with clickable links to those sections.
Step 1: Add Dependency
First, include the OpenPdf dependency in your pom.xml:
<dependency> <groupId>com.github.librepdf</groupId> <artifactId>openpdf</artifactId> <version>1.3.30</version> <!-- Use the latest stable version --> </dependency>
Step 2: Implement TOC Generation
Here's a simplified example that creates a TOC, then adds sections with paragraphs, an image, and a table—with clickable TOC links:
import com.lowagie.text.*; import com.lowagie.text.pdf.*; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; public class OpenPdfTocGenerator { public static void main(String[] args) throws Exception { Document document = new Document(PageSize.A4); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("document_with_toc.pdf")); document.open(); // 1. Add a placeholder for the TOC (we'll fill this later) Paragraph tocTitle = new Paragraph("Table of Contents", FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16)); document.add(tocTitle); document.add(Chunk.NEWLINE); List<SectionEntry> tocEntries = new ArrayList<>(); // 2. Add your content sections, tracking each entry // Section 1: Paragraphs Paragraph section1Title = new Paragraph("1. Introduction", FontFactory.getFont(FontFactory.HELVETICA_BOLD, 14)); PdfAction section1Action = PdfAction.gotoLocalPage(writer.getPageNumber(), new PdfDestination(PdfDestination.FIT), writer); section1Title.setAction(section1Action); document.add(section1Title); tocEntries.add(new SectionEntry("1. Introduction", writer.getPageNumber())); document.add(new Paragraph("This is a sample paragraph for the introduction section...")); document.add(Chunk.NEWLINE); // Section 2: Image Paragraph section2Title = new Paragraph("2. Sample Image", FontFactory.getFont(FontFactory.HELVETICA_BOLD, 14)); PdfAction section2Action = PdfAction.gotoLocalPage(writer.getPageNumber(), new PdfDestination(PdfDestination.FIT), writer); section2Title.setAction(section2Action); document.add(section2Title); tocEntries.add(new SectionEntry("2. Sample Image", writer.getPageNumber())); Image image = Image.getInstance("path/to/your/image.jpg"); image.scaleToFit(PageSize.A4.getWidth() - 40, PageSize.A4.getHeight() - 100); document.add(image); document.add(Chunk.NEWLINE); // Section 3: Table Paragraph section3Title = new Paragraph("3. Sample Table", FontFactory.getFont(FontFactory.HELVETICA_BOLD, 14)); PdfAction section3Action = PdfAction.gotoLocalPage(writer.getPageNumber(), new PdfDestination(PdfDestination.FIT), writer); section3Title.setAction(section3Action); document.add(section3Title); tocEntries.add(new SectionEntry("3. Sample Table", writer.getPageNumber())); PdfPTable table = new PdfPTable(3); table.addCell("Column 1"); table.addCell("Column 2"); table.addCell("Column 3"); table.addCell("Data 1"); table.addCell("Data 2"); table.addCell("Data 3"); document.add(table); // 3. Go back to the TOC placeholder and fill it with clickable entries document.newPage(); document.add(tocTitle); document.add(Chunk.NEWLINE); for (SectionEntry entry : tocEntries) { Paragraph tocEntry = new Paragraph(entry.getTitle()); PdfAction gotoAction = PdfAction.gotoLocalPage(entry.getPageNumber(), new PdfDestination(PdfDestination.FIT), writer); tocEntry.setAction(gotoAction); document.add(tocEntry); document.add(Chunk.NEWLINE); } document.close(); writer.close(); } // Helper class to track TOC entries private static class SectionEntry { private final String title; private final int pageNumber; public SectionEntry(String title, int pageNumber) { this.title = title; this.pageNumber = pageNumber; } public String getTitle() { return title; } public int getPageNumber() { return pageNumber; } } }
Apache PdfBox is another popular open-source PDF library, but it doesn't have built-in TOC generation—you'll need to manually create a document outline (the clickable TOC in PDF viewers) and track page positions as you add content.
Step 1: Add Dependency
Include PdfBox in your pom.xml:
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.32</version> <!-- Use latest stable version --> </dependency> <!-- Optional: For better table support, add pdfbox-layout --> <dependency> <groupId>com.github.vandeseer</groupId> <artifactId>pdfbox-layout</artifactId> <version>1.0.0</version> </dependency>
Step 2: Implement TOC (Outline) Generation
Here's how to create a clickable outline (TOC) and add content:
import org.apache.pdfbox.pdmodel.*; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo; import org.apache.pdfbox.pdmodel.interactive.destination.PDPageFitDestination; import java.io.File; import java.io.IOException; public class PdfBoxTocGenerator { public static void main(String[] args) throws IOException { PDDocument document = new PDDocument(); PDPageContentStream contentStream; // Create outline root (this is the TOC in the PDF viewer) PDOutlineNode rootOutline = document.getDocumentCatalog().getDocumentOutline(); if (rootOutline == null) { rootOutline = new PDOutlineNode(); document.getDocumentCatalog().setDocumentOutline(rootOutline); } // 1. Add Section 1: Paragraphs PDPage section1Page = new PDPage(PDRectangle.A4); document.addPage(section1Page); contentStream = new PDPageContentStream(document, section1Page); // Add section title contentStream.beginText(); contentStream.setFont(PDType1Font.HELVETICA_BOLD, 14); contentStream.newLineAtOffset(50, 750); contentStream.showText("1. Introduction"); contentStream.endText(); // Add paragraph contentStream.beginText(); contentStream.setFont(PDType1Font.HELVETICA, 12); contentStream.newLineAtOffset(50, 730); contentStream.showText("This is a sample paragraph for the introduction section..."); contentStream.endText(); contentStream.close(); // Add outline entry for Section 1 PDOutlineItem section1Outline = new PDOutlineItem(); section1Outline.setTitle("1. Introduction"); PDPageFitDestination dest1 = new PDPageFitDestination(); dest1.setPage(section1Page); PDActionGoTo action1 = new PDActionGoTo(); action1.setDestination(dest1); section1Outline.setAction(action1); rootOutline.addLast(section1Outline); // 2. Add Section 2: Image PDPage section2Page = new PDPage(PDRectangle.A4); document.addPage(section2Page); contentStream = new PDPageContentStream(document, section2Page); contentStream.beginText(); contentStream.setFont(PDType1Font.HELVETICA_BOLD, 14); contentStream.newLineAtOffset(50, 750); contentStream.showText("2. Sample Image"); contentStream.endText(); // Add image PDImageXObject image = PDImageXObject.createFromFile("path/to/your/image.jpg", document); float scale = Math.min((PDRectangle.A4.getWidth() - 100) / image.getWidth(), (PDRectangle.A4.getHeight() - 150) / image.getHeight()); contentStream.drawImage(image, 50, 500, image.getWidth() * scale, image.getHeight() * scale); contentStream.close(); // Add outline entry for Section 2 PDOutlineItem section2Outline = new PDOutlineItem(); section2Outline.setTitle("2. Sample Image"); PDPageFitDestination dest2 = new PDPageFitDestination(); dest2.setPage(section2Page); PDActionGoTo action2 = new PDActionGoTo(); action2.setDestination(dest2); section2Outline.setAction(action2); rootOutline.addLast(section2Outline); // 3. Add Section 3: Table (using pdfbox-layout for simplicity) PDPage section3Page = new PDPage(PDRectangle.A4); document.addPage(section3Page); contentStream = new PDPageContentStream(document, section3Page); contentStream.beginText(); contentStream.setFont(PDType1Font.HELVETICA_BOLD, 14); contentStream.newLineAtOffset(50, 750); contentStream.showText("3. Sample Table"); contentStream.endText(); contentStream.close(); // Use pdfbox-layout to create a table com.github.vandeseer.easytable.Table table = com.github.vandeseer.easytable.Table.builder() .addColumnsOfWidth(150, 150, 150) .addRow( com.github.vandeseer.easytable.Row.builder() .addHeaderCell("Column 1") .addHeaderCell("Column 2") .addHeaderCell("Column 3") .build() ) .addRow( com.github.vandeseer.easytable.Row.builder() .addCell("Data 1") .addCell("Data 2") .addCell("Data 3") .build() ) .build(); new com.github.vandeseer.easytable.TableDrawer() .contentStream(new PDPageContentStream(document, section3Page, PDPageContentStream.AppendMode.APPEND, true, true)) .table(table) .startX(50) .startY(720) .draw(); // Add outline entry for Section 3 PDOutlineItem section3Outline = new PDOutlineItem(); section3Outline.setTitle("3. Sample Table"); PDPageFitDestination dest3 = new PDPageFitDestination(); dest3.setPage(section3Page); PDActionGoTo action3 = new PDActionGoTo(); action3.setDestination(dest3); section3Outline.setAction(action3); rootOutline.addLast(section3Outline); // Save the document document.save("pdfbox_document_with_toc.pdf"); document.close(); } }
Key Notes
- OpenPdf is more similar to iText in syntax, making it easier if you're familiar with iText's older APIs. It handles text flow and links more out-of-the-box.
- PdfBox gives you lower-level control over the PDF structure, but requires more manual work for TOC and tables (hence the optional
pdfbox-layoutdependency for easier table creation). - Both libraries support dynamic content—you just need to track page numbers/positions as you add elements, especially if your content spans multiple pages.
内容的提问来源于stack exchange,提问作者David ROSEY




