TX Text Control .NET for WPF

Signature Fields and Signature Appearance

Topic

A signature field marks the place in a document where a signature belongs: a frame with a name, an optional image and the data of the person who is expected to sign. When the document is saved as PDF, TX Text Control can sign every named field with its own certificate, and the field's image becomes the visible face of that signature. In a WPF application, the fields live in the document of the TextControl, so your code signs what users prepared in the editor. Signing the fields, giving them an image, generating a signature appearance with name and date and the resulting PDF are covered below.

Inserting signature fields with the ribbon, and editing their layout and signer data, belongs to Signature Fields in Document Editor. Sign a Signature Field in Getting Started inserts and signs a first field in code.

What a Signature Field Holds

SignatureField derives from FrameBase, like images and text frames, and is positioned the same way. Three of its parts matter for signing:

  • The name. Name associates the field with a certificate, so a field without a name can't be signed. ID is an optional number of your own.
  • The image. Image is a SignatureImage, or null while the field has none. It can be set when the document is designed or when it's signed.
  • The signer data. SignerData describes the suggested signer: name, title, address, contact information and the reason for signing, in the order of its constructor. Every value can be null or empty.

The fields of the editor's document are in WPF.TextControl.SignatureFields. How to insert, find and remove them, and which formats keep them, is described in Signature Fields in Document Editor; GetItem returns null when no field matches. Signature lines of MS Word documents are loaded as signature fields with their signer data, as the blog article Importing and Processing Signature Fields from MS Word in C# shows.

The samples below need these using directives in MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows;
using Microsoft.Win32;
using TXTextControl;

Sign the Fields of the Document

For each named field, create a DigitalSignature with the field's name as the third constructor parameter, and pass all of them in SaveSettings.SignatureFields:

private void signFieldsButton_Click(object sender, RoutedEventArgs e)
{
    X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
        "certificate.pfx", "123", X509KeyStorageFlags.Exportable);

    List<DigitalSignature> signatures = new List<DigitalSignature>();

    foreach (SignatureField field in textControl1.SignatureFields)
    {
        // A certificate can only be associated with a named field
        if (string.IsNullOrEmpty(field.Name))
        {
            continue;
        }

        signatures.Add(new DigitalSignature(certificate, "", field.Name));
    }

    if (signatures.Count == 0)
    {
        MessageBox.Show(this, "The document has no named signature field.");
        return;
    }

    SaveSettings saveSettings = new SaveSettings()
    {
        SignatureFields = signatures.ToArray()
    };

    textControl1.Save("signed-fields.pdf", StreamType.AdobePDF, saveSettings);
}

The code works as follows:

  • The loop visits every field of the document in the editor and skips those without a name.
  • The second parameter of each DigitalSignature is the URL of a time server, or an empty string for the computer's date and time; see Timestamp Configuration.
  • One save applies all signatures. Different signers and certificates are covered in Multiple Signatures.

X509CertificateLoader is part of .NET 9 and higher. For .NET 8 and the .NET Framework, see Load the Certificate From a PFX File.

Add a Signature Image

The SignatureImage constructors take an image in three forms:

ConstructorCreates the image from
SignatureImage(string fileName, int filterIndex)An image file. The filter index is its format: 1 BMP, 2 TIF, 3 WMF, 4 PNG, 5 JPG, 6 GIF, 7 EMF or 8 SVG.
SignatureImage(MemoryStream memoryStream)Image data in a stream, such as a generated SVG image
SignatureImage(System.Drawing.Image image)A System.Drawing.Image. WPF images, such as a BitmapSource, aren't accepted; save them to a stream instead.

The following handler lets the user choose an image file for the signature field selected in the editor. It uses the WPF OpenFileDialog of Microsoft.Win32, whose ShowDialog returns a bool?:

private void addSignatureImageButton_Click(object sender, RoutedEventArgs e)
{
    // The signature field that is selected in the editor, or null
    SignatureField field = textControl1.SignatureFields.GetItem();

    if (field == null)
    {
        MessageBox.Show(this, "Select a signature field first.");
        return;
    }

    OpenFileDialog dialog = new OpenFileDialog();
    dialog.Filter = "PNG images (*.png)|*.png|SVG images (*.svg)|*.svg";

    if (dialog.ShowDialog(this) == true)
    {
        // Filter index 4 is PNG, 8 is SVG
        int filterIndex = dialog.FilterIndex == 1 ? 4 : 8;
        field.Image = new SignatureImage(dialog.FileName, filterIndex);
    }
}

null as the value of Image removes the image again. To react when the user clicks or selects a field, for example to offer this dialog box, use the events described in Handle Signature Field Events.

Control How the Signature Image Is Saved

A SignatureImage keeps its origin: FileName is the file and path it was loaded from, and FilterIndex its format, with the numbers of the constructor table above. Five more properties decide how the image is written when the document is saved:

