Appearance on Several Pages
To display a signature appearance on all pages of a PDF document is a common request. But the PDF specification (at least in PDF 2.0 - before this was only documented in the "Digital Signature Appearance specifications" of Adobe) officially states that:
The location of a signature within a document can have a bearing on its legal meaning. For this reason, signature fields shall never refer to more than one annotation.
Because of this and according to viewer implementations the SetaPDF-Signer component does not support severals widget annotations for a single signature field.
This demo simply makes use of individual stamp annotations which are added to all other pages when the signature appearance is created.
It uses a proxy class that wraps the original appearance instance and adds the stamp annotations.
PLEASE NOTE: THIS DEMO ONLY WORKS FOR DOCUMENTS WHERE ALL PAGES HAVE THE SAME ROTATION.
<?php
use setasign\SetaPDF2\Demos\Signer\Appearance\OnAllPages as AppearanceOnAllPages;
use setasign\SetaPDF2\Core\Document;
use setasign\SetaPDF2\Core\Font\Type0\Subset;
use setasign\SetaPDF2\Core\Writer\HttpWriter;
use setasign\SetaPDF2\Signer\Signature\Appearance\Dynamic;
use setasign\SetaPDF2\Signer\Signature\Module\Pades as PadesModule;
use setasign\SetaPDF2\Signer\SignatureField;
use setasign\SetaPDF2\Signer\Signer;
// load and register the autoload function
require_once __DIR__ . '/../../../../../bootstrap.php';
// load the wrapper class
require_once __DIR__ . '/../../../../../classes/Signer/Appearance/OnAllPages.php';
$writer = new HttpWriter('several-appearances.pdf', true);
$document = Document::loadByFilename(
$assetsDirectory . '/pdfs/Brand-Guide.pdf',
// $assetsDirectory . '/pdfs/misc/rotated/all.pdf',
$writer
);
// create a signer instance
$signer = new Signer($document);
// add a visible signature field
$field = $signer->addSignatureField(
SignatureField::DEFAULT_FIELD_NAME,
1,
SignatureField::POSITION_RIGHT_BOTTOM,
['x' => -10, 'y' => 10],
180,
60
);
// and define that you want to use this field
$signer->setSignatureFieldName($field->getQualifiedName());
// The name property is used by the appearance module as the author of the stamp annotation
$signer->setName('www.setasign.com');
$certificatePath = $assetsDirectory . '/certificates/setapdf-no-pw.pem';
// now create a signature module
$module = new PadesModule();
// pass the path to the certificate
$module->setCertificate('file://' . $certificatePath);
// set the path to the private key (in this demo the key is also saved in the certificate file)
$module->setPrivateKey('file://' . $certificatePath, '');
// now create the appearance module and pass the signature module along
$appearance = new Dynamic($module);
// let's create a font instance to not use standard fonts (not embedded)
$font = new Subset(
$document,
$assetsDirectory . '/fonts/DejaVu/ttf/DejaVuSans.ttf'
);
// and pass it to the appearance module
$appearance->setFont($font);
// pass the appearance module wrapped into the proxy class to the signer instance
$signer->setAppearance(new AppearanceOnAllPages($appearance));
// sign the document
$signer->sign($module);
<?php
namespace setasign\SetaPDF2\Demos\Signer\Appearance;
use setasign\SetaPDF2\Core\Document;
use setasign\SetaPDF2\Core\Document\Page\Annotation\StampAnnotation;
use setasign\SetaPDF2\Core\Encoding\Encoding;
use setasign\SetaPDF2\Core\XObject\Form;
use setasign\SetaPDF2\Signer\Signature\Appearance\AbstractAppearance;
use setasign\SetaPDF2\Signer\Signer;
use setasign\SetaPDF2\Signer\SignatureField;
class OnAllPages extends AbstractAppearance
{
/**
* @var Form
*/
protected $_formXObject;
/**
* @var AbstractAppearance
*/
protected $_mainAppearance;
/**
* @param AbstractAppearance $mainAppearance
*/
public function __construct(AbstractAppearance $mainAppearance)
{
$this->_mainAppearance = $mainAppearance;
}
/**
* Proxy method to the main appearance instance.
*
* Internally it adds stamp annotations to all other pages on the same position with the same appearance.
*
* @param SignatureField $field
* @param Document $document
* @param Signer $signer
*/
public function createAppearance(
SignatureField $field,
Document $document,
Signer $signer
) {
$this->_mainAppearance->createAppearance($field, $document, $signer);
$pages = $document->getCatalog()->getPages();
$pageOfRealSignature = $pages->getPageByAnnotation($field);
for ($pageNo = 1, $pageCount = $pages->count(); $pageNo <= $pageCount; $pageNo++) {
$page = $pages->getPage($pageNo);
if ($page === $pageOfRealSignature) {
continue;
}
$annotation = new StampAnnotation($field->getRect());
$annotation->setName(\uniqid('', true));
$annotation->setModificationDate(new \DateTime());
$annotation->setPrintFlag();
$annotation->setLocked();
$annotation->setLockedContents();
$annotation->setSubject(\sprintf(
'Copy of signature appearance of signature field "%s"',
Encoding::convertPdfString($field->getQualifiedName())
));
$annotation->setTextLabel($signer->getName());
$annotation->setAppearance($this->_getFormXObject($field, $document, $signer));
$page->getAnnotations()->add($annotation);
}
}
/**
* Get a reusable form XObject.
*
* @param SignatureField $field
* @param Document $document
* @param Signer $signer
* @return Form
*/
protected function _getFormXObject(
SignatureField $field,
Document $document,
Signer $signer
) {
if ($this->_formXObject === null) {
$this->_formXObject = $this->_mainAppearance->_getN2XObject($field, $document, $signer);
$matrix = $field->getAppearance()->getIndirectObject()->ensure()->getValue()->getValue('Matrix');
if ($matrix) {
$this->_formXObject->setMatrix($matrix->toPhp(true));
}
}
return $this->_formXObject;
}
/**
* @param SignatureField $field
* @param Document $document
* @param Signer $signer
* @return Form
*/
protected function _getN2XObject(
SignatureField $field,
Document $document,
Signer $signer
) {
return $this->_mainAppearance->_getN2XObject($field, $document, $signer);
}
}
