Skip to main content
Skip table of contents

iText Core: Signature Appearance Improvements

Introduction: 

In our continuous effort to enhance the user experience, we have introduced new features to the Sign module of iText Core 8.0.2. This update delegates the generation of signature field appearances to the layout and rendering engine, making it easier to customize these appearances.  

While iText Core’s previous approach for generating signature fields allowed their appearance to be customized, advanced customization could be complicated. Therefore, we’ve introduced an extension to the Forms module to represent a signature field. Using the new SignatureFieldAppearance you can take advantage of iText’s layout-based forms to aid appearance customization and decoration in complex use cases, and to apply the same custom appearance to multiple signature fields for instance. 

This enhancement significantly improves the usability of the Sign module, allowing for more flexible and versatile signature appearances. The customization possibilities now extend to adding images, modifying Div elements, and much more. Let's dive into two examples to see the practical impact of these improvements. 

Example 1: Customizing Appearance with a Div Element 

The following code demonstrates how to use a custom Div element as a signature appearance: 

Java
JAVA
package iText.example;

import com.itextpdf.forms.form.element.SignatureFieldAppearance;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.DashedBorder;
import com.itextpdf.layout.borders.RoundDotsBorder;
import com.itextpdf.layout.borders.SolidBorder;
import com.itextpdf.layout.element.*;
import com.itextpdf.layout.properties.*;
import com.itextpdf.layout.renderer.FlexContainerRenderer;

import java.io.IOException;
import java.io.InterruptedIOException;

public class SignApearanceExample1 {
    private static String SRC = "src/main/resources/";
    private static String DEST = "target/";

    public static void main(String[] args) throws Exception {
        new SignApearanceExample1().sigFieldWithDivAppearance(DEST + "signedResult.pdf");
    }

    public void sigFieldWithDivAppearance(String dest) throws IOException, InterruptedIOException {
        try (Document document = new Document(new PdfDocument(new PdfWriter(dest)))) {
            Div div = new Div();
            div.add(new Paragraph("Paragraph inside div with red dotted border and pink background")
                    .setBorder(new DashedBorder(ColorConstants.RED, 1)).setBackgroundColor(ColorConstants.PINK));
            Div flexContainer = new Div();
            flexContainer.setProperty(Property.FLEX_WRAP, FlexWrapPropertyValue.WRAP);
            flexContainer.setProperty(Property.FLEX_DIRECTION, FlexDirectionPropertyValue.ROW_REVERSE);
            flexContainer.setProperty(Property.ALIGN_ITEMS, AlignmentPropertyValue.CENTER);
            flexContainer.add(new Image(ImageDataFactory.create(SRC + "image.png")).scale(0.1f, 0.3f)
                    .setPadding(10)).add(new List()
                    .add(new ListItem("Flex container with").setListSymbol(ListNumberingType.ZAPF_DINGBATS_1))
                    .add(new ListItem(" image and list,").setListSymbol(ListNumberingType.ZAPF_DINGBATS_2))
                    .add(new ListItem(" wrap row-reverse,").setListSymbol(ListNumberingType.ZAPF_DINGBATS_3))
                    .add(new ListItem(" green dots border,").setListSymbol(ListNumberingType.ZAPF_DINGBATS_4))
                    .setPadding(10)).setBorder(new RoundDotsBorder(ColorConstants.GREEN, 10));
            flexContainer.setNextRenderer(new FlexContainerRenderer(flexContainer));
            div.add(flexContainer);

            SignatureFieldAppearance appearance = new SignatureFieldAppearance("form SigField");
            appearance.setContent(div)
                    .setFontColor(ColorConstants.WHITE).setFontSize(10)
                    .setBackgroundColor(ColorConstants.DARK_GRAY)
                    .setBorder(new SolidBorder(ColorConstants.MAGENTA, 2))
                    .setInteractive(true);
            document.add(appearance);
        }
    }
}
C#
C#
using iText.Forms.Form.Element;
using iText.IO.Image;
using iText.Kernel.Colors;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using iText.Layout.Renderer;

namespace ConsoleApp1;

