Differences Between: [Versions 310 and 311] [Versions 311 and 403] [Versions 39 and 311]
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\Type; 12 13 use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException; 14 use setasign\Fpdi\PdfParser\Filter\Ascii85; 15 use setasign\Fpdi\PdfParser\Filter\AsciiHex; 16 use setasign\Fpdi\PdfParser\Filter\FilterException; 17 use setasign\Fpdi\PdfParser\Filter\Flate; 18 use setasign\Fpdi\PdfParser\Filter\Lzw; 19 use setasign\Fpdi\PdfParser\PdfParser; 20 use setasign\Fpdi\PdfParser\PdfParserException; 21 use setasign\Fpdi\PdfParser\StreamReader; 22 use setasign\FpdiPdfParser\PdfParser\Filter\Predictor; 23 24 /** 25 * Class representing a PDF stream object 26 */ 27 class PdfStream extends PdfType 28 { 29 /** 30 * Parses a stream from a stream reader. 31 * 32 * @param PdfDictionary $dictionary 33 * @param StreamReader $reader 34 * @param PdfParser $parser Optional to keep backwards compatibility 35 * @return self 36 * @throws PdfTypeException 37 */ 38 public static function parse(PdfDictionary $dictionary, StreamReader $reader, PdfParser $parser = null) 39 { 40 $v = new self(); 41 $v->value = $dictionary; 42 $v->reader = $reader; 43 $v->parser = $parser; 44 45 $offset = $reader->getOffset(); 46 47 // Find the first "newline" 48 while (($firstByte = $reader->getByte($offset)) !== false) { 49 if ($firstByte !== "\n" && $firstByte !== "\r") { 50 $offset++; 51 } else { 52 break; 53 } 54 } 55 56 if ($firstByte === false) { 57 throw new PdfTypeException( 58 'Unable to parse stream data. No newline after the stream keyword found.', 59 PdfTypeException::NO_NEWLINE_AFTER_STREAM_KEYWORD 60 ); 61 } 62 63 $sndByte = $reader->getByte($offset + 1); 64 if ($firstByte === "\n" || $firstByte === "\r") { 65 $offset++; 66 } 67 68 if ($sndByte === "\n" && $firstByte !== "\n") { 69 $offset++; 70 } 71 72 $reader->setOffset($offset); 73 // let's only save the byte-offset and read the stream only when needed 74 $v->stream = $reader->getPosition() + $reader->getOffset(); 75 76 return $v; 77 } 78 79 /** 80 * Helper method to create an instance. 81 * 82 * @param PdfDictionary $dictionary 83 * @param string $stream 84 * @return self 85 */ 86 public static function create(PdfDictionary $dictionary, $stream) 87 { 88 $v = new self(); 89 $v->value = $dictionary; 90 $v->stream = (string) $stream; 91 92 return $v; 93 } 94 95 /** 96 * Ensures that the passed value is a PdfStream instance. 97 * 98 * @param mixed $stream 99 * @return self 100 * @throws PdfTypeException 101 */ 102 public static function ensure($stream) 103 { 104 return PdfType::ensureType(self::class, $stream, 'Stream value expected.'); 105 } 106 107 /** 108 * The stream or its byte-offset position. 109 * 110 * @var int|string 111 */ 112 protected $stream; 113 114 /** 115 * The stream reader instance. 116 * 117 * @var StreamReader|null 118 */ 119 protected $reader; 120 121 /** 122 * The PDF parser instance. 123 * 124 * @var PdfParser 125 */ 126 protected $parser; 127 128 /** 129 * Get the stream data. 130 * 131 * @param bool $cache Whether cache the stream data or not. 132 * @return bool|string 133 * @throws PdfTypeException 134 * @throws CrossReferenceException 135 * @throws PdfParserException 136 */ 137 public function getStream($cache = false) 138 { 139 if (\is_int($this->stream)) { 140 $length = PdfDictionary::get($this->value, 'Length'); 141 if ($this->parser !== null) { 142 $length = PdfType::resolve($length, $this->parser); 143 } 144 145 if (!($length instanceof PdfNumeric) || $length->value === 0) { 146 $this->reader->reset($this->stream, 100000); 147 $buffer = $this->extractStream(); 148 } else { 149 $this->reader->reset($this->stream, $length->value); 150 $buffer = $this->reader->getBuffer(false); 151 if ($this->parser !== null) { 152 $this->reader->reset($this->stream + strlen($buffer)); 153 $this->parser->getTokenizer()->clearStack(); 154 $token = $this->parser->readValue(); 155 if ($token === false || !($token instanceof PdfToken) || $token->value !== 'endstream') { 156 $this->reader->reset($this->stream, 100000); 157 $buffer = $this->extractStream(); 158 $this->reader->reset($this->stream + strlen($buffer)); 159 } 160 } 161 } 162 163 if ($cache === false) { 164 return $buffer; 165 } 166 167 $this->stream = $buffer; 168 $this->reader = null; 169 } 170 171 return $this->stream; 172 } 173 174 /** 175 * Extract the stream "manually". 176 * 177 * @return string 178 * @throws PdfTypeException 179 */ 180 protected function extractStream() 181 { 182 while (true) { 183 $buffer = $this->reader->getBuffer(false); 184 $length = \strpos($buffer, 'endstream'); 185 if ($length === false) { 186 if (!$this->reader->increaseLength(100000)) { 187 throw new PdfTypeException('Cannot extract stream.'); 188 } 189 continue; 190 } 191 break; 192 } 193 194 $buffer = \substr($buffer, 0, $length); 195 $lastByte = \substr($buffer, -1); 196 197 /* Check for EOL marker = 198 * CARRIAGE RETURN (\r) and a LINE FEED (\n) or just a LINE FEED (\n}, 199 * and not by a CARRIAGE RETURN (\r) alone 200 */ 201 if ($lastByte === "\n") { 202 $buffer = \substr($buffer, 0, -1); 203 204 $lastByte = \substr($buffer, -1); 205 if ($lastByte === "\r") { 206 $buffer = \substr($buffer, 0, -1); 207 } 208 } 209 210 // There are streams in the wild, which have only white signs in them but need to be parsed manually due 211 // to a problem encountered before (e.g. Length === 0). We should set them to empty streams to avoid problems 212 // in further processing (e.g. applying of filters). 213 if (trim($buffer) === '') { 214 $buffer = ''; 215 } 216 217 return $buffer; 218 } 219 220 /** 221 * Get the unfiltered stream data. 222 * 223 * @return string 224 * @throws FilterException 225 * @throws PdfParserException 226 */ 227 public function getUnfilteredStream() 228 { 229 $stream = $this->getStream(); 230 $filters = PdfDictionary::get($this->value, 'Filter'); 231 if ($filters instanceof PdfNull) { 232 return $stream; 233 } 234 235 if ($filters instanceof PdfArray) { 236 $filters = $filters->value; 237 } else { 238 $filters = [$filters]; 239 } 240 241 $decodeParams = PdfDictionary::get($this->value, 'DecodeParms'); 242 if ($decodeParams instanceof PdfArray) { 243 $decodeParams = $decodeParams->value; 244 } else { 245 $decodeParams = [$decodeParams]; 246 } 247 248 foreach ($filters as $key => $filter) { 249 if (!($filter instanceof PdfName)) { 250 continue; 251 } 252 253 $decodeParam = null; 254 if (isset($decodeParams[$key])) { 255 $decodeParam = ($decodeParams[$key] instanceof PdfDictionary ? $decodeParams[$key] : null); 256 } 257 258 switch ($filter->value) { 259 case 'FlateDecode': 260 case 'Fl': 261 case 'LZWDecode': 262 case 'LZW': 263 if (\strpos($filter->value, 'LZW') === 0) { 264 $filterObject = new Lzw(); 265 } else { 266 $filterObject = new Flate(); 267 } 268 269 $stream = $filterObject->decode($stream); 270 271 if ($decodeParam instanceof PdfDictionary) { 272 $predictor = PdfDictionary::get($decodeParam, 'Predictor', PdfNumeric::create(1)); 273 if ($predictor->value !== 1) { 274 if (!\class_exists(Predictor::class)) { 275 throw new PdfParserException( 276 'This PDF document makes use of features which are only implemented in the ' . 277 'commercial "FPDI PDF-Parser" add-on (see https://www.setasign.com/fpdi-pdf-' . 278 'parser).', 279 PdfParserException::IMPLEMENTED_IN_FPDI_PDF_PARSER 280 ); 281 } 282 283 $colors = PdfDictionary::get($decodeParam, 'Colors', PdfNumeric::create(1)); 284 $bitsPerComponent = PdfDictionary::get( 285 $decodeParam, 286 'BitsPerComponent', 287 PdfNumeric::create(8) 288 ); 289 290 $columns = PdfDictionary::get($decodeParam, 'Columns', PdfNumeric::create(1)); 291 292 $filterObject = new Predictor( 293 $predictor->value, 294 $colors->value, 295 $bitsPerComponent->value, 296 $columns->value 297 ); 298 299 $stream = $filterObject->decode($stream); 300 } 301 } 302 303 break; 304 case 'ASCII85Decode': 305 case 'A85': 306 $filterObject = new Ascii85(); 307 $stream = $filterObject->decode($stream); 308 break; 309 310 case 'ASCIIHexDecode': 311 case 'AHx': 312 $filterObject = new AsciiHex(); 313 $stream = $filterObject->decode($stream); 314 break; 315 316 default: 317 throw new FilterException( 318 \sprintf('Unsupported filter "%s".', $filter->value), 319 FilterException::UNSUPPORTED_FILTER 320 ); 321 } 322 } 323 324 return $stream; 325 } 326 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body