1

I'm trying to do this use case:

  • I start with a document that contains a digital signature (our provider is using DCC https://github.com/esig/dss which is based on PDFBox...)
  • with PDFBox 2.0.27 I added an annotation using incremental save (I used different kind of annotation, free text, stamp as text, stamp with image…)
  • I send this document for a new digital sign

Actual result : When I open my document with adobe acrobat reader, I click on validate all in the signature panel and acrobat claims that my annotation was changed The stamp annotation object is correctly set and is not provided in another incremental save, I don't know why Adobe claims it was changed.

Acrobat reader signature panel Link to the final PDF : https://drive.google.com/file/d/1CV7lJ8dY1gDcx3rgHoW5yHJ612JSAK9B/view?usp=share_link

Expected result : When I open the document in acrobat, I can see the annotation, in the signature panel everything is green, the annotations are not marked as modified EVEN after clicking on "Validate ALL"

This is the code source :

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.PDAnnotation;
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;
import java.util.List;

public class PDFAnnotStampText {
    public static void main(String[] args) {

        //Creating PDF document object
        PDDocument document = null;
        String filename = "20221217-VisibleSign-BeforeAnnot-02";

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

            if (!document.isEncrypted()) {
                System.out.println("not encrypted");

                // https://stackoverflow.com/questions/65927280/annotation-content-not-appearing-with-pdfbox
                OutputStream resultFile = new FileOutputStream("testfiles/" + filename + "_stamptxt.pdf");

                PDPage page = (PDPage) 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 {

        List<PDAnnotation> annotations = page.getAnnotations();
        PDAnnotationRubberStamp t = new PDAnnotationRubberStamp();

        t.setAnnotationName(name); // might play important role
        t.setPrinted(true); // always visible
        t.setReadOnly(true); // does not interact with user
        t.setContents(text);

        // calculate realWidth, realHeight according to font size (e.g. using _font.getStringWidth(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);

        annotations.add(t);
        page.setAnnotations(annotations);

        // these must be set for incremental save to work properly (PDFBOX < 3.0.0 at least?)
        ap.getCOSObject().setNeedToBeUpdated(true);
        t.getCOSObject().setNeedToBeUpdated(true);
        page.getResources().getCOSObject().setNeedToBeUpdated(true);
        page.getCOSObject().setNeedToBeUpdated(true);
        doc.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdated(true);
        doc.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);
    }

    private static void modifyAppearanceStream(PDAppearanceStream aps, PDAnnotation ann) throws IOException {
        PDAppearanceContentStream apsContent = null;

        try {
            PDRectangle rect = ann.getRectangle();
            rect = new PDRectangle(0, 0, rect.getWidth(), rect.getHeight()); // need to be relative - this is mega important because otherwise it appears as if nothing is printed
            aps.setBBox(rect); // set bounding box to the dimensions of the annotation itself

            // embed our unicode font (NB: yes, this needs to be done otherwise aps.getResources() == null which will cause NPE later during setFont)
            PDResources res = new PDResources();
            PDFont font = PDType1Font.HELVETICA_BOLD;
            res.add(font).getName(); // okay I create _font elsewhere
            aps.setResources(res);

            // draw directly on the XObject's content stream
            apsContent = new PDAppearanceContentStream(aps);

            apsContent.beginText();
            apsContent.setFont(PDType1Font.HELVETICA_BOLD, 12); // _font
            apsContent.setTextMatrix(Matrix.getTranslateInstance(0, 1));
            apsContent.showText(ann.getContents());
            apsContent.endText();
        }
        finally {
            if (apsContent != null) {
                try {
                    apsContent.close();
                } catch (Exception ex) {
                    System.out.println(ex);
                }
            }
        }

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

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

Pulse Mind
  • 11
  • 2
  • 1
    @KJ You missed the image, see the new edit. He added the stamp annotation (object 129) between signature 1 and 2. After adding signature 2 both signatures are still valid, but now the stamp annotation is considered modified despite that object 129 and related objects were not part of the last incremental save, that is the mystery. – Tilman Hausherr Dec 18 '22 at 03:36
  • https://drive.google.com/drive/folders/1mVuP5XLgRp0KUcZ9K85DC18-f3XL2ZFv?usp=sharing comment by Dimitar KRASTEV, I signed the same document using Acrobat Pro and the DSS service. When the document is signed with Acrobat Pro - everything is fine (at least Adobe are compliant with themselves). When it is signed with DSS - there is a problem with the annotation considered as modified. I did not use the same visible signatures wia DSS and Acrobat (the qualified certificate is the same). (...)analyze those files and see why acrobat considering the annotation modified after the second DSS signature – Tilman Hausherr Dec 18 '22 at 11:04
  • The Adobe signed PDF includes the stamp annotation in the last incremental save and adds two things: 1) `/P 14 0 R` and 2) `/NM(495f9a95-136e-4992-881a-6a834fa85519)` . Maybe Adobe adds these itself quietly and then complains about its own behaviour. Try adding the /P and /NM yourself. (call `setPage()`and `setAnnotationName()`). – Tilman Hausherr Dec 18 '22 at 11:54
  • 1
    Well done @TilmanHausherr and Dimitar, I added t.setPage(page) and it works fine :) – Pulse Mind Dec 18 '22 at 13:56

1 Answers1

1

As told in the comments, the Adobe signed PDF (thank you Dimitar) included the stamp annotation in the last incremental save (the DSS signed PDF didn't) and added two things: 1) /P 14 0 R and 2) /NM (495f9a95-136e-4992-881a-6a834fa85519). Maybe Adobe adds these itself quietly and then complains about its own behavior, despite that both entries are optional.

Calling t.setPage(page) in the code from the question solved the problem.

I've also submitted a bug report on this semi-official site.

Tilman Hausherr
  • 17,731
  • 7
  • 58
  • 97