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