You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PDFBox 2.0.24添加PDAnnotationText报未嵌套在Annot结构元素错误如何解决

PDF/A UA-1/1a规范下嵌套PDAnnotationText对象报错问题

在构造同时符合PDF/A UA-1 / PDF/A-1a规范的PDF文件时,嵌套PDAnnotationText对象时触发无障碍检查工具报错,涉及工具包含3版、2021版PAC以及VeraPDF。

报错详情

  • PAC 2021报错
    • 注解未嵌套在"Annot"结构元素内
    • 存在疑似空对象引用问题,提示对象引用未设置到对象实例
  • VeraPDF报错
    • 除Widget、Popup、Link子类型的注解外,其余注解均需嵌套在Annot标签内
  • PDFDebugger结构查看结果
    已通过PDFDebugger查看注解结构、对应结构树根节点,可复现问题。示例文件包含文本内容,第一、二行各有一个注解可触发上述报错。

规范要求

根据PDF协会发布的结构元素最佳实践指南(第31页),注解需遵循以下构造规则:

Annot结构元素用于包裹除链接和组件外的所有注解,注解分为两类:

  • 标记注解或用作标记注解的类型:Annot结构元素需包裹标记内容和指向实际注解的对象引用,支持嵌套。
  • 其他注解:Annot结构元素通常仅需包裹指向实际注解的引用。

示例:
<Annot>
内容 OBJR (指向<Annot>类型的Highlight)

创建规则

若标记注解对应的待标记内容尚未被结构元素包裹,无法直接与注解关联,会出现适配问题。这种情况下需要先识别并隔离待标记内容,将其包裹在标记内容序列中,再与实际注解一同关联到结构元素。
可能需要对标记内容周边的其他标记内容序列和结构元素做调整。

消费规则

向用户展示标记内容时,应包含以下信息:

  • 被标记的内容
  • 内容被标记的提示及标记注解的类型
  • 注解content字段的内容
  • 所有回复该注解的其他注解

虽PDF/UA-1未强制要求兼容阅读器实现该能力,但处理器还应支持展示注解的属性,例如作者、主题、状态、时间、勾选状态等。

现有实现说明

具备Java开发经验,因PDFBox公开文档较少,参考了公开的无障碍PDF构造示例实现代码,尝试了将注解代码嵌套在内容块内、单独拆分结构等方案均未解决问题,未排查到错误点。相关实现代码如下:

public PDStructureElement drawElement(Cell textCell, float x, float y, int height, 
PDStructureElement parent,
        String structType, int pageIndex, PDPage page) throws IOException {

    // 初始化带MCID的标记内容元素,创建对应的结构元素容器
    PDPageContentStream contentsElement = new PDPageContentStream(pdf, 
pages.get(pageIndex),
            PDPageContentStream.AppendMode.APPEND, false);
    currentElem = addContentToParent(pdf, null, structType, pages.get(pageIndex), 
parent, pageIndex);
    setNextMarkedContentDictionary();
    contentsElement.beginMarkedContent(COSName.P, 
PDPropertyList.create(currentMarkedContentDictionary));

    // 示例使用Old English字体渲染内容
    drawCellText(textCell, (PAGE_WIDTH
            - (int) (templateRegularFont.getStringWidth(textCell.getDefaultText()) /  
1000 * textCell.getFontSize()))
            / 2, y + height + textCell.getFontSize(), contentsElement);

    // 结束标记内容,将P结构元素追加到父容器
    contentsElement.endMarkedContent();
    addContentToParent(pdf, COSName.P, null, pages.get(pageIndex), currentElem, 
pageIndex);
    contentsElement.close();

    // 存在文本注解时的处理逻辑
    if (textCell.getAnnotationText() != null) {
        float stringWidth = 0.00f;

        stringWidth = templateRegularFont
                .getStringWidth(textCell.getDefaultText().substring(0, 
textCell.getDefaultText().length())) / 1000
                * textCell.getFontSize();

        annotations = pages.get(pageIndex).getAnnotations();

        PDRectangle rect = new PDRectangle(x + 5, PAGE_HEIGHT - height - y - 
textCell.getFontSize() - 5,
                stringWidth, textCell.getFontSize() + 5);

        PDPageContentStream contentsAnnot = new PDPageContentStream(pdf, 
pages.get(pageIndex),
                PDPageContentStream.AppendMode.APPEND, false);
        currentElem = addContentToParent(pdf, null, structType, 
pages.get(pageIndex), 
parent, pageIndex);
        setNextMarkedContentDictionary();
        contentsAnnot.beginMarkedContent(COSName.ANNOT, 
PDPropertyList.create(currentMarkedContentDictionary));

        // 构造文本注解对象
        PDAnnotationText annotText = new PDAnnotationText();
        annotText.setAnnotationName(textCell.getDefaultText());
        annotText.setContents(textCell.getDefaultText());
        annotText.setHidden(false);
        annotText.setInvisible(false);
        annotText.setName("Note");
        annotText.setNoRotate(true);
        annotText.setNoView(false);
        annotText.setNoZoom(true);
        annotText.setOpen(false);
        annotText.setPage(pages.get(pageIndex));
        annotText.setPrinted(true);
        annotText.setRectangle(rect);
        annotText.setStructParent(currentStructParent);

        PDAppearanceDictionary appearance = new PDAppearanceDictionary();
        appearance.getCOSObject().setDirect(true);

        PDAppearanceStream appearanceStream = new PDAppearanceStream(pdf);
        appearanceStream.setBBox(rect);
        appearance.setNormalAppearance(appearanceStream);
        annotText.setAppearance(appearance);

        PDFTemplateStructure pdfStructure = new PDFTemplateStructure();
        pdfStructure.setAppearanceDictionary(appearance);

        annotations.add(annotText);
        pages.get(pageIndex).setAnnotations(annotations);

        PDStructureElement fieldElem = new 
PDStructureElement(StandardStructureTypes.P, parent);
        fieldElem.setPage(pages.get(pageIndex));
        COSArray kArray = new COSArray();
        kArray.add(COSInteger.get(currentMCID));
        fieldElem.getCOSObject().setItem(COSName.K, kArray);
        
        // 添加注解的对象引用用于标记
        PDObjectReference objectReference = new PDObjectReference();
        objectReference.setReferencedObject(annotText);
        annotationRefs.add(objectReference);

        addWidgetContent(pdf, annotationRefs.get(annotationRefs.size() - 1), 
fieldElem, StandardStructureTypes.P,
                pageIndex, pages, page);

        contentsAnnot.endMarkedContent();
        addContentToParent(pdf, COSName.ANNOT, null, pages.get(pageIndex), 
currentElem, pageIndex);

        contentsAnnot.close();

        COSDictionary dict = pages.get(pageIndex).getCOSObject();
        while (dict.containsKey(COSName.PARENT)) {
            COSBase COSParent = dict.getDictionaryObject(COSName.PARENT);
            if (COSParent instanceof COSDictionary) {
                dict = (COSDictionary) COSParent;
                dict.setNeedToBeUpdated(true);
            }
        }
    }    
    return currentElem;
}

若需查看其他代码块可补充提供。


内容的提问来源于stack exchange,提问作者MartinLondonUK1977

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 16:15:06