123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- <?php
- /**
- * This file is part of FPDI
- *
- * @package Fpdi
- * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
- * @license http://opensource.org/licenses/mit-license The MIT License
- */
- namespace Fpdi;
- use Fpdi\PdfParser\CrossReference\CrossReferenceException;
- use Fpdi\PdfParser\Filter\FilterException;
- use Fpdi\PdfParser\PdfParser;
- use Fpdi\PdfParser\PdfParserException;
- use Fpdi\PdfParser\StreamReader;
- use Fpdi\PdfParser\Type\PdfArray;
- use Fpdi\PdfParser\Type\PdfBoolean;
- use Fpdi\PdfParser\Type\PdfDictionary;
- use Fpdi\PdfParser\Type\PdfHexString;
- use Fpdi\PdfParser\Type\PdfIndirectObject;
- use Fpdi\PdfParser\Type\PdfIndirectObjectReference;
- use Fpdi\PdfParser\Type\PdfName;
- use Fpdi\PdfParser\Type\PdfNull;
- use Fpdi\PdfParser\Type\PdfNumeric;
- use Fpdi\PdfParser\Type\PdfStream;
- use Fpdi\PdfParser\Type\PdfString;
- use Fpdi\PdfParser\Type\PdfToken;
- use Fpdi\PdfParser\Type\PdfType;
- use Fpdi\PdfParser\Type\PdfTypeException;
- use Fpdi\PdfReader\PageBoundaries;
- use Fpdi\PdfReader\PdfReader;
- use Fpdi\PdfReader\PdfReaderException;
- use /* This namespace/class is used by the commercial FPDI PDF-Parser add-on. */
- /** @noinspection PhpUndefinedClassInspection */
- /** @noinspection PhpUndefinedNamespaceInspection */
- FpdiPdfParser\PdfParser\PdfParser as FpdiPdfParser;
- /**
- * The FpdiTrait
- *
- * This trait offers the core functionalities of FPDI. By passing them to a trait we can reuse it with e.g. TCPDF in a
- * very easy way.
- */
- trait FpdiTrait
- {
- /**
- * The pdf reader instances.
- *
- * @var PdfReader[]
- */
- protected $readers = [];
- /**
- * Instances created internally.
- *
- * @var array
- */
- protected $createdReaders = [];
- /**
- * The current reader id.
- *
- * @var string|null
- */
- protected $currentReaderId;
- /**
- * Data of all imported pages.
- *
- * @var array
- */
- protected $importedPages = [];
- /**
- * A map from object numbers of imported objects to new assigned object numbers by FPDF.
- *
- * @var array
- */
- protected $objectMap = [];
- /**
- * An array with information about objects, which needs to be copied to the resulting document.
- *
- * @var array
- */
- protected $objectsToCopy = [];
- /**
- * Release resources and file handles.
- *
- * This method is called internally when the document is created successfully. By default it only cleans up
- * stream reader instances which were created internally.
- *
- * @param bool $allReaders
- */
- public function cleanUp($allReaders = false)
- {
- $readers = $allReaders ? array_keys($this->readers) : $this->createdReaders;
- foreach ($readers as $id) {
- $this->readers[$id]->getParser()->getStreamReader()->cleanUp();
- unset($this->readers[$id]);
- }
- $this->createdReaders = [];
- }
- /**
- * Set the minimal PDF version.
- *
- * @param string $pdfVersion
- */
- protected function setMinPdfVersion($pdfVersion)
- {
- if (\version_compare($pdfVersion, $this->PDFVersion, '>')) {
- $this->PDFVersion = $pdfVersion;
- }
- }
- /** @noinspection PhpUndefinedClassInspection */
- /**
- * Get a new pdf parser instance.
- *
- * @param StreamReader $streamReader
- * @return PdfParser|FpdiPdfParser
- */
- protected function getPdfParserInstance(StreamReader $streamReader)
- {
- // note: if you get an exception here - turn off errors/warnings on not found for your autoloader.
- // psr-4 (https://www.php-fig.org/psr/psr-4/) says: Autoloader implementations MUST NOT throw
- // exceptions, MUST NOT raise errors of any level, and SHOULD NOT return a value.
- /** @noinspection PhpUndefinedClassInspection */
- if (\class_exists(FpdiPdfParser::class)) {
- /** @noinspection PhpUndefinedClassInspection */
- return new FpdiPdfParser($streamReader);
- }
- return new PdfParser($streamReader);
- }
- /**
- * Get an unique reader id by the $file parameter.
- *
- * @param string|resource|PdfReader|StreamReader $file An open file descriptor, a path to a file, a PdfReader
- * instance or a StreamReader instance.
- * @return string
- */
- protected function getPdfReaderId($file)
- {
- if (\is_resource($file)) {
- $id = (string) $file;
- } elseif (\is_string($file)) {
- $id = \realpath($file);
- if ($id === false) {
- $id = $file;
- }
- } elseif (\is_object($file)) {
- $id = \spl_object_hash($file);
- } else {
- throw new \InvalidArgumentException(
- \sprintf('Invalid type in $file parameter (%s)', \gettype($file))
- );
- }
- /** @noinspection OffsetOperationsInspection */
- if (isset($this->readers[$id])) {
- return $id;
- }
- if (\is_resource($file)) {
- $streamReader = new StreamReader($file);
- } elseif (\is_string($file)) {
- $streamReader = StreamReader::createByFile($file);
- $this->createdReaders[] = $id;
- } else {
- $streamReader = $file;
- }
- $reader = new PdfReader($this->getPdfParserInstance($streamReader));
- /** @noinspection OffsetOperationsInspection */
- $this->readers[$id] = $reader;
- return $id;
- }
- /**
- * Get a pdf reader instance by its id.
- *
- * @param string $id
- * @return PdfReader
- */
- protected function getPdfReader($id)
- {
- if (isset($this->readers[$id])) {
- return $this->readers[$id];
- }
- throw new \InvalidArgumentException(
- \sprintf('No pdf reader with the given id (%s) exists.', $id)
- );
- }
- /**
- * Set the source PDF file.
- *
- * @param string|resource|StreamReader $file Path to the file or a stream resource or a StreamReader instance.
- * @return int The page count of the PDF document.
- * @throws PdfParserException
- */
- public function setSourceFile($file)
- {
- $this->currentReaderId = $this->getPdfReaderId($file);
- $this->objectsToCopy[$this->currentReaderId] = [];
- $reader = $this->getPdfReader($this->currentReaderId);
- $this->setMinPdfVersion($reader->getPdfVersion());
- return $reader->getPageCount();
- }
- /**
- * Imports a page.
- *
- * @param int $pageNumber The page number.
- * @param string $box The page boundary to import. Default set to PageBoundaries::CROP_BOX.
- * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used).
- * @return string A unique string identifying the imported page.
- * @throws CrossReferenceException
- * @throws FilterException
- * @throws PdfParserException
- * @throws PdfTypeException
- * @throws PdfReaderException
- * @see PageBoundaries
- */
- public function importPage($pageNumber, $box = PageBoundaries::CROP_BOX, $groupXObject = true)
- {
- if (null === $this->currentReaderId) {
- throw new \BadMethodCallException('No reader initiated. Call setSourceFile() first.');
- }
- $pageId = $this->currentReaderId;
- $pageNumber = (int)$pageNumber;
- $pageId .= '|' . $pageNumber . '|' . ($groupXObject ? '1' : '0');
- // for backwards compatibility with FPDI 1
- $box = \ltrim($box, '/');
- if (!PageBoundaries::isValidName($box)) {
- throw new \InvalidArgumentException(
- \sprintf('Box name is invalid: "%s"', $box)
- );
- }
- $pageId .= '|' . $box;
- if (isset($this->importedPages[$pageId])) {
- return $pageId;
- }
- $reader = $this->getPdfReader($this->currentReaderId);
- $page = $reader->getPage($pageNumber);
- $bbox = $page->getBoundary($box);
- if ($bbox === false) {
- throw new PdfReaderException(
- \sprintf("Page doesn't have a boundary box (%s).", $box),
- PdfReaderException::MISSING_DATA
- );
- }
- $dict = new PdfDictionary();
- $dict->value['Type'] = PdfName::create('XObject');
- $dict->value['Subtype'] = PdfName::create('Form');
- $dict->value['FormType'] = PdfNumeric::create(1);
- $dict->value['BBox'] = $bbox->toPdfArray();
- if ($groupXObject) {
- $this->setMinPdfVersion('1.4');
- $dict->value['Group'] = PdfDictionary::create([
- 'Type' => PdfName::create('Group'),
- 'S' => PdfName::create('Transparency')
- ]);
- }
- $resources = $page->getAttribute('Resources');
- if ($resources !== null) {
- $dict->value['Resources'] = $resources;
- }
- list($width, $height) = $page->getWidthAndHeight($box);
- $a = 1;
- $b = 0;
- $c = 0;
- $d = 1;
- $e = -$bbox->getLlx();
- $f = -$bbox->getLly();
- $rotation = $page->getRotation();
- if ($rotation !== 0) {
- $rotation *= -1;
- $angle = $rotation * M_PI / 180;
- $a = \cos($angle);
- $b = \sin($angle);
- $c = -$b;
- $d = $a;
- switch ($rotation) {
- case -90:
- $e = -$bbox->getLly();
- $f = $bbox->getUrx();
- break;
- case -180:
- $e = $bbox->getUrx();
- $f = $bbox->getUry();
- break;
- case -270:
- $e = $bbox->getUry();
- $f = -$bbox->getLlx();
- break;
- }
- }
- // we need to rotate/translate
- if ($a != 1 || $b != 0 || $c != 0 || $d != 1 || $e != 0 || $f != 0) {
- $dict->value['Matrix'] = PdfArray::create([
- PdfNumeric::create($a), PdfNumeric::create($b), PdfNumeric::create($c),
- PdfNumeric::create($d), PdfNumeric::create($e), PdfNumeric::create($f)
- ]);
- }
- // try to use the existing content stream
- $pageDict = $page->getPageDictionary();
- try {
- $contentsObject = PdfType::resolve(PdfDictionary::get($pageDict, 'Contents'), $reader->getParser(), true);
- $contents = PdfType::resolve($contentsObject, $reader->getParser());
- // just copy the stream reference if it is only a single stream
- if (
- ($contentsIsStream = ($contents instanceof PdfStream))
- || ($contents instanceof PdfArray && \count($contents->value) === 1)
- ) {
- if ($contentsIsStream) {
- /**
- * @var PdfIndirectObject $contentsObject
- */
- $stream = $contents;
- } else {
- $stream = PdfType::resolve($contents->value[0], $reader->getParser());
- }
- $filter = PdfDictionary::get($stream->value, 'Filter');
- if (!$filter instanceof PdfNull) {
- $dict->value['Filter'] = $filter;
- }
- $length = PdfType::resolve(PdfDictionary::get($stream->value, 'Length'), $reader->getParser());
- $dict->value['Length'] = $length;
- $stream->value = $dict;
- // otherwise extract it from the array and re-compress the whole stream
- } else {
- $streamContent = $this->compress
- ? \gzcompress($page->getContentStream())
- : $page->getContentStream();
- $dict->value['Length'] = PdfNumeric::create(\strlen($streamContent));
- if ($this->compress) {
- $dict->value['Filter'] = PdfName::create('FlateDecode');
- }
- $stream = PdfStream::create($dict, $streamContent);
- }
- // Catch faulty pages and use an empty content stream
- } catch (FpdiException $e) {
- $dict->value['Length'] = PdfNumeric::create(0);
- $stream = PdfStream::create($dict, '');
- }
- $this->importedPages[$pageId] = [
- 'objectNumber' => null,
- 'readerId' => $this->currentReaderId,
- 'id' => 'TPL' . $this->getNextTemplateId(),
- 'width' => $width / $this->k,
- 'height' => $height / $this->k,
- 'stream' => $stream
- ];
- return $pageId;
- }
- /**
- * Draws an imported page onto the page.
- *
- * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
- * aspect ratio.
- *
- * @param mixed $pageId The page id
- * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array
- * with the keys "x", "y", "width", "height", "adjustPageSize".
- * @param float|int $y The ordinate of upper-left corner.
- * @param float|int|null $width The width.
- * @param float|int|null $height The height.
- * @param bool $adjustPageSize
- * @return array The size.
- * @see Fpdi::getTemplateSize()
- */
- public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false)
- {
- if (\is_array($x)) {
- /** @noinspection OffsetOperationsInspection */
- unset($x['pageId']);
- \extract($x, EXTR_IF_EXISTS);
- /** @noinspection NotOptimalIfConditionsInspection */
- if (\is_array($x)) {
- $x = 0;
- }
- }
- if (!isset($this->importedPages[$pageId])) {
- throw new \InvalidArgumentException('Imported page does not exist!');
- }
- $importedPage = $this->importedPages[$pageId];
- $originalSize = $this->getTemplateSize($pageId);
- $newSize = $this->getTemplateSize($pageId, $width, $height);
- if ($adjustPageSize) {
- $this->setPageFormat($newSize, $newSize['orientation']);
- }
- $this->_out(
- // reset standard values, translate and scale
- \sprintf(
- 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q',
- ($newSize['width'] / $originalSize['width']),
- ($newSize['height'] / $originalSize['height']),
- $x * $this->k,
- ($this->h - $y - $newSize['height']) * $this->k,
- $importedPage['id']
- )
- );
- return $newSize;
- }
- /**
- * Get the size of an imported page.
- *
- * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
- * aspect ratio.
- *
- * @param mixed $tpl The template id
- * @param float|int|null $width The width.
- * @param float|int|null $height The height.
- * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P)
- */
- public function getImportedPageSize($tpl, $width = null, $height = null)
- {
- if (isset($this->importedPages[$tpl])) {
- $importedPage = $this->importedPages[$tpl];
- if ($width === null && $height === null) {
- $width = $importedPage['width'];
- $height = $importedPage['height'];
- } elseif ($width === null) {
- $width = $height * $importedPage['width'] / $importedPage['height'];
- }
- if ($height === null) {
- $height = $width * $importedPage['height'] / $importedPage['width'];
- }
- if ($height <= 0. || $width <= 0.) {
- throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.');
- }
- return [
- 'width' => $width,
- 'height' => $height,
- 0 => $width,
- 1 => $height,
- 'orientation' => $width > $height ? 'L' : 'P'
- ];
- }
- return false;
- }
- /**
- * Writes a PdfType object to the resulting buffer.
- *
- * @param PdfType $value
- * @throws PdfTypeException
- */
- protected function writePdfType(PdfType $value)
- {
- if ($value instanceof PdfNumeric) {
- if (\is_int($value->value)) {
- $this->_put($value->value . ' ', false);
- } else {
- $this->_put(\rtrim(\rtrim(\sprintf('%.5F', $value->value), '0'), '.') . ' ', false);
- }
- } elseif ($value instanceof PdfName) {
- $this->_put('/' . $value->value . ' ', false);
- } elseif ($value instanceof PdfString) {
- $this->_put('(' . $value->value . ')', false);
- } elseif ($value instanceof PdfHexString) {
- $this->_put('<' . $value->value . '>');
- } elseif ($value instanceof PdfBoolean) {
- $this->_put($value->value ? 'true ' : 'false ', false);
- } elseif ($value instanceof PdfArray) {
- $this->_put('[', false);
- foreach ($value->value as $entry) {
- $this->writePdfType($entry);
- }
- $this->_put(']');
- } elseif ($value instanceof PdfDictionary) {
- $this->_put('<<', false);
- foreach ($value->value as $name => $entry) {
- $this->_put('/' . $name . ' ', false);
- $this->writePdfType($entry);
- }
- $this->_put('>>');
- } elseif ($value instanceof PdfToken) {
- $this->_put($value->value);
- } elseif ($value instanceof PdfNull) {
- $this->_put('null ');
- } elseif ($value instanceof PdfStream) {
- /**
- * @var $value PdfStream
- */
- $this->writePdfType($value->value);
- $this->_put('stream');
- $this->_put($value->getStream());
- $this->_put('endstream');
- } elseif ($value instanceof PdfIndirectObjectReference) {
- if (!isset($this->objectMap[$this->currentReaderId])) {
- $this->objectMap[$this->currentReaderId] = [];
- }
- if (!isset($this->objectMap[$this->currentReaderId][$value->value])) {
- $this->objectMap[$this->currentReaderId][$value->value] = ++$this->n;
- $this->objectsToCopy[$this->currentReaderId][] = $value->value;
- }
- $this->_put($this->objectMap[$this->currentReaderId][$value->value] . ' 0 R ', false);
- } elseif ($value instanceof PdfIndirectObject) {
- /**
- * @var PdfIndirectObject $value
- */
- $n = $this->objectMap[$this->currentReaderId][$value->objectNumber];
- $this->_newobj($n);
- $this->writePdfType($value->value);
- $this->_put('endobj');
- }
- }
- }
|