Introduction

A digital signature in a PDF document can be created with an invisible or visible signature field.

The SetaPDF-Signer component comes with simple modules that may create the signature appearance for a visible signature field for you or allow you to define your own appearance.

A module instance has to be passed to the SetaPDF_Signer::setAppearance() method. 

In version 2.0 an appearance module was a proxy module for a signature module and also implements the SetaPDF_Signer_Signature_Module_ModuleInterface interface.
Since version 2.1 the modules are separated and a refactor is needed! 

Dynamic Appearance

The SetaPDF-Signer comes with an appearance module that will create a signature appearance simliar to the appearance that can be created with e.g. Acrobat.

The module allows you to define a background, a graphic and it will automatically extract information from the signature certificate. Its simplest output will look like: 

PHP
<?php
require_once('library/SetaPDF/Autoload.php');

// create a writer
$writer = new \SetaPDF_Core_Writer_Http('simple.pdf', true);
// create a new document instance
$document = \SetaPDF_Core_Document::loadByFilename(
    'files/pdfs/tektown/Laboratory-Report.pdf', $writer
);

// create a signer instance
$signer = new \SetaPDF_Signer($document);

// set some signature properties
$signer->setReason('Testing');
$signer->setLocation('SetaPDF-Signer Manual');

// create a signature module
$module = new \SetaPDF_Signer_Signature_Module_Cms();
// load the certificate
$certificate = 'file://files/certificates/setapdf-no-pw.pem';
$module->setCertificate($certificate);
$module->setPrivateKey(array($certificate, '' /* no password */));

// create a visible signature field
$signer->addSignatureField(
    \SetaPDF_Signer_SignatureField::DEFAULT_FIELD_NAME,
    1,
    \SetaPDF_Signer_SignatureField::POSITION_RIGHT_TOP,
    array('x' => -160, 'y' => -100),
    180,
    60
);

// create the appearance
$appearance = new \SetaPDF_Signer_Signature_Appearance_Dynamic($certificate);

// create a subset font
$font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans.ttf');
$appearance->setFont($font);

// define the appearance
$signer->setAppearance($appearance);

// sign the document and send the final document to the initial writer
$signer->sign($module);

Configure the Background

An appearance background can be defined by passing a XObject (Image or Form XObject) to the setBackgroundLogo() method. The second parameter allows you to define an opacity value (between 0 and 1): 

PHP
<?php
require_once('library/SetaPDF/Autoload.php');

// create a writer
$writer = new \SetaPDF_Core_Writer_Http('simple.pdf', true);
// create a new document instance
$document = \SetaPDF_Core_Document::loadByFilename(
    'files/pdfs/tektown/Laboratory-Report.pdf', $writer
);

// create a signer instance
$signer = new \SetaPDF_Signer($document);

// set some signature properties
$signer->setReason('Testing');
$signer->setLocation('SetaPDF-Signer Manual');

// create a signature module
$module = new \SetaPDF_Signer_Signature_Module_Cms();
// load the certificate
$certificate = 'file://files/certificates/setapdf-no-pw.pem';
$module->setCertificate($certificate);
$module->setPrivateKey(array($certificate, '' /* no password */));

// create a visible signature field
$signer->addSignatureField(
    \SetaPDF_Signer_SignatureField::DEFAULT_FIELD_NAME,
    1,
    \SetaPDF_Signer_SignatureField::POSITION_RIGHT_TOP,
    array('x' => -160, 'y' => -100),
    180,
    60
);

// load a PDF for the background appearance
$bgDocument = \SetaPDF_Core_Document::loadByFilename('files/pdfs/tektown/Logo.pdf');
// convert the first page to a XObject
$xObject = $bgDocument
    ->getCatalog()
    ->getPages()
    ->getPage(1)
    ->toXObject($document, \SetaPDF_Core_PageBoundaries::ART_BOX);

// create the appearance
$appearance = new \SetaPDF_Signer_Signature_Appearance_Dynamic($certificate);
$appearance->setBackgroundLogo($xObject, .3);

