When people sign a PDF, they often do not merely want to see that signature in some extra signature panel, they want to see a presentation of that signature in the document itself. This is even more true for documents with multiple signatures when the signers have different roles.

The iText signing API does allow creating such a presentation. You have the choice of using one of a few default appearances or generating the appearance completely yourself.

If you create a signature in an already existing signature field of the PDF, that field may already have an appearance which your signature then would inherit unless you change it explicitly. As this is an interesting case and as you would choose to sign in an existing field by selecting it by its name, we start with a short excursion on the names of signature fields.

Excursion: Signature Field Names

A signature field is a special AcroForm form field. Each such form field is identified by its respective name.

The PdfSigner constructor creates a new, unused field name for the new signature. If you want to use an existing, unsigned signature field (or even if you simply don’t want an arbitrary signature name but a specific one), you can override that default name like this:

PdfSigner signer = new PdfSigner(...); signer.setFieldName("My Signature Name");
PdfSigner signer = new PdfSigner(...); signer.SetFieldName("My Signature Name");

If an unsigned signature field named "My Signature Name" exists in the PDF, that field will be used. If "My Signature Name" is the name of an existing signed signature field or of a non-signature field, an exception will be thrown. Otherwise, a new signature field with that name will be created.

Beware: iText does not allow names with dots in them. Dots in PDF form field names are used to separate levels of a field hierarchy in the document. But the iText signing API does not support signature fields that are not base fields of the form field hierarchy.

Creating Signatures With Appearances

When you create a signature, you can control the details of its in-document appearance via the PdfSignatureAppearance instance of your PdfSigner:

PdfSigner signer = new PdfSigner(...); PdfSignatureAppearance appearance = signer.getSignatureAppearance();
PdfSigner signer = new PdfSigner(...); PdfSignatureAppearance appearance = signer.GetSignatureAppearance();

By default, a new signature field has no in-document appearance. To trigger the creation of an appearance, you have to set the area in which you want the signature to be visualized (and also the page unless the default, page 1, is ok for you). For example:

appearance.setPageRect(new Rectangle(100, 500, 300, 100)); appearance.setPageNumber(2);
appearance.SetPageRect(new Rectangle(100, 500, 300, 100)); appearance.SetPageNumber(2);

This positions the new signature appearance on page 2 in a 300×100 rectangle with the lower left corner at (100, 500). The coordinate system is the one defined by the Media Box and the Crop Box of the page; you will want to make sure that the signature indeed is on-page, in particular in case of third-party documents.

Alternatively you can use the predefined location and page of an existing, unused signature field simply by setting its name in the PdfSigner, see the excursion above.

Built-in iText Signature Appearance Layouts

iText offers a few built-in layouts that organize certain bits of information in the signature appearance rectangle. These bits of information are:

Description - a text containing multiple lines:

  • "Digitally signed by " plus a name derived from the signer certificate.

            For details see the Name item below.

  • "Date: " plus the signing date and time.

            The date and time used here is initialized with the current date and time in the PdfSigner constructor but can be overwritten by setting its sign date property.

  • "Reason: " plus the signing reason (only if reason is set).

            The signing reason is an appearance property that is empty by default.

            The “Reason: ” prefix can be replaced using the appearance reason caption property.

  • "Location: " plus the signing location (only if location is set).

           The signing location is an appearance property that is empty by default.

           The “Location: ” prefix can be replaced using the appearance location caption property.

Graphic - an arbitrary image, e.g. of the signer themselves or their wet signature. This image is set using the appearance signature graphic property

Name - a name derived from the signer certificate.

iText uses the signer certificate subject common name. If the subject has no common name entry, iText uses the e-mail address. If the subject has neither a common name nor an e-mail address, the name remains empty.

If you sign using signDetached, the first certificate from the certificate chain is used automatically. If you use signExternalContainer, you have to explicitly set the signer certificate property of the appearance object.

The following layouts are built-in:

DESCRIPTION - this layout contains only the Description.

appearance.setRenderingMode(RenderingMode.DESCRIPTION); appearance.setReason("My reason for signing"); appearance.setLocation("My location for signing");
appearance.SetRenderingMode(RenderingMode.DESCRIPTION); appearance.SetReason("My reason for signing"); appearance.SetLocation("My location for signing");

