我正在使用 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());
好吧...这真的很难。 我第一次必须从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...并且它也不会更改标题版本。