合并两个 PDF/A 结果也应该是有效的 PDF/A pdfbox

问题描述 投票:0回答:1

我正在使用 pdfbox 合并两个 PDF/A

现在我的代码如下所示:

    PDFMergerUtility mergerUtility = new PDFMergerUtility();

    File file = new File("example/c.pdf");

    mergerUtility.addSource(new File("example/a.pdf"));
    mergerUtility.addSource(new File("example/b.pdf"));

    mergerUtility.setDestinationFileName(file.getAbsolutePath());

    try {
        mergerUtility.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
    } catch (IOException ex) {
        throw new RuntimeException("Unable to merge", ex);
    }

    File inputFile = new File("example/c.pdf");
    PDDocument doc = PDDocument.load(inputFile);

    File orig = new File("example/a.pdf");
    PDDocument origDoc = PDDocument.load(orig);
    File orig2 = new File("example/b.pdf");
    PDDocument orig2Doc = PDDocument.load(orig2);
    PDStructureTreeRoot treeRoot = origDoc.getDocumentCatalog().getStructureTreeRoot();
    PDStructureTreeRoot treeRoot2 = orig2Doc.getDocumentCatalog().getStructureTreeRoot();
    treeRoot.setKids(treeRoot2.getKids());
    doc.getDocumentCatalog().setStructureTreeRoot(treeRoot);

    List<PDOutputIntent> outputIntents=new ArrayList<>();
    outputIntents.add(doc.getDocumentCatalog().getOutputIntents().get(0));
    doc.getDocumentCatalog().setOutputIntents(outputIntents);
    doc.save("example/d.pdf");
    doc.close();

通过将 OutputIntent 设置为与第一页相同(以便 d.pdf 只有一个),我已经解决了很多问题...我遇到的最后一个问题(仅在此验证器上https://avepdf.com/ pdfa-validation)是 4:

“非标准结构类型未映射到任何功能等效的标准类型。”

我能够识别它们,这是由于在我的结果(由 pdfbox 生成)上使用了“THead”和“TBody”。

通过使用相同的 StructureTreeRoot 作为原始文件之一(即“最终文件”),我能够将该错误减少一半(现在我只得到 2 个“非标准结构类型未映射到任何功能等效的标准类型。”) “您可以看到的代码,仍然无法从两个 PDF/A 的合并中获得有效的 PDF/A)...但我不知道如何合并这两个 StructureTreeRoot 或者这是否是真正的解决方案(也许有一种方法可以告诉 pdfbox 只是避免使用 THead 和 TBody 来代替)。

结果已经很好了,因为它通过了大多数 pdf/验证器,我只需要它也通过该验证器(因为它是我工作的公司使用的验证器)...而且我不认为验证器是罪魁祸首,因为两个输入文件都作为有效的 PDF/A 文件传递。

有什么想法吗?

PS。我找到了一种合并两个 PDStructureTreeRoot 的方法仍然不起作用...但我更新了代码。添加

PDStructureTreeRoot treeRoot2 = orig2Doc.getDocumentCatalog().getStructureTreeRoot();
treeRoot.setKids(treeRoot2.getKids());
java merge pdfbox pdfa
1个回答
0
投票

好吧...这真的很难。 我第一次必须从chatgpt那里获得帮助,但这仍然不够,因为当然AI无法创建大量的工作代码,每次我必须进行更正时,它仍然是一个很好的帮助用代码示例(一种原始代码)翻译我的想法。 无论如何,我必须理解的最重要的事情是,不可能通过克隆 StructureTreeRoot 来解决我的问题(但这是在我的编辑中),所以我删除了这部分代码,没有理由保留它。 我不明白如何避免创建 THead 或 TBody。 所以我唯一能做的就是用像 P 这样的标准替代品替换它们中的每一个。

第一个问题是如何以一种可以迭代的方式获得根。为了理解这一点,我首先调试我的树并手动访问它,直到到达 THead...由于我可以使用生成的 PDF 内部的 Visual Studio Code 读取的结构,我有了一些方向。 AI 在这里提供了一点帮助,让我了解如何访问一些受保护的变量,并且我只能在调试模式下访问这些变量。