GRAPHIC - this layout contains only the Graphic.

This mode requires the appearance signature graphic property to be set.

appearance.setRenderingMode(RenderingMode.GRAPHIC); appearance.setSignatureGraphic(ImageDataFactory.create(...));
appearance.SetRenderingMode(RenderingMode.GRAPHIC); appearance.SetSignatureGraphic(ImageDataFactory.Create(...));

GRAPHIC_AND_DESCRIPTION - this layout contains both the Graphic and the Description; depending on the aspect ratio of the appearance rectangle, the Graphic is arranged above or left of the Description.

This mode requires the appearance signature graphic property to be set.

appearance.setRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION); appearance.setReason("Specimen"); appearance.setLocation("Boston"); appearance.setSignatureGraphic(ImageDataFactory.create(...));
appearance.SetRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION); appearance.SetReason("Specimen"); appearance.SetLocation("Boston"); appearance.SetSignatureGraphic(ImageDataFactory.Create(...));

NAME_AND_DESCRIPTION - this layout contains both the Name and the Description; depending on the aspect ratio of the appearance rectangle, the Name is arranged above or left of the Description.

This mode requires the appearance certificate property to be set.

appearance.setRenderingMode(RenderingMode.NAME_AND_DESCRIPTION); appearance.setReason("Specimen"); appearance.setLocation("Boston"); appearance.setCertificate(...);
appearance.SetRenderingMode(RenderingMode.NAME_AND_DESCRIPTION); appearance.SetReason("Specimen"); appearance.SetLocation("Boston"); appearance.SetCertificate(...);

Excursion: Signature Appearance Layers

In the early days of PDF signatures Adobe created appearances with multiple “layers” some of which were manipulated in memory to display specific validation states:

  • n0: background layer, as preset when creating the signature field;
  • n1: validity layer for the unknown (e.g. question mark) and valid states;
  • n2: signature appearance containing information about the signature, e.g. the line art for the handwritten signature and the text giving the name, date, reason and location of the signature;
  • n3: validity layer for the invalid (e.g. X) state, above n2 so that the X cannot be hidden by n2;
  • n4; optional text layer, for a text presentation of the state of a signature, e.g. "Signature Not Verified" or "Signature Valid".

Due to security concerns, Adobe later abandoned this layer-based dynamic approach towards signature appearances, and it was never part of any ISO PDF specification.

While Acrobat still maintains a degree of backwards compatibility, support for this feature in iText was removed in iText 7, merely the concept of a background layer and a signature appearance layer remains. Since references to layers n0 and n2 still linger in the names of some API methods, we'll refer to them as such for convenience.

Simple Customizations

iText allows customizing the standard layouts described in section Built-in iText Signature Appearance Layouts above. The names of some of the customization methods may sound weird at first; the information from the Excursion: Signature Appearance Layers, though, explains these method names:

  • Re-using the appearance of the signature field as background.

If you are signing in an existing, empty signature field with a visualization, you can ask iText to keep it as background n0 layer in the signature appearance. For example, signing this empty signature field

you can get

(Don’t forget to set the signature field name to sign the existing field, see the Excursion: Signature Field Names above.)

  • Adding a background image

In addition to the signature graphic foreground image you can use another bitmap image as background of the n2 layer, optionally at a given scale.

(The default image scale value 0 stretches the image to fit the full appearance rectangle disregarding the aspect ratio; any negative image scale value stretches to fit the rectangle respecting the aspect ratio; any positive value is used as is.)

appearance.setImage(ImageDataFactory.create(...)); appearance.setImageScale(2);
appearance.SetImage(ImageDataFactory.Create(...)); appearance.SetImageScale(2);
  • Customizing description captions

As already mentioned above, you can change the captions “Reason: ” and “Location: ” in the description to arbitrary strings, even empty strings.

appearance.setReasonCaption("Objective: "); appearance.setReason("Specimen"); appearance.setLocationCaption("Whereabouts: "); appearance.setLocation("Boston");
appearance.SetReasonCaption("Objective: "); appearance.SetReason("Specimen"); appearance.SetLocationCaption("Whereabouts: "); appearance.SetLocation("Boston");
  • Customizing the text style

