Tagged Stamp
This PHP demo uses a proxy stamp class which will enclose the stamps in marked-content sequences and add it to the PDF documents structure tree.
By default the new structure element is appended to the root node of the global tag structure.
To add a stamp as a child of an existing structure element, make sure that it has an ID and
pass this as the $parentId parameter of the constructor.
If no individual tag should be created but the one that is resolved by the $parentId should be used, you
have to set the tag-name to null: $stamp->setTagName(null);
NOTE: Such stamp can only appear on a single page!
You can also define an individual tag name, title, actual text, alternate text and the language.
To demonstrate the usage of this class, we added an empty Span-tag to the document element
structure of the PDF and set its ID to date. The demo than adds a text stamp directly into
this prepared structure element.
<?php use setasign\SetaPDF2\Core\DataStructure\Color\Rgb; use setasign\SetaPDF2\Demos\Stamper\Stamp\Tagged as TaggedStamp; use setasign\SetaPDF2\Core\Font\TrueType\Subset; use setasign\SetaPDF2\Core\Writer\HttpWriter; use setasign\SetaPDF2\Stamper\Stamp\TextStamp; use setasign\SetaPDF2\Stamper\Stamper; // load and register the autoload function require_once __DIR__ . '/../../../../../bootstrap.php'; require_once __DIR__ . '/../../../../../classes/Stamper/Stamp/Tagged.php'; // create an HTTP writer $writer = new HttpWriter('tagged.pdf', true); //$writer = new \setasign\SetaPDF2\Core\Writer\FileWriter('tagged.pdf'); // let's get the document $document = \setasign\SetaPDF2\Core\Document::loadByFilename( $assetsDirectory . '/pdfs/camtown/Terms-and-Conditions - Tagged.pdf', $writer ); // create a stamper instance $stamper = new Stamper($document); //--- Create a text stamp and wrap it in a Tagged stamp instance ---// // create a font instance which is needed for the text stamp instance $font = new Subset( $document, $assetsDirectory . '/fonts/DejaVu/ttf/DejaVuSans.ttf' ); // create a stamp instance $textStamp = new TextStamp($font, 10); // set a text $textStamp->setText(date('Y-m-d H:i:s')); // and its color $textStamp->setTextColor(new Rgb(240/255, 90/255, 40/255)); // create a Tagged stamp instance and pass the text stamp to it, // we also define the parent tag found by the id "date". $stamp = new TaggedStamp($textStamp, 'date'); // we want to add the text content into the existing tag, so let's reset the tag-name $stamp->setTagName(null); $stamp->setActualText($textStamp->getText()); $stamp->setTitle('Creation of this Terms and Conditions'); // add the stamp to the stamper instance $stamper->addStamp($stamp, [ 'showOnPage' => 1, 'position' => Stamper::POSITION_RIGHT_TOP, 'translateX' => -40, 'translateY' => -140, ]); // execute the stamp process $stamper->stamp(); // save and finish the document instance $document->save()->finish();
<?php namespace setasign\SetaPDF2\Demos\Stamper\Stamp; use setasign\SetaPDF2\Core\DataStructure\Tree\NameTree; use setasign\SetaPDF2\Core\DataStructure\Tree\NumberTree; use setasign\SetaPDF2\Core\Document; use setasign\SetaPDF2\Core\Document\Action\Action; use setasign\SetaPDF2\Core\Document\OptionalContent\Group; use setasign\SetaPDF2\Core\Document\Page; use setasign\SetaPDF2\Core\Encoding\Encoding; use setasign\SetaPDF2\Core\Type\Dictionary\DictionaryHelper; use setasign\SetaPDF2\Core\Type\PdfArray; use setasign\SetaPDF2\Core\Type\PdfDictionary; use setasign\SetaPDF2\Core\Type\PdfIndirectReference; use setasign\SetaPDF2\Core\Type\PdfName; use setasign\SetaPDF2\Core\Type\PdfNumeric; use setasign\SetaPDF2\Core\Type\PdfString; use setasign\SetaPDF2\NotImplementedException; use setasign\SetaPDF2\Stamper\Stamp\AbstractStamp; /** * Class Tagged */ class Tagged extends AbstractStamp { /** * @var AbstractStamp */ protected $_mainStamp; protected $_parentId; protected $_tagName = 'Span'; protected $_title = ''; protected $_actualText = ''; protected $_alternateText = ''; protected $_language = ''; protected $_stampedOnPage; /** * The constructor * * @param AbstractStamp $mainStamp The main stamp instance * @param string|null $parentId The ID of the parent structure element. If set to null the new tag * will be added to the root of the structure tree. */ public function __construct(AbstractStamp $mainStamp, ?string $parentId = null) { $this->_mainStamp = $mainStamp; $this->_parentId = $parentId; } /** * @param ?string $tagName */ public function setTagName(?string $tagName) { $this->_tagName = $tagName; } /** * @param string $title Title in UTF-8 */ public function setTitle(string $title) { $this->_title = $title; } /** * @param string $actualText Actual text in UTF-8 */ public function setActualText(string $actualText) { $this->_actualText = $actualText; } /** * @param string $alternateText Alternate text in UTF-8 */ public function setAlternateText(string $alternateText) { $this->_alternateText = $alternateText; } /** * @param string $language Language in UTF-8 */ public function setLanguage(string $language) { $this->_language = $language; } /** * @inheritDoc */ public function stamp(Document $document, Page $page, array $stampData) { $this->_mainStamp->_preStamp($document, $page, $stampData); $this->_stamp($document, $page, $stampData); $quadPoints = $this->_mainStamp->_postStamp($document, $page, $stampData); if ($quadPoints !== null) { $this->_mainStamp->_putAction( $document, $page, $stampData, $quadPoints[0], $quadPoints[1], $quadPoints[2], $quadPoints[3] ); } return true; } /** * @inheritDoc */ protected function _stamp(Document $document, Page $page, array $stampData) { $document->getCatalog()->getMarkInfo()->setMarked(true); $structTreeRoot = $document->getCatalog()->getStructTreeRoot(); $structTreeRoot->getDictionary(true); $pageDict = PdfDictionary::ensureType($page->getObject()); if (!$pageDict->offsetExists('StructParents')) { $pageDict->offsetSet( 'StructParents', new PdfNumeric($structTreeRoot->getAndIncrementParentTreeNextKey()) ); } $structParentsKey = PdfNumeric::ensureType($pageDict->getValue('StructParents'))->getValue(); /** @var NumberTree $parentTree */ $parentTree = $structTreeRoot->getParentTree(true); $parentElements = $parentTree->get($structParentsKey); if ($parentElements !== false) { $parentElements = $parentElements->ensure(); } else { $parentElements = new PdfArray(); $parentTree->add($structParentsKey, $document->createNewObject($parentElements)); } $mcid = \count($parentElements); if ($this->_parentId !== null) { $idTree = $structTreeRoot->getIdTree(); if (!$idTree instanceof NameTree) { throw new \InvalidArgumentException(\sprintf( 'The parentId (%s) cannot be found.', $this->_parentId )); } $parent = $idTree->get($this->_parentId); if (!$parent instanceof PdfIndirectReference) { throw new \InvalidArgumentException(\sprintf( 'The parentId (%s) cannot be found.', $this->_parentId )); } } else { $parent = $structTreeRoot->getObject(); } if ($this->_tagName === null) { if ($this->_parentId === null) { throw new \InvalidArgumentException( 'The tagName can only be left, if a parentId is provided.' ); } $element = PdfDictionary::ensureType($parent); $parentElements[] = $parent; $newKidValue = new PdfNumeric($mcid); } else { $element = new PdfDictionary([ 'K' => new PdfNumeric($mcid), 'P' => $parent, 'S' => new PdfName($this->_tagName, true) ]); $newKidValue = $document->createNewObject($element); $parentElements[] = $newKidValue; } if ($this->_tagName === null) { $pageObjectId = $page->getObject()->getObjectId(); if ($this->_stampedOnPage !== null && $this->_stampedOnPage !== $pageObjectId) { throw new \InvalidArgumentException('A stamp without a tag-name can only be stamped on a single page.'); } $element['Pg'] = $page->getObject(); $this->_stampedOnPage = $pageObjectId; } if ($this->_title !== '') { $element->offsetSet('T', new PdfString( Encoding::toPdfString($this->_title) )); } if ($this->_alternateText !== '') { $element->offsetSet('Alt', new PdfString( Encoding::toPdfString($this->_alternateText) )); } if ($this->_actualText !== '') { $element->offsetSet('ActualText', new PdfString( Encoding::toPdfString($this->_actualText) )); } if ($this->_language !== '') { $element->offsetSet('Lang', new PdfString( Encoding::toPdfString($this->_language) )); } $parentDict = PdfDictionary::ensureType($parent); $k = DictionaryHelper::getValue($parentDict, 'K'); if ($k === null) { $k = new PdfArray(); $parentDict->offsetSet('K', $document->createNewObject($k)); } if (!$k instanceof PdfArray) { $k = new PdfArray([$k]); $parentDict->offsetSet('K', $document->createNewObject($k)); } $k[] = $newKidValue; $canvas = $page->getCanvas(); $properties = new PdfDictionary([ 'MCID' => new PdfNumeric($mcid) ]); $canvas->markedContent()->begin($this->_tagName ?? 'Span', $properties); $this->_mainStamp->_stamp($document, $page, $stampData); $canvas->markedContent()->end(); return true; } /* Proxy all standard methods of the main stamp instance */ /** * @inheritDoc */ public function getHeight() { return $this->_mainStamp->getHeight(); } /** * @inheritDoc */ public function getWidth() { return $this->_mainStamp->getWidth(); } /** * @inheritDoc */ public function setOpacity($alpha, $blendMode = 'Normal') { $this->_mainStamp->setOpacity($alpha, $blendMode); } /** * @inheritDoc */ public function getOpacity() { return $this->_mainStamp->getOpacity(); } /** * @inheritDoc */ public function getOpacityBlendMode() { return $this->_mainStamp->getOpacityBlendMode(); } /** * @inheritDoc */ public function setVisibility($visibility) { $this->_mainStamp->setVisibility($visibility); } /** * @inheritDoc */ public function getVisibility() { $this->_mainStamp->getVisibility(); } /** * @inheritDoc */ public function setAction(Action $action) { throw new NotImplementedException('Actions are actually not implemented for tagged stamps.'); } /** * @inheritDoc */ public function getAction() { return $this->_mainStamp->getAction(); } /** * @inheritDoc */ public function setLink($uri) { throw new NotImplementedException('Links are actually not implemented for tagged stamps.'); } /** * @inheritDoc */ public function setOptionalContentGroup(?Group $optionalContentGroup = null) { $this->_mainStamp->setOptionalContentGroup($optionalContentGroup); } /** * @inheritDoc */ public function getOptionalContentGroup() { return $this->_mainStamp->getOptionalContentGroup(); } /** * @inheritDoc */ protected function _getOpacityGraphicState(Document $document, $opacity) { return $this->_mainStamp->_getOpacityGraphicState($document, $opacity); } /** * @inheritDoc */ protected function _getVisibilityGroup(Document $document) { return $this->_mainStamp->_getVisibilityGroup($document); } /** * @inheritDoc */ protected function _ensureResources(Document $document, Page $page) { return $this->_mainStamp->_ensureResources($document, $page); } }