// create a subset font
$font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans.ttf');
$appearance->setFont($font);

// define the appearance
$signer->setAppearance($appearance);

// sign the document and send the final document to the initial writer
$signer->sign($module);

Configure the Graphic

The graphic is the text or graphic that should be displayed in the left row of the appearance. A boolean value defines if the graphic will be shown or not. If you pass an XObject to its setter method the XObject will be displayed: 

PHP
<?php
require_once('library/SetaPDF/Autoload.php');

// create a writer
$writer = new \SetaPDF_Core_Writer_Http('simple.pdf', true);
// create a new document instance
$document = \SetaPDF_Core_Document::loadByFilename(
    'files/pdfs/tektown/Laboratory-Report.pdf', $writer
);

// create a signer instance
$signer = new \SetaPDF_Signer($document);

// set some signature properties
$signer->setReason('Testing');
$signer->setLocation('SetaPDF-Signer Manual');

// create a signature module
$module = new \SetaPDF_Signer_Signature_Module_Cms();
// load the certificate
$certificate = 'file://files/certificates/setapdf-no-pw.pem';
$module->setCertificate($certificate);
$module->setPrivateKey(array($certificate, '' /* no password */));

// create a visible signature field
$signer->addSignatureField(
    \SetaPDF_Signer_SignatureField::DEFAULT_FIELD_NAME,
    1,
    \SetaPDF_Signer_SignatureField::POSITION_RIGHT_TOP,
    array('x' => -160, 'y' => -100),
    180,
    60
);

// load a PDF for the graphic appearance
$graphicDocument = \SetaPDF_Core_Document::loadByFilename('files/pdfs/misc/Handwritten-Signature.pdf');
// convert the first page to a XObject
$xObject = $graphicDocument
    ->getCatalog()
    ->getPages()
    ->getPage(1)
    ->toXObject($document, \SetaPDF_Core_PageBoundaries::ART_BOX);

// create the appearance
$appearance = new \SetaPDF_Signer_Signature_Appearance_Dynamic($certificate);
$appearance->setGraphic($xObject);

// create a subset font
$font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans.ttf');
$appearance->setFont($font);

// define the appearance
$signer->setAppearance($appearance);

// sign the document and send the final document to the initial writer
$signer->sign($module);

Configure Labels and Certificate Data

The right column displays data that was extracted from the certificate that was passed to the signature module. The configuration of this text pieces can be controlled by the setShow() and setShowTpl() methods. Their names are represented via class constants (CONFIG_XXX).

By default the appearance class uses the standard font SetaPDF_Core_Font_Standard_Helvetica. It is also possible to use an embedded TrueType subset font which allows you to use characters which are not available in the standard encoding (WinAnsi) used by SetaPDF_Core_Font_Standard_Helvetica.

Following example shows how to format the labels, hide a value and format the date value individually while using a TrueType font: 

PHP
<?php
require_once('library/SetaPDF/Autoload.php');

// create a writer
$writer = new \SetaPDF_Core_Writer_Http('simple.pdf', true);
// create a new document instance
$document = \SetaPDF_Core_Document::loadByFilename(
    'files/pdfs/tektown/Laboratory-Report.pdf', $writer
);

// create a signer instance
$signer = new \SetaPDF_Signer($document);

// set some signature properties
$signer->setReason('Testing');
$signer->setLocation('SetaPDF-Signer Manual');

// create a signature module
$module = new \SetaPDF_Signer_Signature_Module_Cms();
// load the certificate
$certificate = 'file://files/certificates/setapdf-no-pw.pem';
$module->setCertificate($certificate);
$module->setPrivateKey(array($certificate, '' /* no password */));

// create a visible signature field
$signer->addSignatureField(
    \SetaPDF_Signer_SignatureField::DEFAULT_FIELD_NAME,
    1,
    \SetaPDF_Signer_SignatureField::POSITION_RIGHT_TOP,
    array('x' => -160, 'y' => -100),
    180,
    60
);

// create the appearance
$appearance = new \SetaPDF_Signer_Signature_Appearance_Dynamic($certificate);

// let's use a TrueType font subset
$font = new \SetaPDF_Core_Font_TrueType_Subset(
    $document,
    'files/fonts/DejaVuSans.ttf'
);
// and add it
$appearance->setFont($font);

// disable the distinguished name
$appearance->setShow(
    \SetaPDF_Signer_Signature_Appearance_Dynamic::CONFIG_DISTINGUISHED_NAME, false
);

// Translate the labels to german:
$appearance->setShowTpl(
    \SetaPDF_Signer_Signature_Appearance_Dynamic::CONFIG_REASON, 'Grund: %s'
);

$appearance->setShowTpl(
    \SetaPDF_Signer_Signature_Appearance_Dynamic::CONFIG_LOCATION, 'Ort: %s'
);

$appearance->setShowTpl(
    \SetaPDF_Signer_Signature_Appearance_Dynamic::CONFIG_DATE, 'Datum: %s'
);

$appearance->setShowTpl(
    \SetaPDF_Signer_Signature_Appearance_Dynamic::CONFIG_NAME,
    'Digital signiert durch: %s'
);

// format the date
$appearance->setShowFormat(
    \SetaPDF_Signer_Signature_Appearance_Dynamic::CONFIG_DATE, 'd.m.Y H:i:s'
);

// define the appearance
$signer->setAppearance($appearance);

// sign the document and send the final document to the initial writer
$signer->sign($module);

XObject Appearance

The class SetaPDF_Signer_Signature_Appearance_XObject representing a signature appearance based on an existing XObject.

An XObject could be an image, an extracted page or a fresh unique instance of a Form XObject.

To create an appearance of an existing image, you just have to pass the image instance to the constructor of the module: 

PHP
...

// load the image
$image = \SetaPDF_Core_Image::getByPath('files/pdfs/tektown/Logo.png');
// get an XObject instance of it
$xObject = $image->toXObject($document);
// pass it to the appearance modules constructor
$appearance = new \SetaPDF_Signer_Signature_Appearance_XObject($xObject);

// pass the appearance
$signer->setAppearance($appearance);

// pass the module to the sign method
$signer->sign($module);

An XObject can also be created from a page of an existing document: 

PHP
...

// load a document
$appDocument = \SetaPDF_Core_Document::loadByFilename('files/pdfs/handwritten-signature.pdf');

$pageXObject = $appDocument
    ->getCatalog()
    ->getPages()
    ->getPage(1)->toXObject($document, \SetaPDF_Core_PageBoundaries::ART_BOX);

// create a static visible appearance from the xObject
$appearance = new \SetaPDF_Signer_Signature_Appearance_XObject($pageXObject);

// pass the appearance
$signer->setAppearance($appearance);

// pass the module to the sign method
$signer->sign($module);

At the end it is also possible to create an XObject instance manually and create the appearance by its Canvas instance.

The XObject will placed centered with the maximum height or width possible into the available annotation rectangle while aspect ratio is kept.

For an additional example please see our demo environment.

Example with a Rich-Text Block

Following example uses a rich-text block for the signature appearance. We extract the subject from the certificate and show it with some rich-text styling. For sure this can be combined with images or other individual drawing operations. At the end you can create the appearance completely individual.

PHP
<?php
require_once('library/SetaPDF/Autoload.php');

// create a writer
$writer = new \SetaPDF_Core_Writer_Http('rich-text-in-xobject.pdf', true);
// create a new document instance
$document = \SetaPDF_Core_Document::loadByFilename(
    'files/pdfs/tektown/Laboratory-Report.pdf', $writer
);

// create a signer instance
$signer = new \SetaPDF_Signer($document);

// set some signature properties
$signer->setReason('Testing');
$signer->setLocation('SetaPDF-Signer Manual');

// create a signature module
$module = new \SetaPDF_Signer_Signature_Module_Cms();
// load the certificate
$certificatePath = 'file://files/certificates/setapdf-no-pw.pem';
$module->setCertificate($certificatePath);
$module->setPrivateKey(array($certificatePath, '' /* no password */));