public class SignApearanceExample1
{
    private const string SourceFolder = "/SRC/";
    public static void Main(string[] args)
     {
        new SignApearanceExample1().SigFieldWithDivAppearance( "signedResult.pdf");
     }
    public virtual void SigFieldWithDivAppearance(string outPdf)
     {
         using var document = new Document(new PdfDocument(new PdfWriter(outPdf)));
         var div = new Div();
         div.Add(new Paragraph("Paragraph inside div with red dashed border and pink background")
             .SetBorder(new DashedBorder(ColorConstants.RED, 1)).SetBackgroundColor(ColorConstants.PINK));
         var flexContainer = new Div();
         flexContainer.SetProperty(Property.FLEX_WRAP, FlexWrapPropertyValue.WRAP);
         flexContainer.SetProperty(Property.FLEX_DIRECTION, FlexDirectionPropertyValue.ROW_REVERSE);
         flexContainer.SetProperty(Property.ALIGN_ITEMS, AlignmentPropertyValue.CENTER);
         flexContainer.Add(new Image(ImageDataFactory.Create(SourceFolder + "image.png")).Scale(0.1f, 0.3f)
                 .SetPadding(10)).Add(new List()
             .Add(new ListItem("Flex container with").SetListSymbol(ListNumberingType.ZAPF_DINGBATS_1))
             .Add(new ListItem("image and list,").SetListSymbol(ListNumberingType.ZAPF_DINGBATS_2))
             .Add(new ListItem("wrap, row-reverse,").SetListSymbol(ListNumberingType.ZAPF_DINGBATS_3))
             .Add(new ListItem("green dots border").SetListSymbol(ListNumberingType.ZAPF_DINGBATS_4))
             .SetPadding(10)).SetBorder(new RoundDotsBorder(ColorConstants.GREEN, 10));
         flexContainer.SetNextRenderer(new FlexContainerRenderer(flexContainer));
         div.Add(flexContainer);
         
         var appearance = new SignatureFieldAppearance("form SigField");
         appearance.SetContent(div)
             .SetFontColor(ColorConstants.WHITE).SetFontSize(10)
             .SetBackgroundColor(ColorConstants.DARK_GRAY)
             .SetBorder(new SolidBorder(ColorConstants.MAGENTA, 2))
             .SetInteractive(true);
         document.Add(appearance);
     }
     
}

This example showcases the creation of a custom appearance using a Div element. You can modify the appearance by adding paragraphs, images, and applying various styles, resulting in a unique signature field. 

Example 2: Customizing Appearance with an Image 

The following code illustrates how to set an image as a signature appearance: 

Java
JAVA
package iText.example;

import com.itextpdf.forms.form.element.SignatureFieldAppearance;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.signatures.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Enumeration;

public class SignApearanceExample2 {
    private static final String CERT_PATH = "YOUR_CERTIFICATE_PATH";
    private static String SRC = "src/main/resources/";
    private static String DEST = "target/";
    public static void main(String[] args) throws Exception {
       signWithImageAppearance(SRC + "SignedResult.pdf", DEST + "result_Signed_IMG_Appearance.pdf");
    }

    private static void signWithImageAppearance(String src, String dest) throws Exception {

        PdfSigner pdfSigner = new PdfSigner(new PdfReader(src), new FileOutputStream(dest), new StampingProperties());
        pdfSigner.setCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);

        //Set the name indicating the field to be signed.
        //The field can already be present in the document but most not be signed.
        pdfSigner.setFieldName("signature");
        ImageData clientSignatureImage = ImageDataFactory.create(SRC + "image.png");

        SignatureFieldAppearance signatureAppearance = new SignatureFieldAppearance(pdfSigner.getFieldName())
                .setContent(clientSignatureImage);
        pdfSigner.setPageNumber(1)
                .setPageRect(new Rectangle(100, 100, 25, 15))
                .setSignatureAppearance(signatureAppearance);

        char[] password = "password".toCharArray();
        IExternalSignature pks = getPrivateKeySignature(CERT_PATH, password);
        Certificate[] chain = getCertificateChain(CERT_PATH, password);
        OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
        OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(ocspVerifier);
        java.util.List<ICrlClient> crlClients = Arrays.asList(new CrlClientOnline());

