Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 39 and 400]

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