// create a visible signature field
$field = $signer->addSignatureField(
    \SetaPDF_Signer_SignatureField::DEFAULT_FIELD_NAME,
    1,
    \SetaPDF_Signer_SignatureField::POSITION_CENTER_TOP,
    array('y' => -100),
    180,
    60
);

// creat a X509 certificate instance
$certificate = \SetaPDF_Signer_X509_Certificate::fromFile($certificatePath);

// let's create a rich-text block
$richText = new \SetaPDF_Core_Text_RichTextBlock($document);

// define a font loader callback
$loadedFonts = [];
$fontLoader = static function (\SetaPDF_Core_Document $document, $fontFamily, $fontStyle) use (&$loadedFonts) {
    $cacheKey = $document->getInstanceIdent() . '_' . $fontFamily . '_' . $fontStyle;
    if (!array_key_exists($cacheKey, $loadedFonts)) {
        $font = null;
        if ($fontFamily === 'DejaVuSans' && $fontStyle === '') {
            $font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans.ttf');
        } elseif ($fontFamily === 'DejaVuSans' && $fontStyle === 'B') {
            $font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans-Bold.ttf');
        } elseif ($fontFamily === 'DejaVuSans' && $fontStyle === 'I') {
            $font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans-Oblique.ttf');
        } elseif ($fontFamily === 'DejaVuSans' && $fontStyle === 'BI') {
            $font = new \SetaPDF_Core_Font_Type0_Subset($document, 'files/fonts/DejaVuSans-BoldOblique.ttf');
        }
        $loadedFonts[$cacheKey] = $font;
    }
    return $loadedFonts[$cacheKey];
};

$richText->registerFontLoader($fontLoader);
$richText->setDefaultFontFamily('DejaVuSans');
$richText->setDefaultFontSize(8);
$richText->setAlign(\SetaPDF_Core_Text::ALIGN_CENTER);
$richText->setPadding(3);
$richText->setBorderColor('#3b65b0');
$richText->setBorderWidth(1);
$richText->setBackgroundColor('#ffffff');
$richText->setTextWidth($field->getWidth() - 6);
$subject = [];
foreach ($certificate->getSubjectName(true) as $subjectItem) {
    $item = $subjectItem['name'] . ': ';
    if ($subjectItem['name'] === 'emailAddress') {
        $item .= '<u><b>' . $subjectItem['value'] . '</b></u>';
    } else {
        $item .= '<b>' . $subjectItem['value'] . '</b>';
    }

    $subject[] = $item;
}
$subjectString = implode(', ', $subject);
$richText->setText(<<<HTML
    Signature by {$subjectString}<br/>
    <span style="font-size:4pt;color: #b1b1b1;">Captured by 
    <span style="color:#000000;">tek</span><span style="color:#3b65b0;">town</span>-signer</span>
HTML
);

// create an XObject
$xObject = \SetaPDF_Core_XObject_Form::create($document, [0, 0, $richText->getWidth(), $richText->getHeight()]);
// draw the rich-text block onto the canvas
$richText->draw($xObject->getCanvas(), 0, 0);

// create the appearance instance
$appearance = new \SetaPDF_Signer_Signature_Appearance_XObject($xObject);

// define the appearance
$signer->setAppearance($appearance);

// sign the document and send the final document to the initial writer
$signer->sign($module);

Appearances on Several Pages

The SetaPDF-Signer component does not support multiple appearances of a single signature field.

A simple quote from the "Digital Signature Appearances specifications" (not publicity available anymore) of Adobe (which also landed with a simliar wording in PDF 2.0) will confirm this limitation:

The location of a signature within a document can have a bearing on its legal meaning. For this reason, signature fields never refer to more than one annotation. If more than one location is associated with a signature, the meaning may become ambiguous.[...]

To emulate such behavior it is possible to add stamp annotations to e.g. all pages which share the appearance of the signature field. We prepared a demo for this idea here.