See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 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 FixedReader 18 * 19 * This reader allows a very less overhead parsing of single entries of the cross-reference, because the main entries 20 * are only read when needed and not in a single run. 21 */ 22 class FixedReader extends AbstractReader implements ReaderInterface 23 { 24 /** 25 * @var StreamReader 26 */ 27 protected $reader; 28 29 /** 30 * Data of subsections. 31 * 32 * @var array 33 */ 34 protected $subSections; 35 36 /** 37 * FixedReader constructor. 38 * 39 * @param PdfParser $parser 40 * @throws CrossReferenceException 41 */ 42 public function __construct(PdfParser $parser) 43 { 44 $this->reader = $parser->getStreamReader(); 45 $this->read(); 46 parent::__construct($parser); 47 } 48 49 /** 50 * Get all subsection data. 51 * 52 * @return array 53 */ 54 public function getSubSections() 55 { 56 return $this->subSections; 57 } 58 59 /** 60 * @inheritdoc 61 */ 62 public function getOffsetFor($objectNumber) 63 { 64 foreach ($this->subSections as $offset => list($startObject, $objectCount)) { 65 /** 66 * @var int $startObject 67 * @var int $objectCount 68 */ 69 if ($objectNumber >= $startObject && $objectNumber < ($startObject + $objectCount)) { 70 $position = $offset + 20 * ($objectNumber - $startObject); 71 $this->reader->ensure($position, 20); 72 $line = $this->reader->readBytes(20); 73 if ($line[17] === 'f') { 74 return false; 75 } 76 77 return (int) \substr($line, 0, 10); 78 } 79 } 80 81 return false; 82 } 83 84 /** 85 * Read the cross-reference. 86 * 87 * This reader will only read the subsections in this method. The offsets were resolved individually by this 88 * information. 89 * 90 * @throws CrossReferenceException 91 */ 92 protected function read() 93 { 94 $subSections = []; 95 96 $startObject = $entryCount = $lastLineStart = null; 97 $validityChecked = false; 98 while (($line = $this->reader->readLine(20)) !== false) { 99 if (\strpos($line, 'trailer') !== false) { 100 $this->reader->reset($lastLineStart); 101 break; 102 } 103 104 // jump over if line content doesn't match the expected string 105 if (\sscanf($line, '%d %d', $startObject, $entryCount) !== 2) { 106 continue; 107 } 108 109 $oldPosition = $this->reader->getPosition(); 110 $position = $oldPosition + $this->reader->getOffset(); 111 112 if (!$validityChecked && $entryCount > 0) { 113 $nextLine = $this->reader->readBytes(21); 114 /* Check the next line for maximum of 20 bytes and not longer 115 * By catching 21 bytes and trimming the length should be still 21. 116 */ 117 if (\strlen(\trim($nextLine)) !== 21) { 118 throw new CrossReferenceException( 119 'Cross-reference entries are larger than 20 bytes.', 120 CrossReferenceException::ENTRIES_TOO_LARGE 121 ); 122 } 123 124 /* Check for less than 20 bytes: cut the line to 20 bytes and trim; have to result in exactly 18 bytes. 125 * If it would have less bytes the substring would get the first bytes of the next line which would 126 * evaluate to a 20 bytes long string after trimming. 127 */ 128 if (\strlen(\trim(\substr($nextLine, 0, 20))) !== 18) { 129 throw new CrossReferenceException( 130 'Cross-reference entries are less than 20 bytes.', 131 CrossReferenceException::ENTRIES_TOO_SHORT 132 ); 133 } 134 135 $validityChecked = true; 136 } 137 138 $subSections[$position] = [$startObject, $entryCount]; 139 140 $lastLineStart = $position + $entryCount * 20; 141 $this->reader->reset($lastLineStart); 142 } 143 144 // reset after the last correct parsed line 145 $this->reader->reset($lastLineStart); 146 147 if (\count($subSections) === 0) { 148 throw new CrossReferenceException( 149 'No entries found in cross-reference.', 150 CrossReferenceException::NO_ENTRIES 151 ); 152 } 153 154 $this->subSections = $subSections; 155 } 156 157 /** 158 * Fixes an invalid object number shift. 159 * 160 * This method can be used to repair documents with an invalid subsection header: 161 * 162 * <code> 163 * xref 164 * 1 7 165 * 0000000000 65535 f 166 * 0000000009 00000 n 167 * 0000412075 00000 n 168 * 0000412172 00000 n 169 * 0000412359 00000 n 170 * 0000412417 00000 n 171 * 0000412468 00000 n 172 * </code> 173 * 174 * It shall only be called on the first table. 175 * 176 * @return bool 177 */ 178 public function fixFaultySubSectionShift() 179 { 180 $subSections = $this->getSubSections(); 181 if (\count($subSections) > 1) { 182 return false; 183 } 184 185 $subSection = \current($subSections); 186 if ($subSection[0] != 1) { 187 return false; 188 } 189 190 if ($this->getOffsetFor(1) === false) { 191 foreach ($subSections as $offset => list($startObject, $objectCount)) { 192 $this->subSections[$offset] = [$startObject - 1, $objectCount]; 193 } 194 return true; 195 } 196 197 return false; 198 } 199 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body