COSDictionary catalogDict = doc2.getDocumentCatalog().getCOSObject();
COSObject structTreeRootRef = (COSObject) catalogDict.getItem(COSName.STRUCT_TREE_ROOT);
COSDictionary structTreeRootDict = (COSDictionary) structTreeRootRef.getObject();
COSBase result = structTreeRootDict.getItem(COSName.K);
COSDictionary dict1 = result instanceof COSObject ? (COSDictionary) ((COSObject) result).getObject() : null;
//this
COSArray array = result instanceof COSArray ? (COSArray) result : new COSArray();
//or
COSBase result1 = dict1.getItem(COSName.K);
COSArray array = result1 instanceof COSArray ? (COSArray) result1 : new COSArray();

我没有原始代码,但基本上在第一部分之后我只是做了一个 get(i) 因为我知道我需要获取的每个节点,最后

subDict.setItem(COSName.S, COSName.getPDFName("P"));

当然,这已经是工作代码了(这很棒,因为要达到这一点,我必须学习如何访问 pdf 树,而 pdfbox 在那一端根本不直观),但当然我还没有完成,因为我的解决方案仅适用于我自己的示例的两个 pdf/a 。所以我决定将 get(i) 放入 for 循环中。更好,但仍然不是一个很好的解决方案,因为当我尝试另一个 pdf 时,我的 THead 和 TBody 更深一层,我必须在内部添加另一个 for 循环才能使其再次工作......当然,性能不是很好。 这就是 chatgpt 通过提供递归替代方案再次提供帮助的地方(解决方案很简单,但老实说我根本没有想到)...我仍然需要大量纠正代码,但最终正确的解决方案是这样的:

    COSDictionary catalogDict = doc2.getDocumentCatalog().getCOSObject();
    COSObject structTreeRootRef = (COSObject) catalogDict.getItem(COSName.STRUCT_TREE_ROOT);
    COSDictionary structTreeRootDict = (COSDictionary) structTreeRootRef.getObject();
    COSName newName = COSName.getPDFName("P");

    updateStructureTree(structTreeRootDict, newName);

递归方法:

private static void updateStructureTree(COSDictionary dict, COSName newName) {
    COSBase result = dict.getItem(COSName.K);
    COSDictionary dict1 = result instanceof COSObject ? (COSDictionary) ((COSObject) result).getObject() : null;

    if(dict1 != null){
        COSBase result1 = dict1.getItem(COSName.K);
        COSArray array = result1 instanceof COSArray ? (COSArray) result1 : new COSArray();

        for (COSBase resultItem : array) {
            COSDictionary subDict = resultItem instanceof COSObject ?
                    (COSDictionary) ((COSObject) resultItem).getObject() :
                    new COSDictionary();

            if (subDict.getItem(COSName.S) != null &&
                    (subDict.getItem(COSName.S).equals(COSName.getPDFName("THead")) ||
                            subDict.getItem(COSName.S).equals(COSName.getPDFName("TBody")))) {
                subDict.setItem(COSName.S, newName);
            }

            updateStructureTree(subDict, newName);
        }
    }else{
        COSArray array = result instanceof COSArray ? (COSArray) result : new COSArray();

        for (COSBase resultItem : array) {
            COSDictionary subDict = resultItem instanceof COSObject ?
                    (COSDictionary) ((COSObject) resultItem).getObject() :
                    new COSDictionary();

            if (subDict.getItem(COSName.S) != null &&
                    (subDict.getItem(COSName.S).equals(COSName.getPDFName("THead")) ||
                            subDict.getItem(COSName.S).equals(COSName.getPDFName("TBody")))) {
                subDict.setItem(COSName.S, newName);
            }

            updateStructureTree(subDict, newName);
        }
    }
}

PS我找到了一个替代方案: 只需创建一个列表并

mergerUtility.setDocumentMergeMode(PDFMergerUtility.DocumentMergeMode.OPTIMIZE_RESOURCES_MODE);
for (int i = 1; i < lists.size(); i++) {
    PDDocument currentDoc = PDDocument.load(lists.get(i));

    mergerUtility.appendDocument(docC, currentDoc);
}

并保存您的新文档...使用appendDocument而不是mergeDocuments不会使PDF/A无效,即使它有THhead和TBody...并且它也不会更改标题版本。

© www.soinside.com 2019 - 2024. All rights reserved.