Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
MaximilianKresse committed Apr 5, 2024
2 parents 95c916d + 4725e95 commit 3404e21
Show file tree
Hide file tree
Showing 49 changed files with 569 additions and 78 deletions.
Binary file added assets/pdfs/crumpled-paper.pdf
Binary file not shown.
Binary file added assets/pdfs/misc/4-rects-signed-and-locked.pdf
Binary file not shown.
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "Add a Free Text Annotation to a Signed PDF",
"teaserText": "A complete example of how to add a free text annotation to a digital signed PDF document.",
"faIcon": "",
"faIcon2": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p>
A complete example of how to add a free text annotation to a digital signed PDF document.
</p>
<p>
In this demo we also create the appearance of the free text annotation and calculate the correct coordinates
based on the rotation and boundary box values of the page.
</p>
<p>
Furthermore, we add some normally optional values to the annotation dictionary which are required by Acrobat if
the document is e.g. digital signed afterward.
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

use \SetaPDF_Core_Document_Page_Annotation_FreeText as FreeTextAnnotation;

// load and register the autoload function
require_once '../../../../../bootstrap.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_Http('signed+free-text-annotation.pdf', true);
$document = \SetaPDF_Core_Document::loadByFilename(
$assetsDirectory . '/pdfs/tektown/Laboratory-Report-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();
Original file line number Diff line number Diff line change
Expand Up @@ -24,81 +24,66 @@
)->toXObject($document)
];

// let's find all place holders
// let's find all placeholders
$matches = [];
for ($pageNo = 1; $pageNo <= $pages->count(); $pageNo++) {
/**
* @var \SetaPDF_Extractor_Result_Word[] $words
* @var \SetaPDF_Extractor_Result_Words $words
*/
$words = $extractor->getResultByPageNumber($pageNo);
$matches[] = [$pageNo, $words->search('/{{.*?}}/')];
}

// let's iterate over all words and search for '{{', followed by a anything and followed by '}}'
$segments = null;
foreach ($words AS $word) {
$string = $word->getString();
if ($string === '{{') {
$segments = new \SetaPDF_Extractor_Result_Collection([$word]);
// iterate over the matches
foreach ($matches AS list($pageNo, $results)) {
/** @var \SetaPDF_Extractor_Result_Words $segments */
foreach ($results as $segments) {
$name = trim($segments->getString(), "{} \n");
if (!isset($images[$name])) {
continue;
}

if ($segments === null)
continue;

$segments[] = $word;

if ($string === '}}') {
$matches[] = [$pageNo, $segments];
$segments = null;
// get the bounds of the found phrase
$bounds = $segments->getBounds();
$rect = $bounds[0]->getRectangle();

// get the page object
$page = $pages->getPage($pageNo);
// make sure that the new content is encapsulated in a seperate content stream
$page->getContents()->encapsulateExistingContentInGraphicState();
// get the canvas object
$canvas = $page->getCanvas();

// get some rect data
$x = $rect->getLl()->getX();
$y = $rect->getLl()->getY();
$width = $rect->getWidth();
$height = $rect->getHeight();

// draw a white rectangle
$canvas->draw()
->setNonStrokingColor(1)
->rect($x, $y, $width, $height, \SetaPDF_Core_Canvas_Draw::STYLE_FILL);

/**
* @var \SetaPDF_Core_XObject_Image $image
*/
$image = $images[$name];

// draw the image fitted and centered to the placeholder area
$maxWidth = $image->getWidth($height);
$maxHeight = $image->getHeight($width);

if ($maxHeight > $height) {
$x += $width / 2 - $maxWidth / 2;
$image->draw($canvas, $x, $y, null, $height);
} else {
$y += $height / 2 - $maxHeight / 2;
$image->draw($canvas, $x, $y, $width, null);
}
}
}

// iterate over the matches
foreach ($matches AS $match) {
/** @var \SetaPDF_Extractor_Result_Collection $segments */
$segments = $match[1];

$name = '';
foreach ($segments AS $segment) {
$name .= $segment->getString();
}

$name = trim($name, '{}');

if (!isset($images[$name])) {
continue;
}

// get the bounds of all 3 words
$bounds = $segments->getBounds();
$rect = $bounds[0]->getRectangle();

// get the page object
$page = $pages->getPage($match[0]);
// make sure that the new content is encapsulated in a seperate content stream
$page->getContents()->encapsulateExistingContentInGraphicState();
// get the canvas object
$canvas = $page->getCanvas();

// get some rect data
$x = $rect->getLl()->getX();
$y = $rect->getLl()->getY();
$width = $rect->getWidth();
$height = $rect->getHeight();

// draw a white rectangle
$canvas->draw()
->setNonStrokingColor(1)
->rect($x, $y, $width, $height, \SetaPDF_Core_Canvas_Draw::STYLE_FILL);

/**
* @var \SetaPDF_Core_XObject_Image $image
*/
$image = $images[$name];
// draw the image onto the canvas
$image->draw($canvas, $x, $y, $width, $height);
}

// save and finish the document
$document->setWriter(new \SetaPDF_Core_Writer_Http('document.pdf', true));
$document->save()->finish();
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "Add Text Above Found String",
"teaserText": "This demo finds a string/number, adds a white rectangle and text surrounded by a red border above it.",
"requires": [
"SetaPDF-Core", "SetaPDF-Extractor"
],
"previewFiles": [
"/assets/pdfs/tektown/Laboratory-Report.pdf"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>
This demo finds a string/number (labeled as the "Task Number" in the document), adds a white rectangle and text
surrounded by a red border above it.
</p>
Loading

0 comments on commit 3404e21

Please sign in to comment.