Replace Images
An example that shows you how to replace images in an existing PDF document.
For demonstration purpose we change the image based on their orientation to a new image.
PHP
<?php use setasign\SetaPDF2\Demos\ContentStreamProcessor\ImageProcessor; use setasign\SetaPDF2\Core\Document; use setasign\SetaPDF2\Core\Image\Image; use setasign\SetaPDF2\Core\Writer\HttpWriter; // load and register the autoload function require_once '../../../../../bootstrap.php'; // prepare some files $files = glob($assetsDirectory . '/pdfs/lenstown/products/*.pdf'); $path = displayFiles($files); require_once $classesDirectory . '/ContentStreamProcessor/ImageProcessor.php'; // load a document instance $document = Document::loadByFilename($path); // get access to the pages object $pages = $document->getCatalog()->getPages(); // define the replacement images $portraitImage = Image::getByPath($assetsDirectory . '/images/portrait.jpg'); $portraitXObject = $portraitImage->toXObject($document); $landscapeImage = Image::getByPath($assetsDirectory . '/images/landscape.jpg'); $landscapeXObject = $landscapeImage->toXObject($document); // walk through the pages for ($pageNo = 1; $pageNo <= $pages->count(); $pageNo++) { $page = $pages->getPage($pageNo); // create an image processor instance $imageProcessor = new ImageProcessor($page->getCanvas(), ($page->getRotation() / 90) % 2 > 0); // process the content stream $images = $imageProcessor->process(); foreach ($images AS $image) { // we've several pieces of information available but for demonstration purpose we just compare // the width and height to define which new image will be used if ($image['width'] > $image['height']) { $image['objectReference']->setValue($landscapeXObject->getIndirectObject()); } else { $image['objectReference']->setValue($portraitXObject->getIndirectObject()); } } } // save and finish $document->setWriter(new HttpWriter('replaced-images.pdf', true)); $document->save()->finish();
PHP
<?php namespace setasign\SetaPDF2\Demos\ContentStreamProcessor; use setasign\SetaPDF2\Core\Canvas\Canvas; use setasign\SetaPDF2\Core\Canvas\GraphicState; use setasign\SetaPDF2\Core\Filter\Exception as FilterException; use setasign\SetaPDF2\Core\Geometry\Vector; use setasign\SetaPDF2\Core\Parser\Content; use setasign\SetaPDF2\Core\Resource\ResourceInterface; use setasign\SetaPDF2\Core\Type\PdfIndirectReference; use setasign\SetaPDF2\Core\XObject\XObject; use setasign\SetaPDF2\Core\XObject\Form; use setasign\SetaPDF2\NotImplementedException; /** * Class ImageProcessor */ class ImageProcessor { /** * The content stream. * * @var string */ protected $_canvas; /** * The graphic state. * * @var GraphicState */ protected $_graphicState; /** * The content parser instance. * * @var Content */ protected $_contentParser; /** * The result data. * * @var array */ protected $_result = []; /** * Switch the width and height values. * * @var bool */ protected $_switchWidthAndHeight = false; /** * @var array All object ids of visited XObjects to prevent circular references */ protected $_xObjectObjectIds = []; /** * The constructor. * * The parameters are the content stream and its resource dictionary. * * @param Canvas $canvas * @param bool $switchWidthAndHeight * @param GraphicState|null $graphicState */ public function __construct( Canvas $canvas, bool $switchWidthAndHeight, ?GraphicState $graphicState = null ) { $this->_canvas = $canvas; $this->_switchWidthAndHeight = $switchWidthAndHeight; $this->_graphicState = $graphicState === null ? new GraphicState() : $graphicState; } /** * Get the graphic state. * * @return GraphicState */ public function getGraphicState(): GraphicState { return $this->_graphicState; } /** * Process the content stream and return the resolved data. * * @return array */ public function process(): array { $parser = $this->_getContentParser(); $parser->process(); return $this->_result; } /** * A method to receive the content parser instance. * * @return Content */ protected function _getContentParser() { if ($this->_contentParser === null) { try { $stream = $this->_canvas->getStream(); } catch (FilterException $e) { // if a stream cannot be unfiltered, we ignore it $stream = ''; } $this->_contentParser = new Content($stream); $this->_contentParser->registerOperator(['q', 'Q'], [$this, '_onGraphicStateChange']); $this->_contentParser->registerOperator('cm', [$this, '_onCurrentTransformationMatrix']); $this->_contentParser->registerOperator('Do', [$this, '_onFormXObject']); $this->_contentParser->registerOperator('ID', [$this, '_onInlineImageData']); } return $this->_contentParser; } /** * Callback for inline image data operator * * @param array $arguments * @return bool */ public function _onInlineImageData(array $arguments): bool { $data = []; for ($i = 0, $c = count($arguments); $i < $c; $i += 2) { $data[$arguments[$i]->getValue()] = $arguments[$i + 1]; } if (!(isset($data['W']) || isset($data['Width'])) || !(isset($data['H']) || isset($data['Height']))) { return true; } $pixelWidth = isset($data['W']) ? $data['W']->getValue() : $data['Width']->getValue(); $pixelHeight = isset($data['H']) ? $data['H']->getValue() : $data['Height']->getValue(); $this->_result[] = $this->_getNewResult($pixelWidth, $pixelHeight); $parser = $this->_contentParser->getParser(); $reader = $parser->getReader(); $pos = $reader->getPos(); $offset = $reader->getOffset(); while ( (\preg_match( '/EI[\x00\x09\x0A\x0C\x0D\x20]/', $reader->getBuffer(), $m, PREG_OFFSET_CAPTURE )) === 0 ) { if ($reader->increaseLength(1000) === false) { return false; } } $parser->reset($pos + $offset + ((int) $m[0][1]) + strlen($m[0][0])); return true; } /** * Callback for the content parser which is called if a graphic state token (q/Q) is found. * * @param array $arguments * @param string $operator */ public function _onGraphicStateChange(array $arguments, string $operator) { if ($operator === 'q') { $this->getGraphicState()->save(); } else { $this->getGraphicState()->restore(); } } /** * Callback for the content parser which is called if a "cm" token is found. * * @param array $arguments */ public function _onCurrentTransformationMatrix(array $arguments) { $this->getGraphicState()->addCurrentTransformationMatrix( $arguments[0]->getValue(), $arguments[1]->getValue(), $arguments[2]->getValue(), $arguments[3]->getValue(), $arguments[4]->getValue(), $arguments[5]->getValue() ); } /** * Callback for the content parser which is called if a "Do" operator/token is found. * * @param array $arguments * @throws NotImplementedException */ public function _onFormXObject(array $arguments) { $xObjects = $this->_canvas->getResources(true, false, ResourceInterface::TYPE_X_OBJECT); if ($xObjects === null) { return; } $xObjects = $xObjects->ensure(); $xObject = $xObjects->getValue($arguments[0]->getValue()); if (!($xObject instanceof PdfIndirectReference)) { return; } $xObjectReference = $xObject; $xObject = XObject::get($xObject); if ($xObject instanceof Form) { /* In that case we need to create a new instance of the processor and process * the form xobjects stream. */ if (isset($this->_xObjectObjectIds[$xObject->getIndirectObject()->getObjectId()])) { // recursion return; } $this->_xObjectObjectIds[$xObject->getIndirectObject()->getObjectId()] = true; $gs = $this->getGraphicState(); $gs->save(); $dict = $xObject->getIndirectObject()->ensure()->getValue(); $matrix = $dict->getValue('Matrix'); if ($matrix) { $matrix = $matrix->ensure()->toPhp(); $gs->addCurrentTransformationMatrix( $matrix[0], $matrix[1], $matrix[2], $matrix[3], $matrix[4], $matrix[5] ); } $processor = new self( $xObject->getCanvas(), $this->_switchWidthAndHeight, $gs ); $processor->_xObjectObjectIds =& $this->_xObjectObjectIds; foreach ($processor->process() AS $image) { $this->_result[] = $image; } $gs->restore(); unset($this->_xObjectObjectIds[$xObject->getIndirectObject()->getObjectId()]); } else { $newResult = $this->_getNewResult($xObject->getWidth(), $xObject->getHeight()); $newResult['objectReference'] = $xObjectReference; $this->_result[] = $newResult; } } /** * Helper method to create a result entry. * * @param numeric $pixelWidth * @param numeric $pixelHeight * @return array */ protected function _getNewResult($pixelWidth, $pixelHeight) { // we have an image object, calculate it's outer points in user space $gs = $this->getGraphicState(); $ll = $gs->toUserSpace(new Vector(0, 0, 1)); $ul = $gs->toUserSpace(new Vector(0, 1, 1)); $ur = $gs->toUserSpace(new Vector(1, 1, 1)); $lr = $gs->toUserSpace(new Vector(1, 0, 1)); // ...and match some further information $width = \abs($this->_switchWidthAndHeight ? $ur->subtract($ll)->getY() : $ur->subtract($ll)->getX()); $height = \abs($this->_switchWidthAndHeight ? $ur->subtract($ll)->getX() : $ur->subtract($ll)->getY()); return [ 'll' => $ll->toPoint(), 'ul' => $ul->toPoint(), 'ur' => $ur->toPoint(), 'lr' => $lr->toPoint(), 'width' => $width, 'height' => $height, 'resolutionX' => $pixelWidth / $width * 72, 'resolutionY' => $pixelHeight / $height * 72, 'pixelWidth' => $pixelWidth, 'pixelHeight' => $pixelHeight ]; } }