Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 3 /** 4 * This file is part of FPDI 5 * 6 * @package setasign\Fpdi 7 * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com) 8 * @license http://opensource.org/licenses/mit-license The MIT License 9 */ 10 11 namespace setasign\Fpdi; 12 13 use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException; 14 use setasign\Fpdi\PdfParser\Filter\FilterException; 15 use setasign\Fpdi\PdfParser\PdfParser; 16 use setasign\Fpdi\PdfParser\PdfParserException; 17 use setasign\Fpdi\PdfParser\StreamReader; 18 use setasign\Fpdi\PdfParser\Type\PdfArray; 19 use setasign\Fpdi\PdfParser\Type\PdfBoolean; 20 use setasign\Fpdi\PdfParser\Type\PdfDictionary; 21 use setasign\Fpdi\PdfParser\Type\PdfHexString; 22 use setasign\Fpdi\PdfParser\Type\PdfIndirectObject; 23 use setasign\Fpdi\PdfParser\Type\PdfIndirectObjectReference; 24 use setasign\Fpdi\PdfParser\Type\PdfName; 25 use setasign\Fpdi\PdfParser\Type\PdfNull; 26 use setasign\Fpdi\PdfParser\Type\PdfNumeric; 27 use setasign\Fpdi\PdfParser\Type\PdfStream; 28 use setasign\Fpdi\PdfParser\Type\PdfString; 29 use setasign\Fpdi\PdfParser\Type\PdfToken; 30 use setasign\Fpdi\PdfParser\Type\PdfType; 31 use setasign\Fpdi\PdfParser\Type\PdfTypeException; 32 use setasign\Fpdi\PdfReader\PageBoundaries; 33 use setasign\Fpdi\PdfReader\PdfReader; 34 use setasign\Fpdi\PdfReader\PdfReaderException; 35 use /* This namespace/class is used by the commercial FPDI PDF-Parser add-on. */ 36 /** @noinspection PhpUndefinedClassInspection */ 37 /** @noinspection PhpUndefinedNamespaceInspection */ 38 setasign\FpdiPdfParser\PdfParser\PdfParser as FpdiPdfParser; 39 40 /** 41 * The FpdiTrait 42 * 43 * This trait offers the core functionalities of FPDI. By passing them to a trait we can reuse it with e.g. TCPDF in a 44 * very easy way. 45 */ 46 trait FpdiTrait 47 { 48 /** 49 * The pdf reader instances. 50 * 51 * @var PdfReader[] 52 */ 53 protected $readers = []; 54 55 /** 56 * Instances created internally. 57 * 58 * @var array 59 */ 60 protected $createdReaders = []; 61 62 /** 63 * The current reader id. 64 * 65 * @var string|null 66 */ 67 protected $currentReaderId; 68 69 /** 70 * Data of all imported pages. 71 * 72 * @var array 73 */ 74 protected $importedPages = []; 75 76 /** 77 * A map from object numbers of imported objects to new assigned object numbers by FPDF. 78 * 79 * @var array 80 */ 81 protected $objectMap = []; 82 83 /** 84 * An array with information about objects, which needs to be copied to the resulting document. 85 * 86 * @var array 87 */ 88 protected $objectsToCopy = []; 89 90 /** 91 * Release resources and file handles. 92 * 93 * This method is called internally when the document is created successfully. By default it only cleans up 94 * stream reader instances which were created internally. 95 * 96 * @param bool $allReaders 97 */ 98 public function cleanUp($allReaders = false) 99 { 100 $readers = $allReaders ? array_keys($this->readers) : $this->createdReaders; 101 foreach ($readers as $id) { 102 $this->readers[$id]->getParser()->getStreamReader()->cleanUp(); 103 unset($this->readers[$id]); 104 } 105 106 $this->createdReaders = []; 107 } 108 109 /** 110 * Set the minimal PDF version. 111 * 112 * @param string $pdfVersion 113 */ 114 protected function setMinPdfVersion($pdfVersion) 115 { 116 if (\version_compare($pdfVersion, $this->PDFVersion, '>')) { 117 $this->PDFVersion = $pdfVersion; 118 } 119 } 120 121 /** @noinspection PhpUndefinedClassInspection */ 122 /** 123 * Get a new pdf parser instance. 124 * 125 * @param StreamReader $streamReader 126 * @return PdfParser|FpdiPdfParser 127 */ 128 protected function getPdfParserInstance(StreamReader $streamReader) 129 { 130 // note: if you get an exception here - turn off errors/warnings on not found for your autoloader. 131 // psr-4 (https://www.php-fig.org/psr/psr-4/) says: Autoloader implementations MUST NOT throw 132 // exceptions, MUST NOT raise errors of any level, and SHOULD NOT return a value. 133 /** @noinspection PhpUndefinedClassInspection */ 134 if (\class_exists(FpdiPdfParser::class)) { 135 /** @noinspection PhpUndefinedClassInspection */ 136 return new FpdiPdfParser($streamReader); 137 } 138 139 return new PdfParser($streamReader); 140 } 141 142 /** 143 * Get an unique reader id by the $file parameter. 144 * 145 * @param string|resource|PdfReader|StreamReader $file An open file descriptor, a path to a file, a PdfReader 146 * instance or a StreamReader instance. 147 * @return string 148 */ 149 protected function getPdfReaderId($file) 150 { 151 if (\is_resource($file)) { 152 $id = (string) $file; 153 } elseif (\is_string($file)) { 154 $id = \realpath($file); 155 if ($id === false) { 156 $id = $file; 157 } 158 } elseif (\is_object($file)) { 159 $id = \spl_object_hash($file); 160 } else { 161 throw new \InvalidArgumentException( 162 \sprintf('Invalid type in $file parameter (%s)', \gettype($file)) 163 ); 164 } 165 166 /** @noinspection OffsetOperationsInspection */ 167 if (isset($this->readers[$id])) { 168 return $id; 169 } 170 171 if (\is_resource($file)) { 172 $streamReader = new StreamReader($file); 173 } elseif (\is_string($file)) { 174 $streamReader = StreamReader::createByFile($file); 175 $this->createdReaders[] = $id; 176 } else { 177 $streamReader = $file; 178 } 179 180 $reader = new PdfReader($this->getPdfParserInstance($streamReader)); 181 /** @noinspection OffsetOperationsInspection */ 182 $this->readers[$id] = $reader; 183 184 return $id; 185 } 186 187 /** 188 * Get a pdf reader instance by its id. 189 * 190 * @param string $id 191 * @return PdfReader 192 */ 193 protected function getPdfReader($id) 194 { 195 if (isset($this->readers[$id])) { 196 return $this->readers[$id]; 197 } 198 199 throw new \InvalidArgumentException( 200 \sprintf('No pdf reader with the given id (%s) exists.', $id) 201 ); 202 } 203 204 /** 205 * Set the source PDF file. 206 * 207 * @param string|resource|StreamReader $file Path to the file or a stream resource or a StreamReader instance. 208 * @return int The page count of the PDF document. 209 * @throws PdfParserException 210 */ 211 public function setSourceFile($file) 212 { 213 $this->currentReaderId = $this->getPdfReaderId($file); 214 $this->objectsToCopy[$this->currentReaderId] = []; 215 216 $reader = $this->getPdfReader($this->currentReaderId); 217 $this->setMinPdfVersion($reader->getPdfVersion()); 218 219 return $reader->getPageCount(); 220 } 221 222 /** 223 * Imports a page. 224 * 225 * @param int $pageNumber The page number. 226 * @param string $box The page boundary to import. Default set to PageBoundaries::CROP_BOX. 227 * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). 228 * @return string A unique string identifying the imported page. 229 * @throws CrossReferenceException 230 * @throws FilterException 231 * @throws PdfParserException 232 * @throws PdfTypeException 233 * @throws PdfReaderException 234 * @see PageBoundaries 235 */ 236 public function importPage($pageNumber, $box = PageBoundaries::CROP_BOX, $groupXObject = true) 237 { 238 if (null === $this->currentReaderId) { 239 throw new \BadMethodCallException('No reader initiated. Call setSourceFile() first.'); 240 } 241 242 $pageId = $this->currentReaderId; 243 244 $pageNumber = (int)$pageNumber; 245 $pageId .= '|' . $pageNumber . '|' . ($groupXObject ? '1' : '0'); 246 247 // for backwards compatibility with FPDI 1 248 $box = \ltrim($box, '/'); 249 if (!PageBoundaries::isValidName($box)) { 250 throw new \InvalidArgumentException( 251 \sprintf('Box name is invalid: "%s"', $box) 252 ); 253 } 254 255 $pageId .= '|' . $box; 256 257 if (isset($this->importedPages[$pageId])) { 258 return $pageId; 259 } 260 261 $reader = $this->getPdfReader($this->currentReaderId); 262 $page = $reader->getPage($pageNumber); 263 264 $bbox = $page->getBoundary($box); 265 if ($bbox === false) { 266 throw new PdfReaderException( 267 \sprintf("Page doesn't have a boundary box (%s).", $box), 268 PdfReaderException::MISSING_DATA 269 ); 270 } 271 272 $dict = new PdfDictionary(); 273 $dict->value['Type'] = PdfName::create('XObject'); 274 $dict->value['Subtype'] = PdfName::create('Form'); 275 $dict->value['FormType'] = PdfNumeric::create(1); 276 $dict->value['BBox'] = $bbox->toPdfArray(); 277 278 if ($groupXObject) { 279 $this->setMinPdfVersion('1.4'); 280 $dict->value['Group'] = PdfDictionary::create([ 281 'Type' => PdfName::create('Group'), 282 'S' => PdfName::create('Transparency') 283 ]); 284 } 285 286 $resources = $page->getAttribute('Resources'); 287 if ($resources !== null) { 288 $dict->value['Resources'] = $resources; 289 } 290 291 list($width, $height) = $page->getWidthAndHeight($box); 292 293 $a = 1; 294 $b = 0; 295 $c = 0; 296 $d = 1; 297 $e = -$bbox->getLlx(); 298 $f = -$bbox->getLly(); 299 300 $rotation = $page->getRotation(); 301 302 if ($rotation !== 0) { 303 $rotation *= -1; 304 $angle = $rotation * M_PI / 180; 305 $a = \cos($angle); 306 $b = \sin($angle); 307 $c = -$b; 308 $d = $a; 309 310 switch ($rotation) { 311 case -90: 312 $e = -$bbox->getLly(); 313 $f = $bbox->getUrx(); 314 break; 315 case -180: 316 $e = $bbox->getUrx(); 317 $f = $bbox->getUry(); 318 break; 319 case -270: 320 $e = $bbox->getUry(); 321 $f = -$bbox->getLlx(); 322 break; 323 } 324 } 325 326 // we need to rotate/translate 327 if ($a != 1 || $b != 0 || $c != 0 || $d != 1 || $e != 0 || $f != 0) { 328 $dict->value['Matrix'] = PdfArray::create([ 329 PdfNumeric::create($a), PdfNumeric::create($b), PdfNumeric::create($c), 330 PdfNumeric::create($d), PdfNumeric::create($e), PdfNumeric::create($f) 331 ]); 332 } 333 334 // try to use the existing content stream 335 $pageDict = $page->getPageDictionary(); 336 337 $contentsObject = PdfType::resolve(PdfDictionary::get($pageDict, 'Contents'), $reader->getParser(), true); 338 $contents = PdfType::resolve($contentsObject, $reader->getParser()); 339 340 // just copy the stream reference if it is only a single stream 341 if ( 342 ($contentsIsStream = ($contents instanceof PdfStream)) 343 || ($contents instanceof PdfArray && \count($contents->value) === 1) 344 ) { 345 if ($contentsIsStream) { 346 /** 347 * @var PdfIndirectObject $contentsObject 348 */ 349 $stream = $contents; 350 } else { 351 $stream = PdfType::resolve($contents->value[0], $reader->getParser()); 352 } 353 354 $filter = PdfDictionary::get($stream->value, 'Filter'); 355 if (!$filter instanceof PdfNull) { 356 $dict->value['Filter'] = $filter; 357 } 358 $length = PdfType::resolve(PdfDictionary::get($stream->value, 'Length'), $reader->getParser()); 359 $dict->value['Length'] = $length; 360 $stream->value = $dict; 361 362 // otherwise extract it from the array and re-compress the whole stream 363 } else { 364 $streamContent = $this->compress 365 ? \gzcompress($page->getContentStream()) 366 : $page->getContentStream(); 367 368 $dict->value['Length'] = PdfNumeric::create(\strlen($streamContent)); 369 if ($this->compress) { 370 $dict->value['Filter'] = PdfName::create('FlateDecode'); 371 } 372 373 $stream = PdfStream::create($dict, $streamContent); 374 } 375 376 $this->importedPages[$pageId] = [ 377 'objectNumber' => null, 378 'readerId' => $this->currentReaderId, 379 'id' => 'TPL' . $this->getNextTemplateId(), 380 'width' => $width / $this->k, 381 'height' => $height / $this->k, 382 'stream' => $stream 383 ]; 384 385 return $pageId; 386 } 387 388 /** 389 * Draws an imported page onto the page. 390 * 391 * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the 392 * aspect ratio. 393 * 394 * @param mixed $pageId The page id 395 * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array 396 * with the keys "x", "y", "width", "height", "adjustPageSize". 397 * @param float|int $y The ordinate of upper-left corner. 398 * @param float|int|null $width The width. 399 * @param float|int|null $height The height. 400 * @param bool $adjustPageSize 401 * @return array The size. 402 * @see Fpdi::getTemplateSize() 403 */ 404 public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) 405 { 406 if (\is_array($x)) { 407 /** @noinspection OffsetOperationsInspection */ 408 unset($x['pageId']); 409 \extract($x, EXTR_IF_EXISTS); 410 /** @noinspection NotOptimalIfConditionsInspection */ 411 if (\is_array($x)) { 412 $x = 0; 413 } 414 } 415 416 if (!isset($this->importedPages[$pageId])) { 417 throw new \InvalidArgumentException('Imported page does not exist!'); 418 } 419 420 $importedPage = $this->importedPages[$pageId]; 421 422 $originalSize = $this->getTemplateSize($pageId); 423 $newSize = $this->getTemplateSize($pageId, $width, $height); 424 if ($adjustPageSize) { 425 $this->setPageFormat($newSize, $newSize['orientation']); 426 } 427 428 $this->_out( 429 // reset standard values, translate and scale 430 \sprintf( 431 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q', 432 ($newSize['width'] / $originalSize['width']), 433 ($newSize['height'] / $originalSize['height']), 434 $x * $this->k, 435 ($this->h - $y - $newSize['height']) * $this->k, 436 $importedPage['id'] 437 ) 438 ); 439 440 return $newSize; 441 } 442 443 /** 444 * Get the size of an imported page. 445 * 446 * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the 447 * aspect ratio. 448 * 449 * @param mixed $tpl The template id 450 * @param float|int|null $width The width. 451 * @param float|int|null $height The height. 452 * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) 453 */ 454 public function getImportedPageSize($tpl, $width = null, $height = null) 455 { 456 if (isset($this->importedPages[$tpl])) { 457 $importedPage = $this->importedPages[$tpl]; 458 459 if ($width === null && $height === null) { 460 $width = $importedPage['width']; 461 $height = $importedPage['height']; 462 } elseif ($width === null) { 463 $width = $height * $importedPage['width'] / $importedPage['height']; 464 } 465 466 if ($height === null) { 467 $height = $width * $importedPage['height'] / $importedPage['width']; 468 } 469 470 if ($height <= 0. || $width <= 0.) { 471 throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.'); 472 } 473 474 return [ 475 'width' => $width, 476 'height' => $height, 477 0 => $width, 478 1 => $height, 479 'orientation' => $width > $height ? 'L' : 'P' 480 ]; 481 } 482 483 return false; 484 } 485 486 /** 487 * Writes a PdfType object to the resulting buffer. 488 * 489 * @param PdfType $value 490 * @throws PdfTypeException 491 */ 492 protected function writePdfType(PdfType $value) 493 { 494 if ($value instanceof PdfNumeric) { 495 if (\is_int($value->value)) { 496 $this->_put($value->value . ' ', false); 497 } else { 498 $this->_put(\rtrim(\rtrim(\sprintf('%.5F', $value->value), '0'), '.') . ' ', false); 499 } 500 } elseif ($value instanceof PdfName) { 501 $this->_put('/' . $value->value . ' ', false); 502 } elseif ($value instanceof PdfString) { 503 $this->_put('(' . $value->value . ')', false); 504 } elseif ($value instanceof PdfHexString) { 505 $this->_put('<' . $value->value . '>'); 506 } elseif ($value instanceof PdfBoolean) { 507 $this->_put($value->value ? 'true ' : 'false ', false); 508 } elseif ($value instanceof PdfArray) { 509 $this->_put('[', false); 510 foreach ($value->value as $entry) { 511 $this->writePdfType($entry); 512 } 513 $this->_put(']'); 514 } elseif ($value instanceof PdfDictionary) { 515 $this->_put('<<', false); 516 foreach ($value->value as $name => $entry) { 517 $this->_put('/' . $name . ' ', false); 518 $this->writePdfType($entry); 519 } 520 $this->_put('>>'); 521 } elseif ($value instanceof PdfToken) { 522 $this->_put($value->value); 523 } elseif ($value instanceof PdfNull) { 524 $this->_put('null '); 525 } elseif ($value instanceof PdfStream) { 526 /** 527 * @var $value PdfStream 528 */ 529 $this->writePdfType($value->value); 530 $this->_put('stream'); 531 $this->_put($value->getStream()); 532 $this->_put('endstream'); 533 } elseif ($value instanceof PdfIndirectObjectReference) { 534 if (!isset($this->objectMap[$this->currentReaderId])) { 535 $this->objectMap[$this->currentReaderId] = []; 536 } 537 538 if (!isset($this->objectMap[$this->currentReaderId][$value->value])) { 539 $this->objectMap[$this->currentReaderId][$value->value] = ++$this->n; 540 $this->objectsToCopy[$this->currentReaderId][] = $value->value; 541 } 542 543 $this->_put($this->objectMap[$this->currentReaderId][$value->value] . ' 0 R ', false); 544 } elseif ($value instanceof PdfIndirectObject) { 545 /** 546 * @var PdfIndirectObject $value 547 */ 548 $n = $this->objectMap[$this->currentReaderId][$value->objectNumber]; 549 $this->_newobj($n); 550 $this->writePdfType($value->value); 551 $this->_put('endobj'); 552 } 553 } 554 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body