Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 39 and 402] [Versions 402 and 403]

   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\PdfParser\CrossReference;
  12  
  13  use setasign\Fpdi\PdfParser\PdfParser;
  14  use setasign\Fpdi\PdfParser\StreamReader;
  15  
  16  /**
  17   * Class LineReader
  18   *
  19   * This reader class read all cross-reference entries in a single run.
  20   * It supports reading cross-references with e.g. invalid data (e.g. entries with a length < or > 20 bytes).
  21   */
  22  class LineReader extends AbstractReader implements ReaderInterface
  23  {
  24      /**
  25       * The object offsets.
  26       *
  27       * @var array
  28       */
  29      protected $offsets;
  30  
  31      /**
  32       * LineReader constructor.
  33       *
  34       * @param PdfParser $parser
  35       * @throws CrossReferenceException
  36       */
  37      public function __construct(PdfParser $parser)
  38      {
  39          $this->read($this->extract($parser->getStreamReader()));
  40          parent::__construct($parser);
  41      }
  42  
  43      /**
  44       * @inheritdoc
  45       */
  46      public function getOffsetFor($objectNumber)
  47      {
  48          if (isset($this->offsets[$objectNumber])) {
  49              return $this->offsets[$objectNumber][0];
  50          }
  51  
  52          return false;
  53      }
  54  
  55      /**
  56       * Get all found offsets.
  57       *
  58       * @return array
  59       */
  60      public function getOffsets()
  61      {
  62          return $this->offsets;
  63      }
  64  
  65      /**
  66       * Extracts the cross reference data from the stream reader.
  67       *
  68       * @param StreamReader $reader
  69       * @return string
  70       * @throws CrossReferenceException
  71       */
  72      protected function extract(StreamReader $reader)
  73      {
  74          $bytesPerCycle = 100;
  75          $reader->reset(null, $bytesPerCycle);
  76  
  77          $cycles = 0;
  78          do {
  79              // 6 = length of "trailer" - 1
  80              $pos = \max(($bytesPerCycle * $cycles) - 6, 0);
  81              $trailerPos = \strpos($reader->getBuffer(false), 'trailer', $pos);
  82              $cycles++;
  83          } while ($trailerPos === false && $reader->increaseLength($bytesPerCycle) !== false);
  84  
  85          if ($trailerPos === false) {
  86              throw new CrossReferenceException(
  87                  'Unexpected end of cross reference. "trailer"-keyword not found.',
  88                  CrossReferenceException::NO_TRAILER_FOUND
  89              );
  90          }
  91  
  92          $xrefContent = \substr($reader->getBuffer(false), 0, $trailerPos);
  93          $reader->reset($reader->getPosition() + $trailerPos);
  94  
  95          return $xrefContent;
  96      }
  97  
  98      /**
  99       * Read the cross-reference entries.
 100       *
 101       * @param string $xrefContent
 102       * @throws CrossReferenceException
 103       */
 104      protected function read($xrefContent)
 105      {
 106          // get eol markers in the first 100 bytes
 107          \preg_match_all("/(\r\n|\n|\r)/", \substr($xrefContent, 0, 100), $m);
 108  
 109          if (\count($m[0]) === 0) {
 110              throw new CrossReferenceException(
 111                  'No data found in cross-reference.',
 112                  CrossReferenceException::INVALID_DATA
 113              );
 114          }
 115  
 116          // count(array_count_values()) is faster then count(array_unique())
 117          // @see https://github.com/symfony/symfony/pull/23731
 118          // can be reverted for php7.2
 119          $differentLineEndings = \count(\array_count_values($m[0]));
 120          if ($differentLineEndings > 1) {
 121              $lines = \preg_split("/(\r\n|\n|\r)/", $xrefContent, -1, PREG_SPLIT_NO_EMPTY);
 122          } else {
 123              $lines = \explode($m[0][0], $xrefContent);
 124          }
 125  
 126          unset($differentLineEndings, $m);
 127          if (!\is_array($lines)) {
 128              $this->offsets = [];
 129              return;
 130          }
 131  
 132          $start = 0;
 133          $offsets = [];
 134  
 135          // trim all lines and remove empty lines
 136          $lines = \array_filter(\array_map('\trim', $lines));
 137          foreach ($lines as $line) {
 138              $pieces = \explode(' ', $line);
 139  
 140              switch (\count($pieces)) {
 141                  case 2:
 142                      $start = (int) $pieces[0];
 143                      break;
 144  
 145                  case 3:
 146                      switch ($pieces[2]) {
 147                          case 'n':
 148                              $offsets[$start] = [(int) $pieces[0], (int) $pieces[1]];
 149                              $start++;
 150                              break 2;
 151                          case 'f':
 152                              $start++;
 153                              break 2;
 154                      }
 155                      // fall through if pieces doesn't match
 156  
 157                  default:
 158                      throw new CrossReferenceException(
 159                          \sprintf('Unexpected data in xref table (%s)', \implode(' ', $pieces)),
 160                          CrossReferenceException::INVALID_DATA
 161                      );
 162              }
 163          }
 164  
 165          $this->offsets = $offsets;
 166      }
 167  }