41a0e03c080dd5ca1a31fde5a2c1bf0beaf696d0
[moodle.git] / mod / assign / feedback / editpdf / fpdi / fpdi_pdf_parser.php
1 <?php\r
2 //\r
3 //  FPDI - Version 1.4.4\r
4 //\r
5 //    Copyright 2004-2013 Setasign - Jan Slabon\r
6 //\r
7 //  Licensed under the Apache License, Version 2.0 (the "License");\r
8 //  you may not use this file except in compliance with the License.\r
9 //  You may obtain a copy of the License at\r
10 //\r
11 //      http://www.apache.org/licenses/LICENSE-2.0\r
12 //\r
13 //  Unless required by applicable law or agreed to in writing, software\r
14 //  distributed under the License is distributed on an "AS IS" BASIS,\r
15 //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16 //  See the License for the specific language governing permissions and\r
17 //  limitations under the License.\r
18 //\r
19 \r
20 require_once('pdf_parser.php');\r
21 \r
22 class fpdi_pdf_parser extends pdf_parser {\r
23 \r
24     /**\r
25      * Pages\r
26      * Index beginns at 0\r
27      *\r
28      * @var array\r
29      */\r
30     var $pages;\r
31     \r
32     /**\r
33      * Page count\r
34      * @var integer\r
35      */\r
36     var $page_count;\r
37     \r
38     /**\r
39      * actual page number\r
40      * @var integer\r
41      */\r
42     var $pageno;\r
43     \r
44     /**\r
45      * PDF Version of imported Document\r
46      * @var string\r
47      */\r
48     var $pdfVersion;\r
49     \r
50     /**\r
51      * FPDI Reference\r
52      * @var object\r
53      */\r
54     var $fpdi;\r
55     \r
56     /**\r
57      * Available BoxTypes\r
58      *\r
59      * @var array\r
60      */\r
61     var $availableBoxes = array('/MediaBox', '/CropBox', '/BleedBox', '/TrimBox', '/ArtBox');\r
62         \r
63     /**\r
64      * Constructor\r
65      *\r
66      * @param string $filename  Source-Filename\r
67      * @param object $fpdi      Object of type fpdi\r
68      */\r
69     function fpdi_pdf_parser($filename, &$fpdi) {\r
70         $this->fpdi =& $fpdi;\r
71                 \r
72         parent::pdf_parser($filename);\r
73 \r
74         // resolve Pages-Dictonary\r
75         $pages = $this->pdf_resolve_object($this->c, $this->root[1][1]['/Pages']);\r
76 \r
77         // Read pages\r
78         $this->read_pages($this->c, $pages, $this->pages);\r
79         \r
80         // count pages;\r
81         $this->page_count = count($this->pages);\r
82     }\r
83     \r
84     /**\r
85      * Removes reference to fpdi object and closes the file handle\r
86      */\r
87     function cleanUp() {\r
88         $this->fpdi = null;\r
89         $this->closeFile();\r
90     }\r
91     \r
92     /**\r
93      * Overwrite parent::error()\r
94      *\r
95      * @param string $msg  Error-Message\r
96      */\r
97     function error($msg) {\r
98         $this->fpdi->error($msg);       \r
99     }\r
100     \r
101     /**\r
102      * Get pagecount from sourcefile\r
103      *\r
104      * @return int\r
105      */\r
106     function getPageCount() {\r
107         return $this->page_count;\r
108     }\r
109 \r
110 \r
111     /**\r
112      * Set pageno\r
113      *\r
114      * @param int $pageno Pagenumber to use\r
115      */\r
116     function setPageno($pageno) {\r
117         $pageno = ((int) $pageno) - 1;\r
118 \r
119         if ($pageno < 0 || $pageno >= $this->getPageCount()) {\r
120             $this->fpdi->error('Pagenumber is wrong!');\r
121         }\r
122 \r
123         $this->pageno = $pageno;\r
124     }\r
125     \r
126     /**\r
127      * Get page-resources from current page\r
128      *\r
129      * @return array\r
130      */\r
131     function getPageResources() {\r
132         return $this->_getPageResources($this->pages[$this->pageno]);\r
133     }\r
134     \r
135     /**\r
136      * Get page-resources from /Page\r
137      *\r
138      * @param array $obj Array of pdf-data\r
139      */\r
140     function _getPageResources ($obj) { // $obj = /Page\r
141         $obj = $this->pdf_resolve_object($this->c, $obj);\r
142 \r
143         // If the current object has a resources\r
144         // dictionary associated with it, we use\r
145         // it. Otherwise, we move back to its\r
146         // parent object.\r
147         if (isset ($obj[1][1]['/Resources'])) {\r
148                 $res = $this->pdf_resolve_object($this->c, $obj[1][1]['/Resources']);\r
149                 if ($res[0] == PDF_TYPE_OBJECT)\r
150                 return $res[1];\r
151             return $res;\r
152         } else {\r
153                 if (!isset ($obj[1][1]['/Parent'])) {\r
154                         return false;\r
155                 } else {\r
156                 $res = $this->_getPageResources($obj[1][1]['/Parent']);\r
157                 if ($res[0] == PDF_TYPE_OBJECT)\r
158                     return $res[1];\r
159                 return $res;\r
160                 }\r
161         }\r
162     }\r
163 \r
164 \r
165     /**\r
166      * Get content of current page\r
167      *\r
168      * If more /Contents is an array, the streams are concated\r
169      *\r
170      * @return string\r
171      */\r
172     function getContent() {\r
173         $buffer = '';\r
174         \r
175         if (isset($this->pages[$this->pageno][1][1]['/Contents'])) {\r
176             $contents = $this->_getPageContent($this->pages[$this->pageno][1][1]['/Contents']);\r
177             foreach($contents AS $tmp_content) {\r
178                 $buffer .= $this->_rebuildContentStream($tmp_content) . ' ';\r
179             }\r
180         }\r
181         \r
182         return $buffer;\r
183     }\r
184     \r
185     \r
186     /**\r
187      * Resolve all content-objects\r
188      *\r
189      * @param array $content_ref\r
190      * @return array\r
191      */\r
192     function _getPageContent($content_ref) {\r
193         $contents = array();\r
194         \r
195         if ($content_ref[0] == PDF_TYPE_OBJREF) {\r
196             $content = $this->pdf_resolve_object($this->c, $content_ref);\r
197             if ($content[1][0] == PDF_TYPE_ARRAY) {\r
198                 $contents = $this->_getPageContent($content[1]);\r
199             } else {\r
200                 $contents[] = $content;\r
201             }\r
202         } elseif ($content_ref[0] == PDF_TYPE_ARRAY) {\r
203             foreach ($content_ref[1] AS $tmp_content_ref) {\r
204                 $contents = array_merge($contents,$this->_getPageContent($tmp_content_ref));\r
205             }\r
206         }\r
207 \r
208         return $contents;\r
209     }\r
210 \r
211 \r
212     /**\r
213      * Rebuild content-streams\r
214      *\r
215      * @param array $obj\r
216      * @return string\r
217      */\r
218     function _rebuildContentStream($obj) {\r
219         $filters = array();\r
220         \r
221         if (isset($obj[1][1]['/Filter'])) {\r
222             $_filter = $obj[1][1]['/Filter'];\r
223 \r
224             if ($_filter[0] == PDF_TYPE_OBJREF) {\r
225                 $tmpFilter = $this->pdf_resolve_object($this->c, $_filter);\r
226                 $_filter = $tmpFilter[1];\r
227             }\r
228             \r
229             if ($_filter[0] == PDF_TYPE_TOKEN) {\r
230                 $filters[] = $_filter;\r
231             } elseif ($_filter[0] == PDF_TYPE_ARRAY) {\r
232                 $filters = $_filter[1];\r
233             }\r
234         }\r
235 \r
236         $stream = $obj[2][1];\r
237 \r
238         foreach ($filters AS $_filter) {\r
239             switch ($_filter[1]) {\r
240                 case '/FlateDecode':\r
241                 case '/Fl':\r
242                         // $stream .= "\x0F\x0D"; // in an errorious stream this suffix could work\r
243                         // $stream .= "\x0A";\r
244                         // $stream .= "\x0D";\r
245                         if (function_exists('gzuncompress')) {\r
246                                 $oStream = $stream;\r
247                         $stream = (strlen($stream) > 0) ? @gzuncompress($stream) : '';\r
248                     } else {\r
249                         $this->error(sprintf('To handle %s filter, please compile php with zlib support.',$_filter[1]));\r
250                     }\r
251                     \r
252                     if ($stream === false) {\r
253                         $oStream = substr($oStream, 2);\r
254                         $stream = @gzinflate($oStream);\r
255                         if ($stream == false) {\r
256                                 $this->error('Error while decompressing stream.');\r
257                         }\r
258                     }\r
259                 break;\r
260                 case '/LZWDecode':\r
261                     include_once('filters/FilterLZW_FPDI.php');\r
262                     $decoder = new FilterLZW_FPDI($this->fpdi);\r
263                     $stream = $decoder->decode($stream);\r
264                     break;\r
265                 case '/ASCII85Decode':\r
266                     include_once('filters/FilterASCII85_FPDI.php');\r
267                     $decoder = new FilterASCII85_FPDI($this->fpdi);\r
268                     $stream = $decoder->decode($stream);\r
269                     break;\r
270                 case null:\r
271                     $stream = $stream;\r
272                 break;\r
273                 default:\r
274                     $this->error(sprintf('Unsupported Filter: %s',$_filter[1]));\r
275             }\r
276         }\r
277         \r
278         return $stream;\r
279     }\r
280     \r
281     \r
282     /**\r
283      * Get a Box from a page\r
284      * Arrayformat is same as used by fpdf_tpl\r
285      *\r
286      * @param array $page a /Page\r
287      * @param string $box_index Type of Box @see $availableBoxes\r
288      * @param float Scale factor from user space units to points\r
289      * @return array\r
290      */\r
291     function getPageBox($page, $box_index, $k) {\r
292         $page = $this->pdf_resolve_object($this->c, $page);\r
293         $box = null;\r
294         if (isset($page[1][1][$box_index]))\r
295             $box =& $page[1][1][$box_index];\r
296         \r
297         if (!is_null($box) && $box[0] == PDF_TYPE_OBJREF) {\r
298             $tmp_box = $this->pdf_resolve_object($this->c, $box);\r
299             $box = $tmp_box[1];\r
300         }\r
301             \r
302         if (!is_null($box) && $box[0] == PDF_TYPE_ARRAY) {\r
303             $b =& $box[1];\r
304             return array('x' => $b[0][1] / $k,\r
305                          'y' => $b[1][1] / $k,\r
306                          'w' => abs($b[0][1] - $b[2][1]) / $k,\r
307                          'h' => abs($b[1][1] - $b[3][1]) / $k,\r
308                          'llx' => min($b[0][1], $b[2][1]) / $k,\r
309                          'lly' => min($b[1][1], $b[3][1]) / $k,\r
310                          'urx' => max($b[0][1], $b[2][1]) / $k,\r
311                          'ury' => max($b[1][1], $b[3][1]) / $k,\r
312                          );\r
313         } elseif (!isset ($page[1][1]['/Parent'])) {\r
314             return false;\r
315         } else {\r
316             return $this->getPageBox($this->pdf_resolve_object($this->c, $page[1][1]['/Parent']), $box_index, $k);\r
317         }\r
318     }\r
319 \r
320     /**\r
321      * Get all page boxes by page no\r
322      * \r
323      * @param int The page number\r
324      * @param float Scale factor from user space units to points\r
325      * @return array\r
326      */\r
327      function getPageBoxes($pageno, $k) {\r
328         return $this->_getPageBoxes($this->pages[$pageno - 1], $k);\r
329     }\r
330     \r
331     /**\r
332      * Get all boxes from /Page\r
333      *\r
334      * @param array a /Page\r
335      * @return array\r
336      */\r
337     function _getPageBoxes($page, $k) {\r
338         $boxes = array();\r
339 \r
340         foreach($this->availableBoxes AS $box) {\r
341             if ($_box = $this->getPageBox($page, $box, $k)) {\r
342                 $boxes[$box] = $_box;\r
343             }\r
344         }\r
345 \r
346         return $boxes;\r
347     }\r
348 \r
349     /**\r
350      * Get the page rotation by pageno\r
351      *\r
352      * @param integer $pageno\r
353      * @return array\r
354      */\r
355     function getPageRotation($pageno) {\r
356         return $this->_getPageRotation($this->pages[$pageno - 1]);\r
357     }\r
358     \r
359     function _getPageRotation($obj) { // $obj = /Page\r
360         $obj = $this->pdf_resolve_object($this->c, $obj);\r
361         if (isset ($obj[1][1]['/Rotate'])) {\r
362                 $res = $this->pdf_resolve_object($this->c, $obj[1][1]['/Rotate']);\r
363                 if ($res[0] == PDF_TYPE_OBJECT)\r
364                 return $res[1];\r
365             return $res;\r
366         } else {\r
367                 if (!isset ($obj[1][1]['/Parent'])) {\r
368                         return false;\r
369                 } else {\r
370                 $res = $this->_getPageRotation($obj[1][1]['/Parent']);\r
371                 if ($res[0] == PDF_TYPE_OBJECT)\r
372                     return $res[1];\r
373                 return $res;\r
374                 }\r
375         }\r
376     }\r
377     \r
378     /**\r
379      * Read all /Page(es)\r
380      *\r
381      * @param object pdf_context\r
382      * @param array /Pages\r
383      * @param array the result-array\r
384      */\r
385     function read_pages(&$c, &$pages, &$result) {\r
386         // Get the kids dictionary\r
387         $_kids = $this->pdf_resolve_object ($c, $pages[1][1]['/Kids']);\r
388         \r
389         if (!is_array($_kids))\r
390             $this->error('Cannot find /Kids in current /Page-Dictionary');\r
391             \r
392         if ($_kids[1][0] == PDF_TYPE_ARRAY) {\r
393             $kids = $_kids[1][1];\r
394         } else {\r
395             $kids = $_kids[1];\r
396         }\r
397         \r
398         foreach ($kids as $v) {\r
399                 $pg = $this->pdf_resolve_object ($c, $v);\r
400             if ($pg[1][1]['/Type'][1] === '/Pages') {\r
401                 // If one of the kids is an embedded\r
402                         // /Pages array, resolve it as well.\r
403                 if ($pg !== $pages) {\r
404                     $this->read_pages($c, $pg, $result);\r
405                 }\r
406                 } else {\r
407                         $result[] = $pg;\r
408                 }\r
409         }\r
410     }\r
411 \r
412     \r
413     \r
414     /**\r
415      * Get PDF-Version\r
416      *\r
417      * And reset the PDF Version used in FPDI if needed\r
418      */\r
419     function getPDFVersion() {\r
420         parent::getPDFVersion();\r
421         $this->fpdi->setPDFVersion(max($this->fpdi->getPDFVersion(), $this->pdfVersion));\r
422     }\r
423 }\r