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

PDFBox2添加签名间批注被Acrobat判定修改的原因及解决方法

问题根源

  1. 页面批注列表被整体替换:代码中通过page.setAnnotations(annotations)替换整个页面批注数组,而非追加新批注。增量保存时,这会导致PDF的Annots字典被完全重写,Acrobat会将此视为对原有批注集合的修改。
  2. 不必要的对象更新标记:代码中错误标记页面资源为需更新(page.getResources().getCOSObject().setNeedToBeUpdated(true)),但实际未修改页面资源,这会引发Acrobat对文档变更的误判。
  3. 批注锁定属性缺失:仅设置ReadOnly不足以让Acrobat认定批注不可修改,需同时设置Locked属性。

解决步骤

1. 直接追加批注,避免替换列表

移除page.setAnnotations(annotations),改为直接向页面批注列表添加新对象:

page.getAnnotations().add(t);

2. 移除不必要的更新标记

删除page.getResources().getCOSObject().setNeedToBeUpdated(true),仅保留与批注相关对象的更新标记。

3. 补充批注锁定属性

在创建图章批注时添加:

t.setLocked(true);

4. 优化外观流资源处理

简化字体资源添加逻辑,确保资源关联正确(虽不影响显示,但能避免潜在的PDF结构异常)。


修改后的完整代码

import org.apache.pdfbox.pdmodel.PDAppearanceContentStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationRubberStamp;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.util.Matrix;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class PDFAnnotStampText {
    public static void main(String[] args) {
        PDDocument document = null;
        String filename = "20221217-VisibleSign-BeforeAnnot-02";

        try {
            document = PDDocument.load(new File("testfiles/" + filename + ".pdf"));

            if (!document.isEncrypted()) {
                System.out.println("not encrypted");
                OutputStream resultFile = new FileOutputStream("testfiles/" + filename + "_stamptxt.pdf");
                PDPage page = document.getPage(0);

                addAnnotation("annotation name", document, page, 50, 500, "Annot powered");

                document.saveIncremental(resultFile);
                document.close();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void addAnnotation(String name, PDDocument doc, PDPage page, float x, float y, String text) throws IOException {
        PDAnnotationRubberStamp t = new PDAnnotationRubberStamp();

        t.setAnnotationName(name);
        t.setPrinted(true);
        t.setReadOnly(true);
        t.setLocked(true); // 新增锁定属性
        t.setContents(text);

        float realWidth = 100, realHeight = 15;
        PDRectangle rect = new PDRectangle(x, y, realWidth, realHeight);
        t.setRectangle(rect);

        PDAppearanceDictionary ap = new PDAppearanceDictionary();
        ap.setNormalAppearance(createAppearanceStream(doc, t));
        t.setAppearance(ap);

        // 直接追加批注,不替换整个列表
        page.getAnnotations().add(t);

        // 仅保留必要的更新标记
        ap.getCOSObject().setNeedToBeUpdated(true);
        t.getCOSObject().setNeedToBeUpdated(true);
        page.getCOSObject().setNeedToBeUpdated(true);
        doc.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdated(true);
        doc.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);
    }

    private static void modifyAppearanceStream(PDAppearanceStream aps, PDAnnotationRubberStamp ann) throws IOException {
        try (PDAppearanceContentStream apsContent = new PDAppearanceContentStream(aps)) {
            PDRectangle rect = ann.getRectangle();
            // 设置外观流相对坐标系
            aps.setBBox(new PDRectangle(0, 0, rect.getWidth(), rect.getHeight()));

            PDResources res = new PDResources();
            PDFont font = PDType1Font.HELVETICA_BOLD;
            res.add(font); // 简化字体添加逻辑
            aps.setResources(res);

            apsContent.beginText();
            apsContent.setFont(font, 12);
            // 调整文本位置至批注区域内(可选,优化显示)
            apsContent.setTextMatrix(Matrix.getTranslateInstance(5, 10));
            apsContent.showText(ann.getContents());
            apsContent.endText();
        }

        aps.getResources().getCOSObject().setNeedToBeUpdated(true);
        aps.getCOSObject().setNeedToBeUpdated(true);
    }

    private static PDAppearanceStream createAppearanceStream(final PDDocument document, PDAnnotationRubberStamp ann) throws IOException {
        PDAppearanceStream aps = new PDAppearanceStream(document);
        modifyAppearanceStream(aps, ann);
        return aps;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 12:05:28