AutorÃa | Ultima modificación | Ver Log |
<?php/*** This file is part of FPDI** @package setasign\Fpdi* @copyright Copyright (c) 2024 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* @return int|false*/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 stringif (\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;}}