生成PDF/UA合规PDF遇PAC检查报错:Test Object Not Tagged
我正在开发生成符合PDF/UA标准的PDF文件,核心目标是满足无障碍标准并通过PAC(PDF Accessibility Checker)工具检测。目前遇到的问题是,PAC工具持续标记我的PDF存在错误:"Test Object Not Tagged",这表明必要的标签可能缺失或实现不当,但我不清楚具体遗漏了哪些内容。
我已完成以下操作:
- 定义文档结构元素
- 在PDF内容生成代码中使用正确的标签
- 应用我认为正确的无障碍元数据和设置
然而标签仍未被正确识别,似乎遗漏了某些核心内容。请问为实现PDF/UA合规的正确标签,我可能忽略了哪些步骤?若能指出我可能出错的环节,或需检查的关键标签元素,将不胜感激。
我的代码如下:
public void newPdf() { int mcidCounter = 0; // 从0开始 int structParentCounter = 0; // 从0开始 PDDocument document = new PDDocument(); PDPage page = new PDPage(PDRectangle.A4); document.addPage(page); // 设置页面的StructParents条目 page.setStructParents(structParentCounter); // structParentCounter为0 PDPageContentStream contentStream = new PDPageContentStream(document, page); PDType0Font font = loadFont(FontEnum.BUNDES_SANS_WEB_REGULAR, document); // 将字体添加到页面资源 PDResources resources = page.getResources(); if (resources == null) { resources = new PDResources(); page.setResources(resources); } resources.add(font); PDDocumentCatalog catalog = document.getDocumentCatalog(); PDStructureTreeRoot structureTreeRoot = new PDStructureTreeRoot(); catalog.setStructureTreeRoot(structureTreeRoot); catalog.setLanguage("de-DE"); // 将文档语言设置为德语 PDMarkInfo markInfo = new PDMarkInfo(); markInfo.setMarked(true); catalog.setMarkInfo(markInfo); // 创建文档结构元素 PDStructureElement documentElement = new PDStructureElement(StandardStructureTypes.DOCUMENT, structureTreeRoot); structureTreeRoot.appendKid(documentElement); // 创建段落结构元素 PDStructureElement paragraphElement = new PDStructureElement(StandardStructureTypes.P, documentElement); paragraphElement.setPage(page); documentElement.appendKid(paragraphElement); // 准备带MCID的标记内容 COSDictionary markedContentDictionary = new COSDictionary(); markedContentDictionary.setInt(COSName.MCID, mcidCounter); // 开始标记内容 contentStream.beginMarkedContent(COSName.P, PDPropertyList.create(markedContentDictionary)); contentStream.setFont(font, 12); contentStream.beginText(); contentStream.newLineAtOffset(50, 700); contentStream.showText("Hallo Welt"); contentStream.endText(); contentStream.endMarkedContent(); // 关闭内容流 contentStream.close(); // 创建父树并关联结构元素 COSDictionary parentTreeRoot = new COSDictionary(); PDNumberTreeNode parentTree = new PDNumberTreeNode(parentTreeRoot, COSBase.class); // 映射StructParent到结构元素 Map<Integer, COSObjectable> parentTreeMap = new HashMap<>(); parentTreeMap.put(structParentCounter, paragraphElement); parentTree.setNumbers(parentTreeMap); structureTreeRoot.setParentTree(parentTree); // 保存文档 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); document.save(outputStream); byte[] pdfBytes = outputStream.toByteArray(); document.close(); Path actualPath = Path.of("src/test/resources/pdf/small_test.pdf"); Files.write(actualPath, pdfBytes, StandardOpenOption.CREATE, StandardOpenOption.WRITE); }
PAC工具检测结果显示存在"Test Object Not Tagged"错误。
针对你遇到的PAC检测错误,以下是代码中遗漏的关键环节及修复方案:
1. 未关联标记内容(MCID)与结构元素
你创建了带MCID的标记内容,但段落结构元素没有绑定对应的MCID引用。PDF/UA要求结构元素必须明确关联页面中的标记内容,否则阅读器无法将结构标签与实际文本对应。
修复代码:在创建段落结构元素后添加MCID关联:
// 创建段落结构元素后添加这行 paragraphElement.appendKid(new PDMCID(mcidCounter));
2. 父树(Parent Tree)映射逻辑错误
父树的作用是将页面的StructParents值映射到顶层结构元素(DOCUMENT),而非直接映射到段落元素。当前代码把页面的StructParents绑定到段落元素,不符合PDF规范,导致结构树无法正确遍历。
修复代码:修正父树的映射目标:
Map<Integer, COSObjectable> parentTreeMap = new HashMap<>(); // 将页面的StructParents映射到文档根结构元素 parentTreeMap.put(structParentCounter, documentElement); parentTree.setNumbers(parentTreeMap); structureTreeRoot.setParentTree(parentTree);
3. 缺少显式的角色映射(Role Map)
虽然使用了标准结构标签,但显式声明角色映射能确保阅读器正确识别结构类型,这是PDF/UA的推荐要求。
添加代码:在设置结构树根后添加角色映射:
COSDictionary roleMap = new COSDictionary(); // 绑定标准结构类型到对应角色(可根据需求扩展) roleMap.setItem(COSName.getPDFName("P"), COSName.getPDFName("Paragraph")); roleMap.setItem(COSName.getPDFName("Document"), COSName.getPDFName("Document")); structureTreeRoot.setRoleMap(roleMap);
4. 验证字体的无障碍属性
PDF/UA要求文本使用可嵌入的Unicode字体,确保屏幕阅读器能正确解析文本。检查loadFont方法是否正确嵌入字体,可添加以下代码确保字体嵌入:
// 确保字体被嵌入(子集化或完整嵌入) font.setSubset(true);
5. 可选优化:添加文档大纲
添加大纲不仅能提升无障碍体验,还能帮助PAC工具确认结构完整性,可按需添加:
PDDocumentOutline outline = new PDDocumentOutline(); catalog.setDocumentOutline(outline); PDOutlineItem rootItem = new PDOutlineItem(); rootItem.setTitle("文档内容"); outline.addFirst(rootItem);
内容的提问来源于stack exchange,提问作者CodeComposer

