【发布时间】:2021-09-12 09:13:54
【问题描述】:
我必须通过使用 Java 中的 iText 在生成的 PDF 文件中添加两个 PDF 文件作为树结构来创建 PDF 文件。
我必须用 PDF 文件名创建书签并添加一个超链接到书签。单击书签时,应在该 PDF 文件本身中打开相应的 PDF,而不是作为单独的 PDF。
PDF树
pdf1
pdf2
【问题讨论】:
标签: java pdf itext pdf-generation bookmarks
我必须通过使用 Java 中的 iText 在生成的 PDF 文件中添加两个 PDF 文件作为树结构来创建 PDF 文件。
我必须用 PDF 文件名创建书签并添加一个超链接到书签。单击书签时,应在该 PDF 文件本身中打开相应的 PDF,而不是作为单独的 PDF。
PDF树
pdf1
pdf2
【问题讨论】:
标签: java pdf itext pdf-generation bookmarks
此类书签在 PDF 规范中称为 大纲元素(PDF 32000-1:2008,p.367):
大纲由大纲项(有时称为书签)的树形结构层次结构组成,用作可视目录,将文档的结构显示给用户。
如果您将文档与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();
}
}
结果如下所示(Preview 和 Adobe 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 上的 Adobe Acrobat Reader 中的结果:
【讨论】: