0

I want to ask a question. I added digital paging seal to a multi-page PDF, each page has the same seal, add the digital signature once on the first page, and then the other pages only need to quote the appearance of the first seal. But using adobe Acrobat DC to open, there will be an extra "123" signature in the generated document. What causes it?

I wrote the following code based on this answer and it helped me a lot.

       addAp(doc, doc.getPage(0), rect, signature, xz[0]);
       for (int i = 0; i < doc.getNumberOfPages() - 1; i++) {
            addAnnots(doc.getPage(i));
       }
       addAp(doc, doc.getPage(1), lerect, signature, xz[1]);
       for (int i = 1; i < doc.getNumberOfPages(); i++) {
            addAnnots(doc.getPage(i));
       }

void addAp(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature, BufferedImage signatureImage) throws IOException {
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    List<PDField> acroFormFields = acroForm.getFields();

    PDSignatureField signatureField = new PDSignatureField(acroForm);
    signatureField.setValue(signature);
    PDAnnotationWidget widget = signatureField.getWidgets().get(0);
    acroFormFields.clear();
    acroFormFields.add(signatureField);

    widget.setRectangle(rectangle);
    widget.setPage(pdPage);

    // from PDVisualSigBuilder.createHolderForm()
    PDStream stream = new PDStream(pdDocument);
    PDFormXObject form = new PDFormXObject(stream);
    PDResources res = new PDResources();
    form.setResources(res);
    form.setFormType(1);
    PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight());

    form.setBBox(bbox);

    // from PDVisualSigBuilder.createAppearanceDictionary()
    PDAppearanceDictionary appearance = new PDAppearanceDictionary();
    appearance.getCOSObject().setDirect(true);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    appearance.setNormalAppearance(appearanceStream);
    widget.setAppearance(appearance);

    ByteArrayOutputStream bao = new ByteArrayOutputStream();
    ImageIO.write(signatureImage, "png", bao);

    bao.flush();
    byte[] imageByte = bao.toByteArray();
    bao.close();

    PDImageXObject pdImage = PDImageXObject.createFromByteArray(pdDocument, imageByte, null);


    try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream)) {

        PDExtendedGraphicsState r0 = new PDExtendedGraphicsState();

        r0.setBlendMode(BlendMode.DARKEN);

        cs.setGraphicsStateParameters(r0);

        cs.addComment("This is a comment");
        cs.drawImage(pdImage, 0, 0, rectangle.getWidth(), rectangle.getHeight());

    }

    setPdAnnotationWidget(widget);
}

void addAnnots(PDPage pdPage) throws IOException {

    pdPage.getAnnotations().add(getPdAnnotationWidget());

    COSDictionary pageTreeObject = pdPage.getCOSObject();
    while (pageTreeObject != null) {
        pageTreeObject.setNeedToBeUpdated(true);
        pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT);
    }
}

PDFBOX version is 2.0.20.

After modification:

     ArrayList<PDAnnotationWidget> listWidget = addAp1(doc, signature);
     addAp2(doc, doc.getPage(0), rect, xz[0], listWidget.get(0));
     for (int i = 0; i < doc.getNumberOfPages() - 1; i++) {
         addAnnots(doc.getPage(i));
     }
     addAp2(doc, doc.getPage(1), lerect, xz[1], listWidget.get(1));
     for (int i = 1; i < doc.getNumberOfPages(); i++) {
         addAnnots(doc.getPage(i));
     }

    ArrayList<PDAnnotationWidget> addAp1(PDDocument pdDocument, PDSignature signature) throws IOException {
            ArrayList<PDAnnotationWidget> widgetList = new ArrayList<>();
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    List<PDField> acroFormFields = acroForm.getFields();

    PDAnnotationWidget widget1 = new PDAnnotationWidget();
    PDAnnotationWidget widget2 = new PDAnnotationWidget();
    widgetList.add(widget1);
    widgetList.add(widget2);

    PDSignatureField signatureField = new PDSignatureField(acroForm);
    signatureField.setValue(signature);

    signatureField.setWidgets(widgetList);

    acroFormFields.clear();
    acroFormFields.add(signatureField);


    return widgetList;
}
 void addAp2(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, BufferedImage signatureImage, PDAnnotationWidget widget) throws IOException {

   widget.setPage(pdPage);
   widget.setRectangle(rectangle);
   ....
}

WPS: enter image description here

Adobe Acrobat DC: enter image description here

1 Answers1

1

You add 2 signature fields to the document.

You call addAp twice. Each time that method creates a PDSignatureField, and in the loop immediately after the addAp call the single widget of that field is added to the pages. Thus, both signature fields are reachable in the resulting PDF.

The two signature fields share the signature value.

addAp sets the value of both signature fields to the same signature value. When eventually the signature bytes are written into this value, both signature fields become signed.

Only the second signature field is in the PDF form definition.

addAp removes any field from the PDF form definition before adding the newly generated one. In the end, therefore, the PDF form definition only contains the signature field from the last addAp call.

Adobe Acrobat opens the file...

Adobe Acrobat automatically only validates the signature field in the PDF form definition. But as soon as it displays the widget of the other signature field, it also displays it on the signature panel. As it wasn't there from the start, though, it is displayed as not-yet-validated.


By not clearing the PDF form definition field list in the second addAp call, you should get two automatically validated signature fields in the signature panel.

Alternatively, by creating only a single form field with two widget annotations, you should get only a single signature field in the signature panel.

As a warning: You reference the same widget annotation from multiple pages. This strictly speaking is forbidden by the PDF specification. Thus, any validator may warn about this issue and - as this issue occurs in the context of a signature - message doubts about the validity of that signature.

mkl
  • 90,588
  • 15
  • 125
  • 265
  • Thanks. I just need one signature field. So i will create a `PDSignatureField`.I divided the `addAp` method into `addAp1` and `addAp2` with `acroFormFields.add(signatureField);` as the boundary, `addAp1` is called only once, and returns the list structure of `PDAnnotationWidget`. But the generated document only sees four visible stamps. I updated my question. – Serendipity Aug 03 '22 at 03:23
  • Your code assumes that a widget object is merged with the field object, `new PDAnnotationWidget(signatureField.getCOSObject())` re-uses the field dictionary as widget dictionary. For multiple widgets on a single field, you need to create separate widget objects and add them to the field as it's widgets. – mkl Aug 03 '22 at 05:00
  • After I modified it, I found a new problem. The file is opened with different software, and the realization form of the stamps is different. – Serendipity Aug 03 '22 at 06:33
  • 1
    Ah, ok, so Adobe Acrobat apparently assumes all widget annotations of the same signature field must look alike. I wasn't aware of that. Nonetheless, you're trying something that strictly speaking is forbidden by the PDF specification. Thus, you always have to face the possibility of unexpected viewer behavior. Have you tried using the same appearance in all cases, the full seal, with half the annotation rectangle outside the page area? – mkl Aug 03 '22 at 07:38
  • I understand what you mean.No.I haven't tried. – Serendipity Aug 03 '22 at 07:57