Added HTML2PDF and FPDI to the project.
[openemr.git] / library / html2pdf / fpdi / pdf_parser.php
blob4c6dd8e83e3f3763cc3c69e5e6aa90cb6683d73e
1 <?php
2 //
3 // FPDI - Version 1.4.2
4 //
5 // Copyright 2004-2011 Setasign - Jan Slabon
6 //
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');
49 if (!class_exists('pdf_parser', false)) {
51 class pdf_parser {
53 /**
54 * Filename
55 * @var string
57 var $filename;
59 /**
60 * File resource
61 * @var resource
63 var $f;
65 /**
66 * PDF Context
67 * @var object pdf_context-Instance
69 var $c;
71 /**
72 * xref-Data
73 * @var array
75 var $xref;
77 /**
78 * root-Object
79 * @var array
81 var $root;
83 /**
84 * PDF version of the loaded document
85 * @var string
87 var $pdfVersion;
89 /**
90 * For reading encrypted documents and xref/objectstreams are in use
92 * @var boolean
94 var $readPlain = true;
96 /**
97 * Constructor
99 * @param string $filename Source-Filename
101 function pdf_parser($filename) {
102 $this->filename = $filename;
104 $this->f = @fopen($this->filename, 'rb');
106 if (!$this->f)
107 $this->error(sprintf('Cannot open %s !', $filename));
109 $this->getPDFVersion();
111 $this->c = new pdf_context($this->f);
113 // Read xref-Data
114 $this->xref = array();
115 $this->pdf_read_xref($this->xref, $this->pdf_find_xref());
117 // Check for Encryption
118 $this->getEncryption();
120 // Read root
121 $this->pdf_read_root();
125 * Close the opened file
127 function closeFile() {
128 if (isset($this->f) && is_resource($this->f)) {
129 fclose($this->f);
130 unset($this->f);
135 * Print Error and die
137 * @param string $msg Error-Message
139 function error($msg) {
140 die('<b>PDF-Parser Error:</b> ' . $msg);
144 * Check Trailer for Encryption
146 function getEncryption() {
147 if (isset($this->xref['trailer'][1]['/Encrypt'])) {
148 $this->error('File is encrypted!');
153 * Find/Return /Root
155 * @return array
157 function pdf_find_root() {
158 if ($this->xref['trailer'][1]['/Root'][0] != PDF_TYPE_OBJREF) {
159 $this->error('Wrong Type of Root-Element! Must be an indirect reference');
162 return $this->xref['trailer'][1]['/Root'];
166 * Read the /Root
168 function pdf_read_root() {
169 // read root
170 $this->root = $this->pdf_resolve_object($this->c, $this->pdf_find_root());
174 * Get PDF-Version
176 * And reset the PDF Version used in FPDI if needed
178 function getPDFVersion() {
179 fseek($this->f, 0);
180 preg_match('/\d\.\d/',fread($this->f, 16), $m);
181 if (isset($m[0]))
182 $this->pdfVersion = $m[0];
183 return $this->pdfVersion;
187 * Find the xref-Table
189 function pdf_find_xref() {
190 $toRead = 1500;
192 $stat = fseek ($this->f, -$toRead, SEEK_END);
193 if ($stat === -1) {
194 fseek ($this->f, 0);
196 $data = fread($this->f, $toRead);
198 $pos = strlen($data) - strpos(strrev($data), strrev('startxref'));
199 $data = substr($data, $pos);
201 if (!preg_match('/\s*(\d+).*$/s', $data, $matches)) {
202 $this->error('Unable to find pointer to xref table');
205 return (int) $matches[1];
209 * Read xref-table
211 * @param array $result Array of xref-table
212 * @param integer $offset of xref-table
214 function pdf_read_xref(&$result, $offset) {
215 $o_pos = $offset-min(20, $offset);
216 fseek($this->f, $o_pos); // set some bytes backwards to fetch errorious docs
218 $data = fread($this->f, 100);
220 $xrefPos = strrpos($data, 'xref');
222 if ($xrefPos === false) {
223 fseek($this->f, $offset);
224 $c = new pdf_context($this->f);
225 $xrefStreamObjDec = $this->pdf_read_value($c);
227 if (is_array($xrefStreamObjDec) && isset($xrefStreamObjDec[0]) && $xrefStreamObjDec[0] == PDF_TYPE_OBJDEC) {
228 $this->error(sprintf('This document (%s) probably uses a compression technique which is not supported by the free parser shipped with FPDI.', $this->filename));
229 } else {
230 $this->error('Unable to find xref table.');
234 if (!isset($result['xref_location'])) {
235 $result['xref_location'] = $o_pos + $xrefPos;
236 $result['max_object'] = 0;
239 $cylces = -1;
240 $bytesPerCycle = 100;
242 fseek($this->f, $o_pos = $o_pos + $xrefPos + 4); // set the handle directly after the "xref"-keyword
243 $data = fread($this->f, $bytesPerCycle);
245 while (($trailerPos = strpos($data, 'trailer', max($bytesPerCycle * $cylces++, 0))) === false && !feof($this->f)) {
246 $data .= fread($this->f, $bytesPerCycle);
249 if ($trailerPos === false) {
250 $this->error('Trailer keyword not found after xref table');
253 $data = substr($data, 0, $trailerPos);
255 // get Line-Ending
256 preg_match_all("/(\r\n|\n|\r)/", substr($data, 0, 100), $m); // check the first 100 bytes for linebreaks
258 $differentLineEndings = count(array_unique($m[0]));
259 if ($differentLineEndings > 1) {
260 $lines = preg_split("/(\r\n|\n|\r)/", $data, -1, PREG_SPLIT_NO_EMPTY);
261 } else {
262 $lines = explode($m[0][1], $data);
265 $data = $differentLineEndings = $m = null;
266 unset($data, $differentLineEndings, $m);
268 $linesCount = count($lines);
270 $start = 1;
272 for ($i = 0; $i < $linesCount; $i++) {
273 $line = trim($lines[$i]);
274 if ($line) {
275 $pieces = explode(' ', $line);
276 $c = count($pieces);
277 switch($c) {
278 case 2:
279 $start = (int)$pieces[0];
280 $end = $start + (int)$pieces[1];
281 if ($end > $result['max_object'])
282 $result['max_object'] = $end;
283 break;
284 case 3:
285 if (!isset($result['xref'][$start]))
286 $result['xref'][$start] = array();
288 if (!array_key_exists($gen = (int) $pieces[1], $result['xref'][$start])) {
289 $result['xref'][$start][$gen] = $pieces[2] == 'n' ? (int) $pieces[0] : null;
291 $start++;
292 break;
293 default:
294 $this->error('Unexpected data in xref table');
299 $lines = $pieces = $line = $start = $end = $gen = null;
300 unset($lines, $pieces, $line, $start, $end, $gen);
302 fseek($this->f, $o_pos + $trailerPos + 7);
304 $c = new pdf_context($this->f);
305 $trailer = $this->pdf_read_value($c);
307 $c = null;
308 unset($c);
310 if (!isset($result['trailer'])) {
311 $result['trailer'] = $trailer;
314 if (isset($trailer[1]['/Prev'])) {
315 $this->pdf_read_xref($result, $trailer[1]['/Prev'][1]);
318 $trailer = null;
319 unset($trailer);
321 return true;
325 * Reads an Value
327 * @param object $c pdf_context
328 * @param string $token a Token
329 * @return mixed
331 function pdf_read_value(&$c, $token = null) {
332 if (is_null($token)) {
333 $token = $this->pdf_read_token($c);
336 if ($token === false) {
337 return false;
340 switch ($token) {
341 case '<':
342 // This is a hex string.
343 // Read the value, then the terminator
345 $pos = $c->offset;
347 while(1) {
349 $match = strpos ($c->buffer, '>', $pos);
351 // If you can't find it, try
352 // reading more data from the stream
354 if ($match === false) {
355 if (!$c->increase_length()) {
356 return false;
357 } else {
358 continue;
362 $result = substr ($c->buffer, $c->offset, $match - $c->offset);
363 $c->offset = $match + 1;
365 return array (PDF_TYPE_HEX, $result);
368 break;
369 case '<<':
370 // This is a dictionary.
372 $result = array();
374 // Recurse into this function until we reach
375 // the end of the dictionary.
376 while (($key = $this->pdf_read_token($c)) !== '>>') {
377 if ($key === false) {
378 return false;
381 if (($value = $this->pdf_read_value($c)) === false) {
382 return false;
385 // Catch missing value
386 if ($value[0] == PDF_TYPE_TOKEN && $value[1] == '>>') {
387 $result[$key] = array(PDF_TYPE_NULL);
388 break;
391 $result[$key] = $value;
394 return array (PDF_TYPE_DICTIONARY, $result);
396 case '[':
397 // This is an array.
399 $result = array();
401 // Recurse into this function until we reach
402 // the end of the array.
403 while (($token = $this->pdf_read_token($c)) !== ']') {
404 if ($token === false) {
405 return false;
408 if (($value = $this->pdf_read_value($c, $token)) === false) {
409 return false;
412 $result[] = $value;
415 return array (PDF_TYPE_ARRAY, $result);
417 case '(' :
418 // This is a string
419 $pos = $c->offset;
421 $openBrackets = 1;
422 do {
423 for (; $openBrackets != 0 && $pos < $c->length; $pos++) {
424 switch (ord($c->buffer[$pos])) {
425 case 0x28: // '('
426 $openBrackets++;
427 break;
428 case 0x29: // ')'
429 $openBrackets--;
430 break;
431 case 0x5C: // backslash
432 $pos++;
435 } while($openBrackets != 0 && $c->increase_length());
437 $result = substr($c->buffer, $c->offset, $pos - $c->offset - 1);
438 $c->offset = $pos;
440 return array (PDF_TYPE_STRING, $result);
442 case 'stream':
443 $o_pos = ftell($c->file)-strlen($c->buffer);
444 $o_offset = $c->offset;
446 $c->reset($startpos = $o_pos + $o_offset);
448 $e = 0; // ensure line breaks in front of the stream
449 if ($c->buffer[0] == chr(10) || $c->buffer[0] == chr(13))
450 $e++;
451 if ($c->buffer[1] == chr(10) && $c->buffer[0] != chr(10))
452 $e++;
454 if ($this->actual_obj[1][1]['/Length'][0] == PDF_TYPE_OBJREF) {
455 $tmp_c = new pdf_context($this->f);
456 $tmp_length = $this->pdf_resolve_object($tmp_c, $this->actual_obj[1][1]['/Length']);
457 $length = $tmp_length[1][1];
458 } else {
459 $length = $this->actual_obj[1][1]['/Length'][1];
462 if ($length > 0) {
463 $c->reset($startpos + $e,$length);
464 $v = $c->buffer;
465 } else {
466 $v = '';
468 $c->reset($startpos + $e + $length + 9); // 9 = strlen("endstream")
470 return array(PDF_TYPE_STREAM, $v);
472 default :
473 if (is_numeric ($token)) {
474 // A numeric token. Make sure that
475 // it is not part of something else.
476 if (($tok2 = $this->pdf_read_token ($c)) !== false) {
477 if (is_numeric ($tok2)) {
479 // Two numeric tokens in a row.
480 // In this case, we're probably in
481 // front of either an object reference
482 // or an object specification.
483 // Determine the case and return the data
484 if (($tok3 = $this->pdf_read_token ($c)) !== false) {
485 switch ($tok3) {
486 case 'obj':
487 return array (PDF_TYPE_OBJDEC, (int) $token, (int) $tok2);
488 case 'R':
489 return array (PDF_TYPE_OBJREF, (int) $token, (int) $tok2);
491 // If we get to this point, that numeric value up
492 // there was just a numeric value. Push the extra
493 // tokens back into the stack and return the value.
494 array_push ($c->stack, $tok3);
498 array_push ($c->stack, $tok2);
501 if ($token === (string)((int)$token))
502 return array (PDF_TYPE_NUMERIC, (int)$token);
503 else
504 return array (PDF_TYPE_REAL, (float)$token);
505 } else if ($token == 'true' || $token == 'false') {
506 return array (PDF_TYPE_BOOLEAN, $token == 'true');
507 } else if ($token == 'null') {
508 return array (PDF_TYPE_NULL);
509 } else {
510 // Just a token. Return it.
511 return array (PDF_TYPE_TOKEN, $token);
517 * Resolve an object
519 * @param object $c pdf_context
520 * @param array $obj_spec The object-data
521 * @param boolean $encapsulate Must set to true, cause the parsing and fpdi use this method only without this para
523 function pdf_resolve_object(&$c, $obj_spec, $encapsulate = true) {
524 // Exit if we get invalid data
525 if (!is_array($obj_spec)) {
526 $ret = false;
527 return $ret;
530 if ($obj_spec[0] == PDF_TYPE_OBJREF) {
532 // This is a reference, resolve it
533 if (isset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]])) {
535 // Save current file position
536 // This is needed if you want to resolve
537 // references while you're reading another object
538 // (e.g.: if you need to determine the length
539 // of a stream)
541 $old_pos = ftell($c->file);
543 // Reposition the file pointer and
544 // load the object header.
546 $c->reset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]]);
548 $header = $this->pdf_read_value($c);
550 if ($header[0] != PDF_TYPE_OBJDEC || $header[1] != $obj_spec[1] || $header[2] != $obj_spec[2]) {
551 $toSearchFor = $obj_spec[1] . ' ' . $obj_spec[2] . ' obj';
552 if (preg_match('/' . $toSearchFor . '/', $c->buffer)) {
553 $c->offset = strpos($c->buffer, $toSearchFor) + strlen($toSearchFor);
554 // reset stack
555 $c->stack = array();
556 } else {
557 $this->error("Unable to find object ({$obj_spec[1]}, {$obj_spec[2]}) at expected location");
561 // If we're being asked to store all the information
562 // about the object, we add the object ID and generation
563 // number for later use
564 $result = array();
565 $this->actual_obj =& $result;
566 if ($encapsulate) {
567 $result = array (
568 PDF_TYPE_OBJECT,
569 'obj' => $obj_spec[1],
570 'gen' => $obj_spec[2]
574 // Now simply read the object data until
575 // we encounter an end-of-object marker
576 while(1) {
577 $value = $this->pdf_read_value($c);
578 if ($value === false || count($result) > 4) {
579 // in this case the parser coudn't find an endobj so we break here
580 break;
583 if ($value[0] == PDF_TYPE_TOKEN && $value[1] === 'endobj') {
584 break;
587 $result[] = $value;
590 $c->reset($old_pos);
592 if (isset($result[2][0]) && $result[2][0] == PDF_TYPE_STREAM) {
593 $result[0] = PDF_TYPE_STREAM;
596 return $result;
598 } else {
599 return $obj_spec;
606 * Reads a token from the file
608 * @param object $c pdf_context
609 * @return mixed
611 function pdf_read_token(&$c)
613 // If there is a token available
614 // on the stack, pop it out and
615 // return it.
617 if (count($c->stack)) {
618 return array_pop($c->stack);
621 // Strip away any whitespace
623 do {
624 if (!$c->ensure_content()) {
625 return false;
627 $c->offset += strspn($c->buffer, " \n\r\t", $c->offset);
628 } while ($c->offset >= $c->length - 1);
630 // Get the first character in the stream
632 $char = $c->buffer[$c->offset++];
634 switch ($char) {
636 case '[':
637 case ']':
638 case '(':
639 case ')':
641 // This is either an array or literal string
642 // delimiter, Return it
644 return $char;
646 case '<':
647 case '>':
649 // This could either be a hex string or
650 // dictionary delimiter. Determine the
651 // appropriate case and return the token
653 if ($c->buffer[$c->offset] == $char) {
654 if (!$c->ensure_content()) {
655 return false;
657 $c->offset++;
658 return $char . $char;
659 } else {
660 return $char;
663 case '%':
665 // This is a comment - jump over it!
667 $pos = $c->offset;
668 while(1) {
669 $match = preg_match("/(\r\n|\r|\n)/", $c->buffer, $m, PREG_OFFSET_CAPTURE, $pos);
670 if ($match === 0) {
671 if (!$c->increase_length()) {
672 return false;
673 } else {
674 continue;
678 $c->offset = $m[0][1]+strlen($m[0][0]);
680 return $this->pdf_read_token($c);
683 default:
685 // This is "another" type of token (probably
686 // a dictionary entry or a numeric value)
687 // Find the end and return it.
689 if (!$c->ensure_content()) {
690 return false;
693 while(1) {
695 // Determine the length of the token
697 $pos = strcspn($c->buffer, " %[]<>()\r\n\t/", $c->offset);
699 if ($c->offset + $pos <= $c->length - 1) {
700 break;
701 } else {
702 // If the script reaches this point,
703 // the token may span beyond the end
704 // of the current buffer. Therefore,
705 // we increase the size of the buffer
706 // and try again--just to be safe.
708 $c->increase_length();
712 $result = substr($c->buffer, $c->offset - 1, $pos + 1);
714 $c->offset += $pos;
715 return $result;