        // Sign the document using the detached mode, CMS or CAdES equivalent.
        pdfSigner.signDetached(new BouncyCastleDigest(), pks, chain, crlClients, ocspClient, null,
                0, PdfSigner.CryptoStandard.CMS);
    }
    private static PrivateKeySignature getPrivateKeySignature(String certificatePath, char[] password) throws Exception {
        PrivateKey pk = null;

        KeyStore p12 = KeyStore.getInstance("pkcs12");
        p12.load(new FileInputStream(certificatePath), password);

        Enumeration<String> aliases = p12.aliases();
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            if (p12.isKeyEntry(alias)) {
                pk = (PrivateKey) p12.getKey(alias, password);
                break;
            }
        }
        Security.addProvider(new BouncyCastleProvider());
        return new PrivateKeySignature(pk, DigestAlgorithms.SHA512, BouncyCastleProvider.PROVIDER_NAME);
    }

    private static Certificate[] getCertificateChain(String certificatePath, char[] password) throws Exception {
        Certificate[] certChain = null;

        KeyStore p12 = KeyStore.getInstance("pkcs12");
        p12.load(new FileInputStream(certificatePath), password);

        Enumeration<String> aliases = p12.aliases();
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            if (p12.isKeyEntry(alias)) {
                certChain = p12.getCertificateChain(alias);
                break;
            }
        }
        return certChain;
    }
}
C#
C#
using iText.Bouncycastle.Crypto;
using iText.Bouncycastle.X509;
using iText.Commons.Bouncycastle.Cert;
using iText.Commons.Bouncycastle.Crypto;
using iText.Forms.Form.Element;
using iText.IO.Image;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Signatures;
using Org.BouncyCastle.Pkcs;

namespace example
{
    public class SignApearanceExample2
    {
        private const string CertPath = "YOUR_CERTIFICATE_PATH";

        public static void Main(string[] args)
        {
            SignWithImageAppearance("SignedResult.pdf", "result_Signed_IMG_Appearance.pdf");
        }

        private static void SignWithImageAppearance(string src, string dest)
        {
            var pdfSigner = new PdfSigner(new PdfReader(src), new FileStream(dest, FileMode.Create),
                new StampingProperties());
            pdfSigner.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);

            //Set the name indicating the field to be signed. 
            //The field can already be present in the document but most not be signed. 
            pdfSigner.SetFieldName("signature");

            var clientSignatureImage = ImageDataFactory.Create("image.png");

            var signatureAppearance = new SignatureFieldAppearance(pdfSigner.GetFieldName())
                .SetContent(clientSignatureImage);
            pdfSigner.SetPageNumber(1)
                .SetPageRect(new Rectangle(100, 100,
                    25, 15))
                .SetSignatureAppearance(signatureAppearance);

            var password = "password".ToCharArray();
            IExternalSignature pks = GetPrivateKeySignature(CertPath, password);
            var chain = GetCertificateChain(CertPath, password);
            var ocspVerifier = new OCSPVerifier(null, null);
            var ocspClient = new OcspClientBouncyCastle(ocspVerifier);
            var crlClients = new List<ICrlClient>(new[] { new CrlClientOnline() });

            // Sign the document using the detached mode, CMS or CAdES equivalent. 
            pdfSigner.SignDetached(pks, chain, crlClients, ocspClient, null, 0,
                PdfSigner.CryptoStandard.CMS);
        }

        private static PrivateKeySignature GetPrivateKeySignature(string certificatePath, char[] password)
        {
            string? alias = null;
            var pk12 = new Pkcs12StoreBuilder().Build();
            pk12.Load(new FileStream(certificatePath, FileMode.Open, FileAccess.Read), password);

            foreach (var a in pk12.Aliases)
            {
                alias = ((string)a);
                if (pk12.IsKeyEntry(alias))
                {
                    break;
                }
            }

            IPrivateKey pk = new PrivateKeyBC(pk12.GetKey(alias).Key);
            return new PrivateKeySignature(pk, DigestAlgorithms.SHA512);
        }

        private static IX509Certificate[] GetCertificateChain(string certificatePath, char[] password)
        {
            string? alias = null;
            var pk12 = new Pkcs12StoreBuilder().Build();
            pk12.Load(new FileStream(certificatePath, FileMode.Open, FileAccess.Read), password);

            foreach (var a in pk12.Aliases)
            {
                alias = ((string)a);
                if (pk12.IsKeyEntry(alias))
                {
                    break;
                }
            }

            X509CertificateEntry[] ce = pk12.GetCertificateChain(alias);
            var chain = new IX509Certificate[ce.Length];
            for (var k = 0; k < ce.Length; ++k)
            {
                chain[k] = new X509CertificateBC(ce[k].Certificate);
            }

            return chain;
        }
    }
}

This example sets an image as the signature appearance. It specifies the signature field's properties, including its position, size, and image source, offering flexibility in creating visually appealing signatures. 

Conclusion: 

With these enhancements, you have the freedom to customize signature appearances in diverse ways. Whether it's adding a Div element for a unique design or incorporating an image for a personal touch, iText Core 8.0.2 is now more suitable for creating a signature field appearance that is tailored to your needs. 

Resources:

signedResult.pdf

result_Signed_IMG_Appearance.pdf

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.