PropertySets
SaveModeWhether the image is stored with its data (SaveAsData), as a reference to its file (SaveAsFileReference) or as SaveSettings.ImageSaveMode specifies (Auto). Without a setting, an image loaded from a file is stored as a file reference, and one from a stream or the clipboard with its data.
ExportFilterIndexThe format the image is saved in, numbered from 1 as in ImageCollection.ExportFilters: 1 BMP, 2 WMF, 3 PNG, 4 JPG, 5 EMF, 6 TIF or 7 GIF. 0 keeps the original format.
ExportFileNameThe file name of the image when the document is saved. An empty string uses the original name or one built from the document's file name, a number and the extension, such as contract_1.png.
ExportCompressionQualityThe quality of a lossy compression, from 1 to 100. 0, the default, uses SaveSettings.ImageCompressionQuality.
ExportMaxResolutionThe maximum resolution in dots per inch; a larger image is reduced. 0, the default, uses SaveSettings.ImageMaxResolution.

The export numbers differ from the import numbers: PNG is 4 for FilterIndex and 3 for ExportFilterIndex. The following lines make the image of the selected field travel with the document as PNG data, at no more than 300 dots per inch:

SignatureField field = textControl1.SignatureFields.GetItem();

if (field?.Image != null)
{
    // Store the image data, not a reference to the original file
    field.Image.SaveMode = ImageSaveMode.SaveAsData;
    field.Image.ExportFilterIndex = 3; // PNG
    field.Image.ExportMaxResolution = 300;
}

Users set the same options in the dialog box of the field. SignatureFieldLayoutDialog opens the layout, size and text distances of the selected signature field, and SignatureFieldLayoutDialog(2) its third tab with the saving attributes of the signature image, which is available only when the field has an image. Built-in Dialog Boxes lists the other dialog boxes.

Create a Signature Appearance

Whatever the image of a field shows is the appearance of its signature. A common choice is a stamp with the signer's name, the reason and the date, possibly next to a handwritten signature. Since SignatureImage reads an SVG image from a stream, your code can generate the stamp from the signer data of the field. The following methods do this for a field named txsign and sign it:

private void SetSignatureStamp(SignatureField field)
{
    SignerData signer = field.SignerData;
    string name = SecurityElement.Escape(signer?.Name ?? "");
    string reason = SecurityElement.Escape(signer?.Reason ?? "");

    // An SVG image with a frame, the signer's name, the reason and the date
    string svg =
        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"300\" height=\"100\">" +
        "<rect x=\"1\" y=\"1\" width=\"298\" height=\"98\" fill=\"none\" stroke=\"#1F4E79\" stroke-width=\"2\"/>" +
        "<text x=\"12\" y=\"30\" font-family=\"Arial\" font-size=\"14\" fill=\"#1F4E79\">Digitally signed by</text>" +
        $"<text x=\"12\" y=\"54\" font-family=\"Arial\" font-size=\"18\" font-weight=\"bold\">{name}</text>" +
        $"<text x=\"12\" y=\"76\" font-family=\"Arial\" font-size=\"12\">{reason}</text>" +
        $"<text x=\"12\" y=\"92\" font-family=\"Arial\" font-size=\"12\">{DateTime.Now:yyyy-MM-dd}</text>" +
        "</svg>";

    byte[] data = Encoding.UTF8.GetBytes(svg);

    using (MemoryStream stream = new MemoryStream(data))
    {
        field.Image = new SignatureImage(stream);
    }
}

private void signWithStampButton_Click(object sender, RoutedEventArgs e)
{
    X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
        "certificate.pfx", "123", X509KeyStorageFlags.Exportable);

    SignatureField field = textControl1.SignatureFields.GetItem("txsign");

    if (field == null)
    {
        MessageBox.Show(this, "The document has no signature field named txsign.");
        return;
    }

    SetSignatureStamp(field);

    SaveSettings saveSettings = new SaveSettings()
    {
        SignatureFields = new DigitalSignature[]
        {
            new DigitalSignature(certificate, "", field.Name)
        }
    };

    textControl1.Save("signed-stamp.pdf", StreamType.AdobePDF, saveSettings);
}

The code works as follows:

  • The name and the reason come from the field's SignerData, which users enter on the ribbon or your code sets as a new SignerData object. SecurityElement.Escape keeps characters such as & and < from breaking the SVG.
  • The stamp is written into a MemoryStream, and the SignatureImage constructor reads it from there.
  • The date in the stamp comes from the computer. With a time server, the trusted signing time is part of the signature itself.

The blog article PDF Digital Signatures: Signature Appearance in C# builds a similar stamp around an existing SVG signature and places the field at a corner of the page. Its result in Adobe Acrobat:

A signed signature field in Adobe Acrobat that shows a handwritten signature next to red text with the signer's name, address and the date.

What the PDF Contains

  • A signed field shows its image. Adobe Acrobat Reader opens its validation status on a click and lists it by field name in the Signatures panel.
  • A field without a DigitalSignature stays an unsigned signature field in the PDF, which a PDF reader can sign later, for example Acrobat Reader with a digital ID.
  • A whole-document signature with SaveSettings.DigitalSignature uses an invisible field instead, as Signing PDF Documents describes.

This is the field txsign from Getting Started after signing, with the validation status open:

Adobe Acrobat showing a signed signature field with a handwritten signature and the validation status of the signature of the field txsign.

Next Steps