You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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.


Using OpenPdf (Fork of iText 4.x)

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; }
    }
}

Using Apache PdfBox

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-layout dependency 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

火山引擎 最新活动