java itext2到itext7:合并PDF文件

wn9m85ua  于 2023-10-24  发布在  Java
关注(0)|答案(2)|浏览(370)

几年前,我用itext2写了一个小应用程序,用于每周收集报告并将其连接到一个PDF中。该应用程序使用com.lowagie.text.pdf.PdfCopy复制和合并PDF。它运行良好。完全符合预期。
几周前,我考虑将应用程序迁移到itex7。为此,我使用了com.itextpdf.kernel.pdf.PdfDocumentcopyPagesTo方法。当在同一个文件集上运行时,这会产生如下警告:

WARN PdfNameTree - Name "section.1" already exists in the name tree; old value will be replaced by the new one.

当我点击合并PDF的第一个文档中的“section.1”链接时,我被带到最后一个文档的“section.1”。不是我所期望的,也不是使用itext2应用程序时发生的情况。在itext2生成的PDF中,如果我点击合并PDF中第一个文档的“section.1”链接,我被带到第一份文件的第一节。
copyPagesTo的Javadocs中有一个提示:
如果不同文档中的大纲目标名称相同,则所有此类大纲都将指向结果文档中的一个位置。在这种情况下,iText将记录警告。可以通过重命名源文档中的目标名称来避免这种情况。
我觉得很奇怪的是,这在itext7中是必要的,尽管在itext2中没有。
有什么简单的办法可以解决他的问题吗?
我也尝试了Sejda桌面应用程序,它产生了正确的结果,但我更喜欢通过批处理脚本自动化这个过程。

sqyvllje

sqyvllje1#

我猜iText 2甚至不知道这可能是一个问题。
如果iText无法消除重复的目标名称,步骤大致如下:
按照每个文档中的/Catalog -〉/Names -〉/Dests查找目标名称树。
通过添加后缀来删除重复的名称。请记住,添加了后缀的名称可能与同一文档或另一文档中的现有名称相同。请小心!
现在你可以重写目标名称树了。因为你只使用了后缀,所以你可以在适当的地方这样做--名称的字典顺序没有改变,所以搜索树结构没有被破坏。
现在,在每个PDF中为新名称重写目标链接。例如,任何带有键/Dest的字典条目,或/后藤操作中的任何/D。
现在,在所有这些预处理之后,文件将合并而不会出现名称冲突。
(我知道这一切,因为我刚刚在自己的PDF软件中实现了它。这有点麻烦,但并不棘手。)
如果您愿意,我可以提供一个具有此功能的cpdf的开发版本,如果您想测试它的话。

1zmg4dgp

1zmg4dgp2#

根据@johnwhitington的回复,我得到了以下程序。
正如John所建议的,首先在名称树中重命名目标:

Map<String, PdfString> renameDestinations(PdfDocument pdf,
                                                  String suffix) {

    Map<String, PdfString> renamed = new HashMap<>();

    PdfNameTree nameTree = pdf.getCatalog().getNameTree(PdfName.Dests);
    Map<String, PdfObject> names = nameTree.getNames();

    Set<String> ks = new HashSet<String> (names.keySet());
    for (String key : ks) {
        String oldName = key;
        PdfString newName = new PdfString(oldName + suffix);
        PdfObject value = names.get(key);
        names.remove(key);
        names.put(newName.toUnicodeString(), value);
        renamed.put(oldName, newName);
    }

    nameTree.setModified();

    return renamed;
}

然后将大纲中的目标更改为新目标:

void renameOutlines(PdfOutline pdo, Map<String, PdfString> old2new) {

    for (PdfOutline child : pdo.getAllChildren()) {
        renameOutlines(child, old2new);
    }

    PdfDestination dest = pdo.getDestination();

    if (!((dest instanceof PdfNamedDestination)
         || (dest instanceof PdfStringDestination))) {
        return;
    }

    String oldDest = "";
    if (pdo.getDestination().getPdfObject().isString()) {

        oldDest = ((PdfString) pdo.getDestination()
                                  .getPdfObject()).toUnicodeString();

    } else if (pdo.getDestination().getPdfObject().isName()) {

        oldDest = ((PdfName) pdo.getDestination()
                                .getPdfObject()).getValue();
    }

    if (oldDest != null && old2new.containsKey(oldDest)) {
        pdo.addDestination(new PdfStringDestination(
                                            old2new.get(oldDest)));
    }

}

最后,更改每个页面上的链接以使用新的目的地:

void renameLinks(PdfDocument pdf, Map<String, PdfString> renamed) {

    int numPages = pdf.getNumberOfPages();
    for (int p = 1; p <= numPages; p++) {

        List<PdfAnnotation> annots = pdf.getPage(p).getAnnotations();

        for (PdfAnnotation annot : annots) {

                PdfObject dest = annot.getPdfObject().get(PdfName.Dest);

                if (dest != null && dest.isName()) {
                    String oldString = ((PdfName) dest).getValue();
                    if (renamed.containsKey(oldString)) {  
                        annot.getPdfObject().put(PdfName.Dest, 
                                   renamed.get(oldString));
                    }
                }
        }
    }
}

后缀当然应该是唯一的每个文件被合并。

相关问题