skip to Main Content

I’m trying to add a page with an annotation box to an existing signed pdf. This should be possible with TCPDF and FPDF. Unfortunately, it removes the sign and the pdf cannot be validated after being altered. The displayed pdf looks the same, though.

The code (generated by AI) follows:

<?php
use FpdfFpdf;
use setasignFpdiFpdi;

require_once '../lib/fpdf/Fpdf.php';
require_once '../lib/tcpdf/tcpdf.php';
require_once '../lib/fpdi/src/autoload.php';

// require_once '../vendor/autoload.php';

$pdf = new Fpdi();

$pdf->setSourceFile('test-form-signed.pdf');

// Loop through the pages of the original PDF
$pageCount = $pdf->setSourceFile('test-form-signed.pdf');
for ($i = 1; $i <= $pageCount; $i++) {
    $tplId = $pdf->importPage($i);
    $pdf->addPage();
    $pdf->useTemplate($tplId, 0, 0);
}

// Add a blank new page at the end
$pdf->AddPage();

// Set annotation properties
$pdf->SetFont('Helvetica', '', 12);
$pdf->SetTextColor(0, 0, 0);
$pdf->Text(10, 10, "This is a text annotation on a new blank page.");

// Save the output PDF
$pdf->Output('F', 'test-form-annotated-signed.pdf');

The test-form-annotated-signed.pdf is about half the size of the original file and it no longer passes a validation test. Probably, the signature/certificate was removed during the process.

By the way, the pdf is signed with a OCES-3 certificate issued by the State of Denmark.

Please note that solutions that involve using Adobe Acrobat, Signicat etc. are not an option.

2

Answers


  1. Chosen as BEST ANSWER

    This can be achieved by using the paid software package, Setasign-Core. The following demonstrates it (code found in the link provided by @JanSlabom in the comments):

    <?php
    
    use SetaPDF_Core_Document_Page_Annotation_FreeText as FreeTextAnnotation;
    
    // load and register the autoload function
    require_once '../vendor/autoload.php';
    
    // let's define some properties first
    $x = 10;
    $yTop = 10; // we take the upper left as the origin
    
    $borderWidth = 1;
    
    $borderColor = '#FF0000';
    $fillColor = '#00FF00';
    $textColor = '#0000FF';
    
    $text = "Received: " . date('Y-m-d H:i:s');
    $align = SetaPDF_Core_Text::ALIGN_LEFT;
    
    // create a document instance by loading an existing PDF
    $writer = new SetaPDF_Core_Writer_File('test-form-annotated-signed.pdf', true);
    $document = SetaPDF_Core_Document::loadByFilename(
        'test-form-signed.pdf',
        $writer
        );
    
    // we will need a font instance
    $font = SetaPDF_Core_Font_Standard_Helvetica::create($document);
    $fontSize = 12;
    
    // now we create a text block first to know the final size:
    $box = new SetaPDF_Core_Text_Block($font, $fontSize);
    $box->setTextColor($textColor);
    $box->setBorderWidth($borderWidth);
    $box->setBorderColor($borderColor);
    $box->setBackgroundColor($fillColor);
    $box->setAlign($align);
    $box->setText($text);
    $box->setPadding(2);
    
    $width = $box->getWidth();
    $height = $box->getHeight();
    
    // now draw the text block onto a canvas (we add the $borderWidth to show the complete border)
    $appearance = SetaPDF_Core_XObject_Form::create($document, [0, 0, $width + $borderWidth, $height + $borderWidth]);
    $box->draw($appearance->getCanvas(), $borderWidth / 2, $borderWidth / 2);
    
    // now we need a page and calculate the correct coordinates for our annotation
    $page = $document->getCatalog()->getPages()->getPage(1);
    // we need its rotation
    $rotation = $page->getRotation();
    // ...and page boundary box
    $box = $page->getBoundary();
    
    // with this information we create a graphic state
    $pageGs = new SetaPDF_Core_Canvas_GraphicState();
    switch ($rotation) {
        case 90:
            $pageGs->translate($box->getWidth(), 0);
            break;
        case 180:
            $pageGs->translate($box->getWidth(), $box->getHeight());
            break;
        case 270:
            $pageGs->translate(0, $box->getHeight());
            break;
    }
    
    $pageGs->rotate($box->llx, $box->lly, $rotation);
    $pageGs->translate($box->llx, $box->lly);
    
    // ...and a helper function to translate coordinates into vectors by using the page graphic state
    $f = static function($x, $y) use ($pageGs) {
        $v = new SetaPDF_Core_Geometry_Vector($x, $y);
        return $v->multiply($pageGs->getCurrentTransformationMatrix());
    };
    
    // calculate the ordinate
    $y = $page->getHeight() - $height - $yTop;
    
    $ll = $f($x, $y);
    $ur = $f($x + $width + $borderWidth, $y + $height + $borderWidth);
    
    // now we create the annotation object:
    $annotation = new FreeTextAnnotation(
        [$ll->getX(), $ll->getY(), $ur->getX(), $ur->getY()],
        'Helv',
        $fontSize,
        $borderColor
        );
    $annotation->getBorderStyle()->setWidth($borderWidth);
    $annotation->setColor($fillColor);
    $annotation->setTextLabel("John Dow"); // Used as Author in a Reader application
    $annotation->setContents($text);
    $annotation->setName(uniqid('', true));
    $annotation->setModificationDate(new DateTime());
    $annotation->setAppearance($appearance);
    
    // now we need to add some things regarding "variable text" that are required by e.g. Acrobat (if you want to add
    // e.g. a digital signature directly after adding a free-text annotation)
    $dict = $annotation->getDictionary();
    $dict->offsetSet(
        'DS',
        new SetaPDF_Core_Type_String('font: Helvetica, sans-serif ' . sprintf('%.2F', $fontSize) . 'pt;color: ' . $textColor)
        );
    switch ($align) {
        case SetaPDF_Core_Text::ALIGN_CENTER:
            $align = 'center';
            break;
        case SetaPDF_Core_Text::ALIGN_RIGHT:
            $align = 'right';
            break;
        case SetaPDF_Core_Text::ALIGN_JUSTIFY:
            $align = 'justify';
            break;
        default:
            $align = 'left';
    }
    
    $dict->offsetSet('RC', new SetaPDF_Core_Type_String(
        '<?xml version="1.0"?><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" ' .
        'xfa:APIVersion="Acrobat:11.0.23" xfa:spec="2.0.2"  style="font-size:' . $fontSize . 'pt;text-align:' . $align .
        ';color:' . $textColor . ';font-weight:normal;font-style:normal;font-family:Helvetica,sans-serif;font-stretch:normal">' .
        '<p dir="ltr"><span style="font-family:Helvetica">' . htmlentities($annotation->getContents(), ENT_XML1) . '</span></p></body>'
        ));
    
    // lastly add the annotation to the page
    $page->getAnnotations()->add($annotation);
    
    $document->save()->finish();
    

  2. You cannot modify an existing PDF with FPDI.

    With FPDI you import existing pages into a reusable structure (XObjects) which can be used with e.g. FPDF or TCPDF.

    The resulting PDF is a completely new PDF with a completely different internal structure.

    Most PDF viewers allow you to annotate signed PDFs (via incremental updates) but will note these modifications.

    FWIK: It is impossible to add an additional page without invalidating existing the signatures.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search