3 // FPDI - Version 1.2.1
5 // Copyright 2004-2008 Setasign - Jan Slabon
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
20 if (!defined ('PDF_TYPE_NULL'))
21 define ('PDF_TYPE_NULL', 0);
22 if (!defined ('PDF_TYPE_NUMERIC'))
23 define ('PDF_TYPE_NUMERIC', 1);
24 if (!defined ('PDF_TYPE_TOKEN'))
25 define ('PDF_TYPE_TOKEN', 2);
26 if (!defined ('PDF_TYPE_HEX'))
27 define ('PDF_TYPE_HEX', 3);
28 if (!defined ('PDF_TYPE_STRING'))
29 define ('PDF_TYPE_STRING', 4);
30 if (!defined ('PDF_TYPE_DICTIONARY'))
31 define ('PDF_TYPE_DICTIONARY', 5);
32 if (!defined ('PDF_TYPE_ARRAY'))
33 define ('PDF_TYPE_ARRAY', 6);
34 if (!defined ('PDF_TYPE_OBJDEC'))
35 define ('PDF_TYPE_OBJDEC', 7);
36 if (!defined ('PDF_TYPE_OBJREF'))
37 define ('PDF_TYPE_OBJREF', 8);
38 if (!defined ('PDF_TYPE_OBJECT'))
39 define ('PDF_TYPE_OBJECT', 9);
40 if (!defined ('PDF_TYPE_STREAM'))
41 define ('PDF_TYPE_STREAM', 10);
42 if (!defined ('PDF_TYPE_BOOLEAN'))
43 define ('PDF_TYPE_BOOLEAN', 11);
44 if (!defined ('PDF_TYPE_REAL'))
45 define ('PDF_TYPE_REAL', 12);
47 require_once("pdf_context.php");
48 require_once("wrapper_functions.php");
66 * @var object pdf_context-Instance
83 * PDF version of the loaded document
91 * @param string $filename Source-Filename
93 function pdf_parser($filename) {
94 $this->filename = $filename;
96 $this->f = @fopen($this->filename, "rb");
99 $this->error(sprintf("Cannot open %s !", $filename));
101 $this->getPDFVersion();
103 $this->c = new pdf_context($this->f);
105 $this->pdf_read_xref($this->xref, $this->pdf_find_xref());
107 // Check for Encryption
108 $this->getEncryption();
111 $this->pdf_read_root();
115 * Close the opened file
117 function closeFile() {
118 if (isset($this->f)) {
125 * Print Error and die
127 * @param string $msg Error-Message
129 function error($msg) {
130 die("<b>PDF-Parser Error:</b> ".$msg);
134 * Check Trailer for Encryption
136 function getEncryption() {
137 if (isset($this->xref['trailer'][1]['/Encrypt'])) {
138 $this->error("File is encrypted!");
147 function pdf_find_root() {
148 if ($this->xref['trailer'][1]['/Root'][0] != PDF_TYPE_OBJREF) {
149 $this->error("Wrong Type of Root-Element! Must be an indirect reference");
151 return $this->xref['trailer'][1]['/Root'];
157 function pdf_read_root() {
159 $this->root = $this->pdf_resolve_object($this->c, $this->pdf_find_root());
165 * And reset the PDF Version used in FPDI if needed
167 function getPDFVersion() {
169 preg_match("/\d\.\d/",fread($this->f,16),$m);
171 $this->pdfVersion = $m[0];
172 return $this->pdfVersion;
176 * Find the xref-Table
178 function pdf_find_xref() {
181 $stat = fseek ($this->f, -$toRead, SEEK_END);
185 $data = fread($this->f, $toRead);
187 $pos = strlen($data) - strpos(strrev($data), strrev('startxref'));
188 $data = substr($data, $pos);
190 if (!preg_match('/\s*(\d+).*$/s', $data, $matches)) {
191 $this->error("Unable to find pointer to xref table");
194 return (int) $matches[1];
200 * @param array $result Array of xref-table
201 * @param integer $offset of xref-table
203 function pdf_read_xref(&$result, $offset) {
204 fseek($this->f, $o_pos = $offset-20); // set some bytes backwards to fetch errorious docs
206 $data = fread($this->f, 100);
208 $xrefPos = strpos($data, 'xref');
210 if ($xrefPos === false) {
211 $this->error('Unable to find xref table.');
214 if (!isset($result['xref_location'])) {
215 $result['xref_location'] = $o_pos+$xrefPos;
216 $result['max_object'] = 0;
220 $bytesPerCycle = 100;
222 fseek($this->f, $o_pos = $o_pos+$xrefPos+4); // set the handle directly after the "xref"-keyword
223 $data = fread($this->f, $bytesPerCycle);
225 while (($trailerPos = strpos($data, 'trailer', max($bytesPerCycle*$cylces++, 0))) === false && !feof($this->f)) {
226 $data .= fread($this->f, $bytesPerCycle);
229 if ($trailerPos === false) {
230 $this->error('Trailer keyword not found after xref table');
233 $data = substr($data, 0, $trailerPos);
236 preg_match_all("/(\r\n|\n|\r)/", substr($data, 0, 100), $m); // check the first 100 bytes for linebreaks
238 $differentLineEndings = count(array_unique($m[0]));
239 if ($differentLineEndings > 1) {
240 $lines = preg_split("/(\r\n|\n|\r)/", $data, -1, PREG_SPLIT_NO_EMPTY);
242 $lines = explode($m[0][1], $data);
245 $data = $differentLineEndings = $m = null;
246 unset($data, $differentLineEndings, $m);
248 $linesCount = count($lines);
252 for ($i = 0; $i < $linesCount; $i++) {
253 $line = trim($lines[$i]);
255 $pieces = explode(" ", $line);
259 $start = (int)$pieces[0];
260 $end = $start+(int)$pieces[1];
261 if ($end > $result['max_object'])
262 $result['max_object'] = $end;
265 if (!isset($result['xref'][$start]))
266 $result['xref'][$start] = array();
268 if (!array_key_exists($gen = (int) $pieces[1], $result['xref'][$start])) {
269 $result['xref'][$start][$gen] = $pieces[2] == 'n' ? (int) $pieces[0] : null;
274 $this->error('Unexpected data in xref table');
279 $lines = $pieces = $line = $start = $end = $gen = null;
280 unset($lines, $pieces, $line, $start, $end, $gen);
282 fseek($this->f, $o_pos+$trailerPos+7);
284 $c = new pdf_context($this->f);
285 $trailer = $this->pdf_read_value($c);
290 if (!isset($result['trailer'])) {
291 $result['trailer'] = $trailer;
294 if (isset($trailer[1]['/Prev'])) {
295 $this->pdf_read_xref($result, $trailer[1]['/Prev'][1]);
307 * @param object $c pdf_context
308 * @param string $token a Token
311 function pdf_read_value(&$c, $token = null) {
312 if (is_null($token)) {
313 $token = $this->pdf_read_token($c);
316 if ($token === false) {
322 // This is a hex string.
323 // Read the value, then the terminator
329 $match = strpos ($c->buffer, '>', $pos);
331 // If you can't find it, try
332 // reading more data from the stream
334 if ($match === false) {
335 if (!$c->increase_length()) {
342 $result = substr ($c->buffer, $c->offset, $match - $c->offset);
343 $c->offset = $match + 1;
345 return array (PDF_TYPE_HEX, $result);
350 // This is a dictionary.
354 // Recurse into this function until we reach
355 // the end of the dictionary.
356 while (($key = $this->pdf_read_token($c)) !== '>>') {
357 if ($key === false) {
361 if (($value = $this->pdf_read_value($c)) === false) {
364 $result[$key] = $value;
367 return array (PDF_TYPE_DICTIONARY, $result);
374 // Recurse into this function until we reach
375 // the end of the array.
376 while (($token = $this->pdf_read_token($c)) !== ']') {
377 if ($token === false) {
381 if (($value = $this->pdf_read_value($c, $token)) === false) {
388 return array (PDF_TYPE_ARRAY, $result);
396 for (; $openBrackets != 0 && $pos < $c->length; $pos++) {
397 switch (ord($c->buffer[$pos])) {
404 case 0x5C: // backslash
408 } while($openBrackets != 0 && $c->increase_length());
410 $result = substr($c->buffer, $c->offset, $pos - $c->offset - 1);
413 return array (PDF_TYPE_STRING, $result);
417 $o_pos = ftell($c->file)-strlen($c->buffer);
418 $o_offset = $c->offset;
420 $c->reset($startpos = $o_pos + $o_offset);
422 $e = 0; // ensure line breaks in front of the stream
423 if ($c->buffer[0] == chr(10) || $c->buffer[0] == chr(13))
425 if ($c->buffer[1] == chr(10) && $c->buffer[0] != chr(10))
428 if ($this->actual_obj[1][1]['/Length'][0] == PDF_TYPE_OBJREF) {
429 $tmp_c = new pdf_context($this->f);
430 $tmp_length = $this->pdf_resolve_object($tmp_c,$this->actual_obj[1][1]['/Length']);
431 $length = $tmp_length[1][1];
433 $length = $this->actual_obj[1][1]['/Length'][1];
437 $c->reset($startpos+$e,$length);
442 $c->reset($startpos+$e+$length+9); // 9 = strlen("endstream")
444 return array(PDF_TYPE_STREAM, $v);
447 // this is a comment - just jump over it
450 // PHP 4.3.3 required
451 #$match = preg_match("/(\r\n|\r|\n)/", $c->buffer, $m, PREG_OFFSET_CAPTURE, $pos);
453 $match = preg_match("/(\r\n|\r|\n)/", substr($c->buffer, $pos), $m);
454 if ($match === false) {
455 if (!$c->increase_length()) {
462 // PHP 4.3.3 required
463 #$c->offset = $m[0][1]+strlen($m[0][0]);
465 $c->offset = strpos($c->buffer, $m[0], $pos)+strlen($m[0]);
467 return $this->pdf_read_value($c);
471 if (is_numeric ($token)) {
472 // A numeric token. Make sure that
473 // it is not part of something else.
474 if (($tok2 = $this->pdf_read_token ($c)) !== false) {
475 if (is_numeric ($tok2)) {
477 // Two numeric tokens in a row.
478 // In this case, we're probably in
479 // front of either an object reference
480 // or an object specification.
481 // Determine the case and return the data
482 if (($tok3 = $this->pdf_read_token ($c)) !== false) {
485 return array (PDF_TYPE_OBJDEC, (int) $token, (int) $tok2);
487 return array (PDF_TYPE_OBJREF, (int) $token, (int) $tok2);
489 // If we get to this point, that numeric value up
490 // there was just a numeric value. Push the extra
491 // tokens back into the stack and return the value.
492 array_push ($c->stack, $tok3);
496 array_push ($c->stack, $tok2);
499 if ($token === (string)((int)$token))
500 return array (PDF_TYPE_NUMERIC, (int)$token);
502 return array (PDF_TYPE_REAL, (float)$token);
503 } else if ($token == 'true' || $token == 'false') {
504 return array (PDF_TYPE_BOOLEAN, $token == 'true');
507 // Just a token. Return it.
508 return array (PDF_TYPE_TOKEN, $token);
517 * @param object $c pdf_context
518 * @param array $obj_spec The object-data
519 * @param boolean $encapsulate Must set to true, cause the parsing and fpdi use this method only without this para
521 function pdf_resolve_object(&$c, $obj_spec, $encapsulate = true) {
522 // Exit if we get invalid data
523 if (!is_array($obj_spec)) {
527 if ($obj_spec[0] == PDF_TYPE_OBJREF) {
529 // This is a reference, resolve it
530 if (isset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]])) {
532 // Save current file position
533 // This is needed if you want to resolve
534 // references while you're reading another object
535 // (e.g.: if you need to determine the length
538 $old_pos = ftell($c->file);
540 // Reposition the file pointer and
541 // load the object header.
543 $c->reset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]]);
545 $header = $this->pdf_read_value($c,null,true);
547 if ($header[0] != PDF_TYPE_OBJDEC || $header[1] != $obj_spec[1] || $header[2] != $obj_spec[2]) {
548 $this->error("Unable to find object ({$obj_spec[1]}, {$obj_spec[2]}) at expected location");
551 // If we're being asked to store all the information
552 // about the object, we add the object ID and generation
553 // number for later use
554 $this->actual_obj =& $result;
558 'obj' => $obj_spec[1],
559 'gen' => $obj_spec[2]
565 // Now simply read the object data until
566 // we encounter an end-of-object marker
568 $value = $this->pdf_read_value($c);
569 if ($value === false || count($result) > 4) {
570 // in this case the parser coudn't find an endobj so we break here
574 if ($value[0] == PDF_TYPE_TOKEN && $value[1] === 'endobj') {
583 if (isset($result[2][0]) && $result[2][0] == PDF_TYPE_STREAM) {
584 $result[0] = PDF_TYPE_STREAM;
597 * Reads a token from the file
599 * @param object $c pdf_context
602 function pdf_read_token(&$c)
604 // If there is a token available
605 // on the stack, pop it out and
608 if (count($c->stack)) {
609 return array_pop($c->stack);
612 // Strip away any whitespace
615 if (!$c->ensure_content()) {
618 $c->offset += _strspn($c->buffer, " \n\r\t", $c->offset);
619 } while ($c->offset >= $c->length - 1);
621 // Get the first character in the stream
623 $char = $c->buffer[$c->offset++];
632 // This is either an array or literal string
633 // delimiter, Return it
640 // This could either be a hex string or
641 // dictionary delimiter. Determine the
642 // appropriate case and return the token
644 if ($c->buffer[$c->offset] == $char) {
645 if (!$c->ensure_content()) {
649 return $char . $char;
656 // This is "another" type of token (probably
657 // a dictionary entry or a numeric value)
658 // Find the end and return it.
660 if (!$c->ensure_content()) {
666 // Determine the length of the token
668 $pos = _strcspn($c->buffer, " []<>()\r\n\t/", $c->offset);
669 if ($c->offset + $pos <= $c->length - 1) {
672 // If the script reaches this point,
673 // the token may span beyond the end
674 // of the current buffer. Therefore,
675 // we increase the size of the buffer
676 // and try again--just to be safe.
678 $c->increase_length();
682 $result = substr($c->buffer, $c->offset - 1, $pos + 1);