source: branches/version-2_4/data/module/pdf/pdf_parser.php @ 18394

Revision 18394, 18.5 KB checked in by kajiwara, 14 years ago (diff)

#529 pdf 関連モジュールを適切な場所に配置変更いたしました。

Line 
1<?php
2//
3//  FPDI - Version 1.2
4//
5//    Copyright 2004-2007 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
10//
11//      http://www.apache.org/licenses/LICENSE-2.0
12//
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.
18//
19
20if (!defined ('PDF_TYPE_NULL'))
21    define ('PDF_TYPE_NULL', 0);
22if (!defined ('PDF_TYPE_NUMERIC'))
23    define ('PDF_TYPE_NUMERIC', 1);
24if (!defined ('PDF_TYPE_TOKEN'))
25    define ('PDF_TYPE_TOKEN', 2);
26if (!defined ('PDF_TYPE_HEX'))
27    define ('PDF_TYPE_HEX', 3);
28if (!defined ('PDF_TYPE_STRING'))
29    define ('PDF_TYPE_STRING', 4);
30if (!defined ('PDF_TYPE_DICTIONARY'))
31    define ('PDF_TYPE_DICTIONARY', 5);
32if (!defined ('PDF_TYPE_ARRAY'))
33    define ('PDF_TYPE_ARRAY', 6);
34if (!defined ('PDF_TYPE_OBJDEC'))
35    define ('PDF_TYPE_OBJDEC', 7);
36if (!defined ('PDF_TYPE_OBJREF'))
37    define ('PDF_TYPE_OBJREF', 8);
38if (!defined ('PDF_TYPE_OBJECT'))
39    define ('PDF_TYPE_OBJECT', 9);
40if (!defined ('PDF_TYPE_STREAM'))
41    define ('PDF_TYPE_STREAM', 10);
42
43require_once("pdf_context.php");
44require_once("wrapper_functions.php");
45
46class pdf_parser {
47   
48    /**
49     * Filename
50     * @var string
51     */
52    var $filename;
53   
54    /**
55     * File resource
56     * @var resource
57     */
58    var $f;
59   
60    /**
61     * PDF Context
62     * @var object pdf_context-Instance
63     */
64    var $c;
65   
66    /**
67     * xref-Data
68     * @var array
69     */
70    var $xref;
71
72    /**
73     * root-Object
74     * @var array
75     */
76    var $root;
77   
78   
79    /**
80     * Constructor
81     *
82     * @param string $filename  Source-Filename
83     */
84    function pdf_parser($filename) {
85        $this->filename = $filename;
86       
87        $this->f = @fopen($this->filename, "rb");
88
89        if (!$this->f)
90            $this->error(sprintf("Cannot open %s !", $filename));
91
92        $this->getPDFVersion();
93
94        $this->c =& new pdf_context($this->f);
95        // Read xref-Data
96        $this->pdf_read_xref($this->xref, $this->pdf_find_xref());
97
98        // Check for Encryption
99        $this->getEncryption();
100
101        // Read root
102        $this->pdf_read_root();
103    }
104   
105    /**
106     * Close the opened file
107     */
108    function closeFile() {
109        if (isset($this->f)) {
110            fclose($this->f);   
111            unset($this->f);
112        }   
113    }
114   
115    /**
116     * Print Error and die
117     *
118     * @param string $msg  Error-Message
119     */
120    function error($msg) {
121        die("<b>PDF-Parser Error:</b> ".$msg); 
122    }
123   
124    /**
125     * Check Trailer for Encryption
126     */
127    function getEncryption() {
128        if (isset($this->xref['trailer'][1]['/Encrypt'])) {
129            $this->error("File is encrypted!");
130        }
131    }
132   
133    /**
134     * Find/Return /Root
135     *
136     * @return array
137     */
138    function pdf_find_root() {
139        if ($this->xref['trailer'][1]['/Root'][0] != PDF_TYPE_OBJREF) {
140            $this->error("Wrong Type of Root-Element! Must be an indirect reference");
141        }
142        return $this->xref['trailer'][1]['/Root'];
143    }
144
145    /**
146     * Read the /Root
147     */
148    function pdf_read_root() {
149        // read root
150        $this->root = $this->pdf_resolve_object($this->c, $this->pdf_find_root());
151    }
152   
153    /**
154     * Get PDF-Version
155     *
156     * And reset the PDF Version used in FPDI if needed
157     */
158    function getPDFVersion() {
159        fseek($this->f, 0);
160        preg_match("/\d\.\d/",fread($this->f,16),$m);
161        $this->pdfVersion = $m[0];
162    }
163   
164    /**
165     * Find the xref-Table
166     */
167    function pdf_find_xref() {
168        fseek ($this->f, -min(filesize($this->filename),1500), SEEK_END);
169        $data = fread($this->f, 1500);
170       
171        $pos = strlen($data) - strpos(strrev($data), strrev('startxref'));
172        $data = substr($data, $pos);
173       
174        if (!preg_match('/\s*(\d+).*$/s', $data, $matches)) {
175            $this->error("Unable to find pointer to xref table");
176        }
177
178        return (int) $matches[1];
179    }
180
181    /**
182     * Read xref-table
183     *
184     * @param array $result Array of xref-table
185     * @param integer $offset of xref-table
186     * @param integer $start start-position in xref-table
187     * @param integer $end end-position in xref-table
188     */
189    function pdf_read_xref(&$result, $offset, $start = null, $end = null) {
190        if (is_null ($start) || is_null ($end)) {
191            fseek($this->f, $o_pos = $offset);
192            $data = trim(fgets($this->f,1024));
193                       
194            if (strlen($data) == 0)
195                $data = trim(fgets($this->f,1024));
196                   
197            if ($data !== 'xref') {
198                fseek($this->f, $o_pos);
199                $data = trim(_fgets($this->f, true));
200                if ($data !== 'xref') {
201                    if (preg_match('/(.*xref)(.*)/m', $data, $m)) { // xref 0 128 - in one line
202                        fseek($this->f, $o_pos+strlen($m[1]));                     
203                    } elseif (preg_match('/(x|r|e|f)+/', $data, $m)) { // correct invalid xref-pointer
204                        $tmpOffset = $offset-4+strlen($m[0]);
205                        $this->pdf_read_xref($result, $tmpOffset, $start, $end);
206                        return;
207                    } else {
208                        $this->error("Unable to find xref table - Maybe a Problem with 'auto_detect_line_endings'");
209                    }
210                }
211            }
212
213            $o_pos = ftell($this->f);
214            $data = explode(' ', trim(fgets($this->f,1024)));
215            if (count($data) != 2) {
216                fseek($this->f, $o_pos);
217                $data = explode(' ', trim(_fgets($this->f, true)));
218           
219                if (count($data) != 2) {
220                    if (count($data) > 2) { // no lineending
221                        $n_pos = $o_pos+strlen($data[0])+strlen($data[1])+2;
222                        fseek($this->f, $n_pos);
223                    } else {
224                        $this->error("Unexpected header in xref table");
225                    }
226                }
227            }
228            $start = $data[0];
229            $end = $start + $data[1];
230        }
231
232        if (!isset($result['xref_location'])) {
233            $result['xref_location'] = $offset;
234        }
235
236        if (!isset($result['max_object']) || $end > $result['max_object']) {
237            $result['max_object'] = $end;
238        }
239
240        for (; $start < $end; $start++) {
241            $data = ltrim(fread($this->f, 20)); // Spezifications says: 20 bytes including newlines
242            $offset = substr($data, 0, 10);
243            $generation = substr($data, 11, 5);
244
245            if (!isset ($result['xref'][$start][(int) $generation])) {
246                $result['xref'][$start][(int) $generation] = (int) $offset;
247            }
248        }
249
250        $o_pos = ftell($this->f);
251        $data = fgets($this->f,1024);
252        if (strlen(trim($data)) == 0)
253            $data = fgets($this->f, 1024);
254       
255        if (preg_match("/trailer/",$data)) {
256            if (preg_match("/(.*trailer[ \n\r]*)/",$data,$m)) {
257                fseek($this->f, $o_pos+strlen($m[1]));
258            }
259           
260            $c =&  new pdf_context($this->f);
261            $trailer = $this->pdf_read_value($c);
262           
263            if (isset($trailer[1]['/Prev'])) {
264                $this->pdf_read_xref($result, $trailer[1]['/Prev'][1]);
265                $result['trailer'][1] = array_merge($result['trailer'][1], $trailer[1]);
266            } else {
267                $result['trailer'] = $trailer;
268            }
269        } else {
270            $data = explode(' ', trim($data));
271           
272            if (count($data) != 2) {
273                fseek($this->f, $o_pos);
274                $data = explode(' ', trim (_fgets ($this->f, true)));
275
276                if (count($data) != 2) {
277                    $this->error("Unexpected data in xref table");
278                }
279            }
280           
281            $this->pdf_read_xref($result, null, (int) $data[0], (int) $data[0] + (int) $data[1]);
282        }
283    }
284
285
286    /**
287     * Reads an Value
288     *
289     * @param object $c pdf_context
290     * @param string $token a Token
291     * @return mixed
292     */
293    function pdf_read_value(&$c, $token = null) {
294        if (is_null($token)) {
295            $token = $this->pdf_read_token($c);
296        }
297       
298        if ($token === false) {
299            return false;
300        }
301
302        switch ($token) {
303            case    '<':
304                // This is a hex string.
305                // Read the value, then the terminator
306
307                $pos = $c->offset;
308
309                while(1) {
310
311                    $match = strpos ($c->buffer, '>', $pos);
312               
313                    // If you can't find it, try
314                    // reading more data from the stream
315
316                    if ($match === false) {
317                        if (!$c->increase_length()) {
318                            return false;
319                        } else {
320                            continue;
321                        }
322                    }
323
324                    $result = substr ($c->buffer, $c->offset, $match - $c->offset);
325                    $c->offset = $match+1;
326                   
327                    return array (PDF_TYPE_HEX, $result);
328                }
329               
330                break;
331            case    '<<':
332                // This is a dictionary.
333
334                $result = array();
335
336                // Recurse into this function until we reach
337                // the end of the dictionary.
338                while (($key = $this->pdf_read_token($c)) !== '>>') {
339                    if ($key === false) {
340                        return false;
341                    }
342                   
343                    if (($value =   $this->pdf_read_value($c)) === false) {
344                        return false;
345                    }
346                    $result[$key] = $value;
347                }
348               
349                return array (PDF_TYPE_DICTIONARY, $result);
350
351            case    '[':
352                // This is an array.
353
354                $result = array();
355
356                // Recurse into this function until we reach
357                // the end of the array.
358                while (($token = $this->pdf_read_token($c)) !== ']') {
359                    if ($token === false) {
360                        return false;
361                    }
362                   
363                    if (($value = $this->pdf_read_value($c, $token)) === false) {
364                        return false;
365                    }
366                   
367                    $result[] = $value;
368                }
369               
370                return array (PDF_TYPE_ARRAY, $result);
371
372            case    '('     :
373                // This is a string
374
375                $pos = $c->offset;
376
377                while(1) {
378
379                    // Start by finding the next closed
380                    // parenthesis
381
382                    $match = strpos ($c->buffer, ')', $pos);
383
384                    // If you can't find it, try
385                    // reading more data from the stream
386
387                    if ($match === false) {
388                        if (!$c->increase_length()) {
389                            return false;
390                        } else {
391                            continue;
392                        }
393                    }
394
395                    // Make sure that there is no backslash
396                    // before the parenthesis. If there is,
397                    // move on. Otherwise, return the string.
398                    $esc = preg_match('/([\\\\]+)$/', $tmpresult = substr($c->buffer, $c->offset, $match - $c->offset), $m);
399                   
400                    if ($esc === 0 || strlen($m[1]) % 2 == 0) {
401                        $result = $tmpresult;
402                        $c->offset = $match + 1;
403                        return array (PDF_TYPE_STRING, $result);
404                    } else {
405                        $pos = $match + 1;
406
407                        if ($pos > $c->offset + $c->length) {
408                            $c->increase_length();
409                        }
410                    }                   
411                }
412
413            case "stream":
414                $o_pos = ftell($c->file)-strlen($c->buffer);
415                $o_offset = $c->offset;
416               
417                $c->reset($startpos = $o_pos + $o_offset);
418               
419                $e = 0; // ensure line breaks in front of the stream
420                if ($c->buffer[0] == chr(10) || $c->buffer[0] == chr(13))
421                    $e++;
422                if ($c->buffer[1] == chr(10) && $c->buffer[0] != chr(10))
423                    $e++;
424               
425                if ($this->actual_obj[1][1]['/Length'][0] == PDF_TYPE_OBJREF) {
426                    $tmp_c =& new pdf_context($this->f);
427                    $tmp_length = $this->pdf_resolve_object($tmp_c,$this->actual_obj[1][1]['/Length']);
428                    $length = $tmp_length[1][1];
429                } else {
430                    $length = $this->actual_obj[1][1]['/Length'][1];   
431                }
432               
433                if ($length > 0) {
434                    $c->reset($startpos+$e,$length);
435                    $v = $c->buffer;
436                } else {
437                    $v = '';   
438                }
439                $c->reset($startpos+$e+$length+9); // 9 = strlen("endstream")
440               
441                return array(PDF_TYPE_STREAM, $v);
442               
443            default :
444                if (is_numeric ($token)) {
445                    // A numeric token. Make sure that
446                    // it is not part of something else.
447                    if (($tok2 = $this->pdf_read_token ($c)) !== false) {
448                        if (is_numeric ($tok2)) {
449
450                            // Two numeric tokens in a row.
451                            // In this case, we're probably in
452                            // front of either an object reference
453                            // or an object specification.
454                            // Determine the case and return the data
455                            if (($tok3 = $this->pdf_read_token ($c)) !== false) {
456                                switch ($tok3) {
457                                    case    'obj'   :
458                                        return array (PDF_TYPE_OBJDEC, (int) $token, (int) $tok2);
459                                    case    'R'     :
460                                        return array (PDF_TYPE_OBJREF, (int) $token, (int) $tok2);
461                                }
462                                // If we get to this point, that numeric value up
463                                // there was just a numeric value. Push the extra
464                                // tokens back into the stack and return the value.
465                                array_push ($c->stack, $tok3);
466                            }
467                        }
468
469                        array_push ($c->stack, $tok2);
470                    }
471
472                    return array (PDF_TYPE_NUMERIC, $token);
473                } else {
474
475                    // Just a token. Return it.
476                    return array (PDF_TYPE_TOKEN, $token);
477                }
478
479         }
480    }
481   
482    /**
483     * Resolve an object
484     *
485     * @param object $c pdf_context
486     * @param array $obj_spec The object-data
487     * @param boolean $encapsulate Must set to true, cause the parsing and fpdi use this method only without this para
488     */
489    function pdf_resolve_object(&$c, $obj_spec, $encapsulate = true) {
490        // Exit if we get invalid data
491        if (!is_array($obj_spec)) {
492            return false;
493        }
494
495        if ($obj_spec[0] == PDF_TYPE_OBJREF) {
496
497            // This is a reference, resolve it
498            if (isset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]])) {
499
500                // Save current file position
501                // This is needed if you want to resolve
502                // references while you're reading another object
503                // (e.g.: if you need to determine the length
504                // of a stream)
505
506                $old_pos = ftell($c->file);
507
508                // Reposition the file pointer and
509                // load the object header.
510               
511                $c->reset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]]);
512
513                $header = $this->pdf_read_value($c,null,true);
514
515                if ($header[0] != PDF_TYPE_OBJDEC || $header[1] != $obj_spec[1] || $header[2] != $obj_spec[2]) {
516                    $this->error("Unable to find object ({$obj_spec[1]}, {$obj_spec[2]}) at expected location");
517                }
518
519                // If we're being asked to store all the information
520                // about the object, we add the object ID and generation
521                // number for later use
522                $this->actual_obj =& $result;
523                if ($encapsulate) {
524                    $result = array (
525                        PDF_TYPE_OBJECT,
526                        'obj' => $obj_spec[1],
527                        'gen' => $obj_spec[2]
528                    );
529                } else {
530                    $result = array();
531                }
532
533                // Now simply read the object data until
534                // we encounter an end-of-object marker
535                while(1) {
536                    $value = $this->pdf_read_value($c);
537                    if ($value === false || count($result) > 4) {
538                        // in this case the parser coudn't find an endobj so we break here
539                        break;
540                    }
541
542                    if ($value[0] == PDF_TYPE_TOKEN && $value[1] === 'endobj') {
543                        break;
544                    }
545
546                    $result[] = $value;
547                }
548
549                $c->reset($old_pos);
550
551                if (isset($result[2][0]) && $result[2][0] == PDF_TYPE_STREAM) {
552                    $result[0] = PDF_TYPE_STREAM;
553                }
554
555                return $result;
556            }
557        } else {
558            return $obj_spec;
559        }
560    }
561
562   
563   
564    /**
565     * Reads a token from the file
566     *
567     * @param object $c pdf_context
568     * @return mixed
569     */
570    function pdf_read_token(&$c)
571    {
572        // If there is a token available
573        // on the stack, pop it out and
574        // return it.
575
576        if (count($c->stack)) {
577            return array_pop($c->stack);
578        }
579
580        // Strip away any whitespace
581
582        do {
583            if (!$c->ensure_content()) {
584                return false;
585            }
586            $c->offset += _strspn($c->buffer, " \n\r\t", $c->offset);
587        } while ($c->offset >= $c->length - 1);
588
589        // Get the first character in the stream
590
591        $char = $c->buffer[$c->offset++];
592
593        switch ($char) {
594
595            case '['    :
596            case ']'    :
597            case '('    :
598            case ')'    :
599
600                // This is either an array or literal string
601                // delimiter, Return it
602
603                return $char;
604
605            case '<'    :
606            case '>'    :
607
608                // This could either be a hex string or
609                // dictionary delimiter. Determine the
610                // appropriate case and return the token
611
612                if ($c->buffer[$c->offset] == $char) {
613                    if (!$c->ensure_content()) {
614                        return false;
615                    }
616                    $c->offset++;
617                    return $char . $char;
618                } else {
619                    return $char;
620                }
621
622            default     :
623
624                // This is "another" type of token (probably
625                // a dictionary entry or a numeric value)
626                // Find the end and return it.
627
628                if (!$c->ensure_content()) {
629                    return false;
630                }
631
632                while(1) {
633
634                    // Determine the length of the token
635
636                    $pos = _strcspn($c->buffer, " []<>()\r\n\t/", $c->offset);
637                    if ($c->offset + $pos <= $c->length - 1) {
638                        break;
639                    } else {
640                        // If the script reaches this point,
641                        // the token may span beyond the end
642                        // of the current buffer. Therefore,
643                        // we increase the size of the buffer
644                        // and try again--just to be safe.
645
646                        $c->increase_length();
647                    }
648                }
649
650                $result = substr($c->buffer, $c->offset - 1, $pos + 1);
651
652                $c->offset += $pos;
653                return $result;
654        }
655    }
656
657   
658}
659
660?>
Note: See TracBrowser for help on using the repository browser.