You can also change text font, text size, and text color.

(The default font size value 0 chooses the largest size for the text still to fit; in case of the NAME_AND_DESCRIPTION layout it does so separately for the name and the description.)

appearance.setLayer2Font(PdfFontFactory.createFont(StandardFonts.COURIER)); appearance.setLayer2FontColor(new DeviceRgb(0xF9, 0x9D, 0x25)); appearance.setLayer2FontSize(10);
appearance.SetLayer2Font(PdfFontFactory.CreateFont(StandardFonts.COURIER)); appearance.SetLayer2FontColor(new DeviceRgb(0xF9, 0x9D, 0x25)); appearance.SetLayer2FontSize(10);
  • Customizing the description text

Instead of letting iText assemble a generic description, you can select a completely custom one.

(This custom description will only be used in the visual appearance, it will not be present in an easily machine-readable form. To provide the text also in machine-readable form, consider setting the reason property to the same text.)

String restriction = "..."; appearance.setReason(restriction); appearance.setLayer2Text(restriction);
String restriction = "..."; appearance.SetReason(restriction); appearance.SetLayer2Text(restriction);

Completely Custom Appearances Layers

You can even design complete signature appearance layers (see the Excursion: Signature Appearance Layers above) yourself! For this purpose simply call the getLayer0 or getLayer2 method to retrieve a PdfFormXObject which is yours to draw on.

You can customize the n0 layer to give your signature appearance a special field background like this:

PdfFormXObject layer0 = appearance.getLayer0(); Rectangle rectangle = layer0.getBBox().toRectangle(); PdfCanvas canvas = new PdfCanvas(layer0, pdfSigner.getDocument()); canvas.setStrokeColor(new DeviceRgb(0xF9, 0x9D, 0x25)) .setLineWidth(2); for (int i = (int)(rectangle.getLeft() - rectangle.getHeight()); i < rectangle.getRight(); i += 5) { canvas.moveTo(i, rectangle.getBottom()) .lineTo(i + rectangle.getHeight(), rectangle.getTop()); } canvas.stroke();
PdfFormXObject layer0 = appearance.GetLayer0(); Rectangle rectangle = layer0.GetBBox().ToRectangle(); PdfCanvas canvas = new PdfCanvas(layer0, pdfSigner.GetDocument()); canvas.SetStrokeColor(new DeviceRgb(0xF9, 0x9D, 0x25)) .SetLineWidth(2); for (int i = (int)(rectangle.GetLeft() - rectangle.GetHeight()); i < rectangle.GetRight(); i += 5) { canvas.MoveTo(i, rectangle.GetBottom()) .LineTo(i + rectangle.GetHeight(), rectangle.GetTop()); } canvas.Stroke();

And you can customize the n2 layer to give your signature appearance a personalized foreground like this:

ImageData badge = ImageDataFactory.create(...); ImageData sign = ImageDataFactory.create(...); PdfFormXObject layer2 = appearance.getLayer2(); Rectangle rectangle = layer2.getBBox().toRectangle(); PdfCanvas canvas = new PdfCanvas(layer2, pdfSigner.getDocument()); float xCenter = rectangle.getLeft() + rectangle.getWidth() / 2; float yCenter = rectangle.getBottom() + rectangle.getHeight() / 2; float badgeWidth = rectangle.getHeight() - 20; float badgeHeight = badgeWidth * badge.getHeight()/badge.getWidth(); canvas.setLineWidth(20) .setStrokeColorRgb(.9f, .1f, .1f) .moveTo(rectangle.getLeft(), rectangle.getBottom()) .lineTo(rectangle.getRight(), rectangle.getTop()) .moveTo(xCenter + rectangle.getHeight(), yCenter - rectangle.getWidth()) .lineTo(xCenter - rectangle.getHeight(), yCenter + rectangle.getWidth()) .stroke(); sign.setTransparency(new int[] {0, 0}); canvas.addImageFittedIntoRectangle(sign, new Rectangle(0, yCenter, badgeWidth * sign.getWidth() / sign.getHeight() / 2, badgeWidth / 2), false); canvas.concatMatrix(AffineTransform.getRotateInstance( Math.atan2(rectangle.getHeight(), rectangle.getWidth()), xCenter, yCenter)); canvas.addImageFittedIntoRectangle(badge, new Rectangle( xCenter - badgeWidth / 2, yCenter - badgeHeight + badgeWidth / 2, badgeWidth, badgeHeight), false);
ImageData badge = ImageDataFactory.Create(...); ImageData sign = ImageDataFactory.Create(...); PdfFormXObject layer2 = appearance.GetLayer2(); Rectangle rectangle = layer2.GetBBox().ToRectangle(); PdfCanvas canvas = new PdfCanvas(layer2, pdfSigner.GetDocument()); float xCenter = rectangle.GetLeft() + rectangle.GetWidth() / 2; float yCenter = rectangle.GetBottom() + rectangle.GetHeight() / 2; float badgeWidth = rectangle.GetHeight() - 20; float badgeHeight = badgeWidth * badge.GetHeight()/badge.GetWidth(); canvas.SetLineWidth(20) .SetStrokeColorRgb(.9f, .1f, .1f) .MoveTo(rectangle.GetLeft(), rectangle.GetBottom()) .LineTo(rectangle.GetRight(), rectangle.GetTop()) .MoveTo(xCenter + rectangle.GetHeight(), yCenter - rectangle.GetWidth()) .LineTo(xCenter - rectangle.GetHeight(), yCenter + rectangle.GetWidth()) .Stroke(); sign.SetTransparency(new int[] {0, 0}); canvas.AddImageFittedIntoRectangle(sign, new Rectangle(0, yCenter, badgeWidth * sign.GetWidth() / sign.GetHeight() / 2, badgeWidth / 2), false); canvas.ConcatMatrix(AffineTransform.GetRotateInstance( Math.Atan2(rectangle.GetHeight(), rectangle.GetWidth()), xCenter, yCenter)); canvas.AddImageFittedIntoRectangle(badge, new Rectangle( xCenter - badgeWidth / 2, yCenter - badgeHeight + badgeWidth / 2, badgeWidth, badgeHeight), false);

Of course you can also customize both layers. Using both code pieces from above you get:

And using the custom layer 2 in combination with the reuse of the existing appearance of the empty signature field above in layer 0 you get:

Thus, you can give your signature any appearance you want, and with a creative vein you can even make it look good… ;)

Appearance Attributes Used Outside Of The Appearance

The PdfSignatureAppearance class has some properties which are not (or not only) used for creating the visual appearance of the signature in the document but instead (or also) are stored in a machine-readable manner in the signature field value.

  • Contact Information

The value of the signature appearance Contact property is stored in the ContactInfo entry of the signature field value. This entry is specified as the Information provided by the signer to enable a recipient to contact the signer to verify the signature. A phone number is given as an example.

String oldValue = appearance.getContact(); appearance.setContact(newValue);
String oldValue = appearance.GetContact(); appearance.SetContact(newValue);
  • Location

The value of the signature appearance Location property is used in the visual appearance Description and is stored in the Location entry of the signature field value. This entry is specified as The CPU host name or physical location of the signing.

String oldValue = appearance.getLocation(); appearance.setLocation(newValue);
String oldValue = appearance.GetLocation(); appearance.SetLocation(newValue);
  • Reason

The value of the signature appearance Reason property is used in the visual appearance Description and is stored in the Reason entry of the signature field value. This entry is specified as The reason for the signing, such as ( I agree…).

String oldValue = appearance.getReason(); appearance.setReason(newValue);
String oldValue = appearance.GetReason(); appearance.SetReason(newValue);
  • Signature Creator

The value of the signature appearance SignatureCreator property is stored in the Name entry of the App dictionary in the Prop_Build dictionary in the signature field value. This entry is specified as The name of the software module used to create the signature.

String oldValue = appearance.getSignatureCreator(); appearance.setSignatureCreator(newValue);
String oldValue = appearance.GetSignatureCreator(); appearance.SetSignatureCreator(newValue);

Setting all four values you can find them in Adobe Reader in the signature panel,

in the signature properties,

and in the advanced signature properties

In Foxit Reader you find them in the signature properties

and the advanced signature properties