diff options
Diffstat (limited to 'vendor/setasign/fpdi/src/PdfParser/CrossReference/FixedReader.php')
-rw-r--r-- | vendor/setasign/fpdi/src/PdfParser/CrossReference/FixedReader.php | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/vendor/setasign/fpdi/src/PdfParser/CrossReference/FixedReader.php b/vendor/setasign/fpdi/src/PdfParser/CrossReference/FixedReader.php new file mode 100644 index 0000000..883feec --- /dev/null +++ b/vendor/setasign/fpdi/src/PdfParser/CrossReference/FixedReader.php @@ -0,0 +1,199 @@ +<?php + +/** + * This file is part of FPDI + * + * @package setasign\Fpdi + * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com) + * @license http://opensource.org/licenses/mit-license The MIT License + */ + +namespace setasign\Fpdi\PdfParser\CrossReference; + +use setasign\Fpdi\PdfParser\PdfParser; +use setasign\Fpdi\PdfParser\StreamReader; + +/** + * Class FixedReader + * + * This reader allows a very less overhead parsing of single entries of the cross-reference, because the main entries + * are only read when needed and not in a single run. + */ +class FixedReader extends AbstractReader implements ReaderInterface +{ + /** + * @var StreamReader + */ + protected $reader; + + /** + * Data of subsections. + * + * @var array + */ + protected $subSections; + + /** + * FixedReader constructor. + * + * @param PdfParser $parser + * @throws CrossReferenceException + */ + public function __construct(PdfParser $parser) + { + $this->reader = $parser->getStreamReader(); + $this->read(); + parent::__construct($parser); + } + + /** + * Get all subsection data. + * + * @return array + */ + public function getSubSections() + { + return $this->subSections; + } + + /** + * @inheritdoc + */ + public function getOffsetFor($objectNumber) + { + foreach ($this->subSections as $offset => list($startObject, $objectCount)) { + /** + * @var int $startObject + * @var int $objectCount + */ + if ($objectNumber >= $startObject && $objectNumber < ($startObject + $objectCount)) { + $position = $offset + 20 * ($objectNumber - $startObject); + $this->reader->ensure($position, 20); + $line = $this->reader->readBytes(20); + if ($line[17] === 'f') { + return false; + } + + return (int) \substr($line, 0, 10); + } + } + + return false; + } + + /** + * Read the cross-reference. + * + * This reader will only read the subsections in this method. The offsets were resolved individually by this + * information. + * + * @throws CrossReferenceException + */ + protected function read() + { + $subSections = []; + + $startObject = $entryCount = $lastLineStart = null; + $validityChecked = false; + while (($line = $this->reader->readLine(20)) !== false) { + if (\strpos($line, 'trailer') !== false) { + $this->reader->reset($lastLineStart); + break; + } + + // jump over if line content doesn't match the expected string + if (\sscanf($line, '%d %d', $startObject, $entryCount) !== 2) { + continue; + } + + $oldPosition = $this->reader->getPosition(); + $position = $oldPosition + $this->reader->getOffset(); + + if (!$validityChecked && $entryCount > 0) { + $nextLine = $this->reader->readBytes(21); + /* Check the next line for maximum of 20 bytes and not longer + * By catching 21 bytes and trimming the length should be still 21. + */ + if (\strlen(\trim($nextLine)) !== 21) { + throw new CrossReferenceException( + 'Cross-reference entries are larger than 20 bytes.', + CrossReferenceException::ENTRIES_TOO_LARGE + ); + } + + /* Check for less than 20 bytes: cut the line to 20 bytes and trim; have to result in exactly 18 bytes. + * If it would have less bytes the substring would get the first bytes of the next line which would + * evaluate to a 20 bytes long string after trimming. + */ + if (\strlen(\trim(\substr($nextLine, 0, 20))) !== 18) { + throw new CrossReferenceException( + 'Cross-reference entries are less than 20 bytes.', + CrossReferenceException::ENTRIES_TOO_SHORT + ); + } + + $validityChecked = true; + } + + $subSections[$position] = [$startObject, $entryCount]; + + $lastLineStart = $position + $entryCount * 20; + $this->reader->reset($lastLineStart); + } + + // reset after the last correct parsed line + $this->reader->reset($lastLineStart); + + if (\count($subSections) === 0) { + throw new CrossReferenceException( + 'No entries found in cross-reference.', + CrossReferenceException::NO_ENTRIES + ); + } + + $this->subSections = $subSections; + } + + /** + * Fixes an invalid object number shift. + * + * This method can be used to repair documents with an invalid subsection header: + * + * <code> + * xref + * 1 7 + * 0000000000 65535 f + * 0000000009 00000 n + * 0000412075 00000 n + * 0000412172 00000 n + * 0000412359 00000 n + * 0000412417 00000 n + * 0000412468 00000 n + * </code> + * + * It shall only be called on the first table. + * + * @return bool + */ + public function fixFaultySubSectionShift() + { + $subSections = $this->getSubSections(); + if (\count($subSections) > 1) { + return false; + } + + $subSection = \current($subSections); + if ($subSection[0] != 1) { + return false; + } + + if ($this->getOffsetFor(1) === false) { + foreach ($subSections as $offset => list($startObject, $objectCount)) { + $this->subSections[$offset] = [$startObject - 1, $objectCount]; + } + return true; + } + + return false; + } +} |