Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  /**
   3   * This file is part of FPDI
   4   *
   5   * @package   setasign\Fpdi
   6   * @copyright Copyright (c) 2019 Setasign - Jan Slabon (https://www.setasign.com)
   7   * @license   http://opensource.org/licenses/mit-license The MIT License
   8   */
   9  
  10  namespace setasign\Fpdi\PdfReader;
  11  
  12  use setasign\Fpdi\PdfParser\Filter\FilterException;
  13  use setasign\Fpdi\PdfParser\PdfParser;
  14  use setasign\Fpdi\PdfParser\PdfParserException;
  15  use setasign\Fpdi\PdfParser\Type\PdfArray;
  16  use setasign\Fpdi\PdfParser\Type\PdfDictionary;
  17  use setasign\Fpdi\PdfParser\Type\PdfIndirectObject;
  18  use setasign\Fpdi\PdfParser\Type\PdfNull;
  19  use setasign\Fpdi\PdfParser\Type\PdfNumeric;
  20  use setasign\Fpdi\PdfParser\Type\PdfStream;
  21  use setasign\Fpdi\PdfParser\Type\PdfType;
  22  use setasign\Fpdi\PdfParser\Type\PdfTypeException;
  23  use setasign\Fpdi\PdfReader\DataStructure\Rectangle;
  24  use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException;
  25  
  26  /**
  27   * Class representing a page of a PDF document
  28   *
  29   * @package setasign\Fpdi\PdfReader
  30   */
  31  class Page
  32  {
  33      /**
  34       * @var PdfIndirectObject
  35       */
  36      protected $pageObject;
  37  
  38      /**
  39       * @var PdfDictionary
  40       */
  41      protected $pageDictionary;
  42  
  43      /**
  44       * @var PdfParser
  45       */
  46      protected $parser;
  47  
  48      /**
  49       * Inherited attributes
  50       *
  51       * @var null|array
  52       */
  53      protected $inheritedAttributes;
  54  
  55      /**
  56       * Page constructor.
  57       *
  58       * @param PdfIndirectObject $page
  59       * @param PdfParser $parser
  60       */
  61      public function __construct(PdfIndirectObject $page, PdfParser $parser)
  62      {
  63          $this->pageObject = $page;
  64          $this->parser = $parser;
  65      }
  66  
  67      /**
  68       * Get the indirect object of this page.
  69       *
  70       * @return PdfIndirectObject
  71       */
  72      public function getPageObject()
  73      {
  74          return $this->pageObject;
  75      }
  76  
  77      /**
  78       * Get the dictionary of this page.
  79       *
  80       * @return PdfDictionary
  81       * @throws PdfParserException
  82       * @throws PdfTypeException
  83       * @throws CrossReferenceException
  84       */
  85      public function getPageDictionary()
  86      {
  87          if (null === $this->pageDictionary) {
  88              $this->pageDictionary = PdfDictionary::ensure(PdfType::resolve($this->getPageObject(), $this->parser));
  89          }
  90  
  91          return $this->pageDictionary;
  92      }
  93  
  94      /**
  95       * Get a page attribute.
  96       *
  97       * @param string $name
  98       * @param bool $inherited
  99       * @return PdfType|null
 100       * @throws PdfParserException
 101       * @throws PdfTypeException
 102       * @throws CrossReferenceException
 103       */
 104      public function getAttribute($name, $inherited = true)
 105      {
 106          $dict = $this->getPageDictionary();
 107  
 108          if (isset($dict->value[$name])) {
 109              return $dict->value[$name];
 110          }
 111  
 112          $inheritedKeys = ['Resources', 'MediaBox', 'CropBox', 'Rotate'];
 113          if ($inherited && \in_array($name, $inheritedKeys, true)) {
 114              if ($this->inheritedAttributes === null) {
 115                  $this->inheritedAttributes = [];
 116                  $inheritedKeys = \array_filter($inheritedKeys, function ($key) use ($dict) {
 117                      return !isset($dict->value[$key]);
 118                  });
 119  
 120                  if (\count($inheritedKeys) > 0) {
 121                      $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser);
 122                      while ($parentDict instanceof PdfDictionary) {
 123                          foreach ($inheritedKeys as $index => $key) {
 124                              if (isset($parentDict->value[$key])) {
 125                                  $this->inheritedAttributes[$key] = $parentDict->value[$key];
 126                                  unset($inheritedKeys[$index]);
 127                              }
 128                          }
 129  
 130                          /** @noinspection NotOptimalIfConditionsInspection */
 131                          if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) {
 132                              $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser);
 133                          } else {
 134                              break;
 135                          }
 136                      }
 137                  }
 138              }
 139  
 140              if (isset($this->inheritedAttributes[$name])) {
 141                  return $this->inheritedAttributes[$name];
 142              }
 143          }
 144  
 145          return null;
 146      }
 147  
 148      /**
 149       * Get the rotation value.
 150       *
 151       * @return int
 152       * @throws PdfParserException
 153       * @throws PdfTypeException
 154       * @throws CrossReferenceException
 155       */
 156      public function getRotation()
 157      {
 158          $rotation = $this->getAttribute('Rotate');
 159          if (null === $rotation) {
 160              return 0;
 161          }
 162  
 163          $rotation = PdfNumeric::ensure(PdfType::resolve($rotation, $this->parser))->value % 360;
 164  
 165          if ($rotation < 0) {
 166              $rotation += 360;
 167          }
 168  
 169          return $rotation;
 170      }
 171  
 172      /**
 173       * Get a boundary of this page.
 174       *
 175       * @param string $box
 176       * @param bool $fallback
 177       * @return bool|Rectangle
 178       * @throws PdfParserException
 179       * @throws PdfTypeException
 180       * @throws CrossReferenceException
 181       * @see PageBoundaries
 182       */
 183      public function getBoundary($box = PageBoundaries::CROP_BOX, $fallback = true)
 184      {
 185          $value = $this->getAttribute($box);
 186  
 187          if ($value !== null) {
 188              return Rectangle::byPdfArray($value, $this->parser);
 189          }
 190  
 191          if ($fallback === false) {
 192              return false;
 193          }
 194  
 195          switch ($box) {
 196              case PageBoundaries::BLEED_BOX:
 197              case PageBoundaries::TRIM_BOX:
 198              case PageBoundaries::ART_BOX:
 199                  return $this->getBoundary(PageBoundaries::CROP_BOX, true);
 200              case PageBoundaries::CROP_BOX:
 201                  return $this->getBoundary(PageBoundaries::MEDIA_BOX, true);
 202          }
 203  
 204          return false;
 205      }
 206  
 207      /**
 208       * Get the width and height of this page.
 209       *
 210       * @param string $box
 211       * @param bool $fallback
 212       * @return array|bool
 213       * @throws PdfParserException
 214       * @throws PdfTypeException
 215       * @throws CrossReferenceException
 216       */
 217      public function getWidthAndHeight($box = PageBoundaries::CROP_BOX, $fallback = true)
 218      {
 219          $boundary = $this->getBoundary($box, $fallback);
 220          if ($boundary === false) {
 221              return false;
 222          }
 223  
 224          $rotation = $this->getRotation();
 225          $interchange = ($rotation / 90) % 2;
 226  
 227          return [
 228              $interchange ? $boundary->getHeight() : $boundary->getWidth(),
 229              $interchange ? $boundary->getWidth() : $boundary->getHeight()
 230          ];
 231      }
 232  
 233      /**
 234       * Get the raw content stream.
 235       *
 236       * @return string
 237       * @throws PdfReaderException
 238       * @throws PdfTypeException
 239       * @throws FilterException
 240       * @throws PdfParserException
 241       */
 242      public function getContentStream()
 243      {
 244          $dict = $this->getPageDictionary();
 245          $contents = PdfType::resolve(PdfDictionary::get($dict, 'Contents'), $this->parser);
 246          if ($contents instanceof PdfNull) {
 247              return '';
 248          }
 249  
 250          if ($contents instanceof PdfArray) {
 251              $result = [];
 252              foreach ($contents->value as $content) {
 253                  $content = PdfType::resolve($content, $this->parser);
 254                  if (!($content instanceof PdfStream)) {
 255                      continue;
 256                  }
 257                  $result[] = $content->getUnfilteredStream();
 258              }
 259  
 260              return \implode("\n", $result);
 261          }
 262  
 263          if ($contents instanceof PdfStream) {
 264              return $contents->getUnfilteredStream();
 265          }
 266  
 267          throw new PdfReaderException(
 268              'Array or stream expected.',
 269              PdfReaderException::UNEXPECTED_DATA_TYPE
 270          );
 271      }
 272  }