向内部 PDF 文件添加超链接

Adding hyperlink to inner PDF files

我必须通过在 Java 中使用 iText 在生成的 PDF 文件中添加两个 PDF 文件作为树结构来创建 PDF 文件。

我必须用 PDF 文件名创建书签并向书签添加超链接。单击书签时,应在该 PDF 文件本身中打开相应的 PDF,而不是作为单独的 PDF。

PDFTREE

 pdf1 
 pdf2 

此类书签在PDF规范中被称为大纲元素PDF 32000-1:2008p .367):

The outline consists of a tree-structured hierarchy of outline items (sometimes called bookmarks), which serve as a visual table of contents to display the document’s structure to the user.

如果您使用 PdfMerger 合并文档,默认情况下会将轮廓复制到生成的 PDF 中。但是,您希望每个文档有一个主节点,而不是一个简单的书签列表。由于克隆和复制大纲绝非易事,因此最好让 iText 处理它。不幸的是,我们几乎无法直接控制轮廓的合并方式。

我们可以构建一个 SpecialMerger 作为 PdfMerger 的包装器来提取克隆的轮廓(第一步),然后将它们放入层次结构中(第二步)。每个合并的 PDF 的大纲与主节点的所需名称及其引用(合并的 PDF 中的页码)一起临时存储在 outlineList 中。合并所有PDF后,我们可以将临时存储的轮廓附加回根节点。

public static class SpecialMerger {

    private final PdfDocument outputPdf;
    private final PdfMerger merger;
    private final PdfOutline rootOutline;
    private final List<DocumentOutline> outlineList = new ArrayList<>();
    private int nextPageNr = 1;

    public SpecialMerger(final PdfDocument outputPdf) {
        if (outputPdf.getNumberOfPages() != 0) {
            throw new IllegalArgumentException("PDF must be empty");
        }
        this.outputPdf = outputPdf;
        this.merger = new PdfMerger(outputPdf, true, true);
        this.rootOutline = outputPdf.getOutlines(false);
    }

    public void merge(PdfDocument from, int fromPage, int toPage, String filename) {
        merger.merge(from, fromPage, toPage); // merge with normal PdfMerger

        // extract and clone outline of merged document
        final List<PdfOutline> children = new ArrayList<>(rootOutline.getAllChildren());
        rootOutline.getAllChildren().clear(); // clear root outline
        outlineList.add(new DocumentOutline(filename, nextPageNr, children));
        nextPageNr = outputPdf.getNumberOfPages() + 1; // update next page number
    }

    public void writeOutline() {
        outlineList.forEach(o -> {
            final PdfOutline outline = rootOutline.addOutline(o.getName()); // bookmark with PDF name
            outline.addDestination(PdfExplicitDestination.createFit(outputPdf.getPage(o.getPageNr())));
            outline.setStyle(PdfOutline.FLAG_BOLD);
            o.getChildern().forEach(outline::addOutline); // add all extracted child bookmarks
        });
    }

    private static class DocumentOutline {

        private final String name;
        private final int pageNr;
        private final List<PdfOutline> childern;

        public DocumentOutline(final String pdfName, final int pageNr, final List<PdfOutline> childern) {
            this.name = pdfName;
            this.pageNr = pageNr;
            this.childern = childern;
        }

        public String getName() {
            return name;
        }

        public int getPageNr() {
            return pageNr;
        }

        public List<PdfOutline> getChildern() {
            return childern;
        }
    }
}

现在,我们可以使用这个自定义合并来合并 PDF,然后添加大纲 writeOutline:

public static void main(String[] args) throws IOException {
    String filename1 = "pdf1.pdf";
    String filename2 = "pdf2.pdf";
    try (
            PdfDocument generatedPdf = new PdfDocument(new PdfWriter("output.pdf"));
            PdfDocument pdfDocument1 = new PdfDocument(new PdfReader(filename1));
            PdfDocument pdfDocument2 = new PdfDocument(new PdfReader(filename2))
    ) {
        final SpecialMerger merger = new SpecialMerger(generatedPdf);
        merger.merge(pdfDocument1, 1, pdfDocument1.getNumberOfPages(), filename1);
        merger.merge(pdfDocument2, 1, pdfDocument2.getNumberOfPages(), filename2);
        merger.writeOutline();
    }
}

结果如下所示(PreviewAdobe Acrobat Reader 在 macOS 上):


另一种选择是通过嵌入 PDF 来制作作品集。但是,并非所有 PDF 查看器都支持此功能,并且大多数用户不习惯这些组合。

public static void main(String[] args) throws IOException {
    String filename1 = "pdf1.pdf";
    String filename2 = "pdf2.pdf";

    try (PdfDocument generatedPdf = new PdfDocument(new PdfWriter("portfolio.pdf"))) {
        Document doc = new Document(generatedPdf);
        doc.add(new Paragraph("This PDF contains embedded documents."));
        doc.add(new Paragraph("Use a compatible PDF viewer if you cannot see them."));

        PdfCollection collection = new PdfCollection();
        collection.setView(PdfCollection.TILE);
        generatedPdf.getCatalog().setCollection(collection);

        addAttachment(generatedPdf, filename1, filename1);
        addAttachment(generatedPdf, filename2, filename2);
    }
}

private static void addAttachment(PdfDocument doc, String attachmentPath, String name) throws IOException {
    PdfFileSpec fileSpec = PdfFileSpec.createEmbeddedFileSpec(doc, attachmentPath, name, name, null, null);
    doc.addFileAttachment(name, fileSpec);
}

macOS 上 Adob​​e Acrobat Reader 中的结果: