MDL-52336 core: patch PHPExcel for PHP7 compatibility
[moodle.git] / mod / assign / feedback / editpdf / fpdi / fpdi.php
1 <?php\r
2 //\r
3 //  FPDI - Version 1.5.4\r
4 //\r
5 //    Copyright 2004-2015 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 if (!class_exists('FPDF_TPL')) {\r
21     require_once('fpdf_tpl.php');\r
22 }\r
23 \r
24 /**\r
25  * Class FPDI\r
26  */\r
27 class FPDI extends FPDF_TPL\r
28 {\r
29     /**\r
30      * FPDI version\r
31      *\r
32      * @string\r
33      */\r
34     const VERSION = '1.5.3';\r
35 \r
36     /**\r
37      * Actual filename\r
38      *\r
39      * @var string\r
40      */\r
41     public $currentFilename;\r
42 \r
43     /**\r
44      * Parser-Objects\r
45      *\r
46      * @var fpdi_pdf_parser[]\r
47      */\r
48     public $parsers = array();\r
49     \r
50     /**\r
51      * Current parser\r
52      *\r
53      * @var fpdi_pdf_parser\r
54      */\r
55     public $currentParser;\r
56 \r
57     /**\r
58      * The name of the last imported page box\r
59      *\r
60      * @var string\r
61      */\r
62     public $lastUsedPageBox;\r
63 \r
64     /**\r
65      * Object stack\r
66      *\r
67      * @var array\r
68      */\r
69     protected $_objStack;\r
70     \r
71     /**\r
72      * Done object stack\r
73      *\r
74      * @var array\r
75      */\r
76     protected $_doneObjStack;\r
77 \r
78     /**\r
79      * Current Object Id.\r
80      *\r
81      * @var integer\r
82      */\r
83     protected $_currentObjId;\r
84     \r
85     /**\r
86      * Cache for imported pages/template ids\r
87      *\r
88      * @var array\r
89      */\r
90     protected $_importedPages = array();\r
91     \r
92     /**\r
93      * Set a source-file.\r
94      *\r
95      * Depending on the PDF version of the used document the PDF version of the resulting document will\r
96      * be adjusted to the higher version.\r
97      *\r
98      * @param string $filename A valid path to the PDF document from which pages should be imported from\r
99      * @return int The number of pages in the document\r
100      */\r
101     public function setSourceFile($filename)\r
102     {\r
103         $_filename = realpath($filename);\r
104         if (false !== $_filename)\r
105             $filename = $_filename;\r
106 \r
107         $this->currentFilename = $filename;\r
108         \r
109         if (!isset($this->parsers[$filename])) {\r
110             $this->parsers[$filename] = $this->_getPdfParser($filename);\r
111             $this->setPdfVersion(\r
112                 max($this->getPdfVersion(), $this->parsers[$filename]->getPdfVersion())\r
113             );\r
114         }\r
115 \r
116         $this->currentParser = $this->parsers[$filename];\r
117         \r
118         return $this->parsers[$filename]->getPageCount();\r
119     }\r
120     \r
121     /**\r
122      * Returns a PDF parser object\r
123      *\r
124      * @param string $filename\r
125      * @return fpdi_pdf_parser\r
126      */\r
127     protected function _getPdfParser($filename)\r
128     {\r
129         if (!class_exists('fpdi_pdf_parser')) {\r
130             require_once('fpdi_pdf_parser.php');\r
131         }\r
132         return new fpdi_pdf_parser($filename);\r
133     }\r
134     \r
135     /**\r
136      * Get the current PDF version.\r
137      *\r
138      * @return string\r
139      */\r
140     public function getPdfVersion()\r
141     {\r
142                 return $this->PDFVersion;\r
143         }\r
144     \r
145     /**\r
146      * Set the PDF version.\r
147      *\r
148      * @param string $version\r
149      */\r
150     public function setPdfVersion($version = '1.3')\r
151     {\r
152         $this->PDFVersion = sprintf('%.1F', $version);\r
153     }\r
154         \r
155     /**\r
156      * Import a page.\r
157      *\r
158      * The second parameter defines the bounding box that should be used to transform the page into a\r
159      * form XObject.\r
160      *\r
161      * Following values are available: MediaBox, CropBox, BleedBox, TrimBox, ArtBox.\r
162      * If a box is not especially defined its default box will be used:\r
163      *\r
164      * <ul>\r
165      *   <li>CropBox: Default -> MediaBox</li>\r
166      *   <li>BleedBox: Default -> CropBox</li>\r
167      *   <li>TrimBox: Default -> CropBox</li>\r
168      *   <li>ArtBox: Default -> CropBox</li>\r
169      * </ul>\r
170      *\r
171      * It is possible to get the used page box by the {@link getLastUsedPageBox()} method.\r
172      *\r
173      * @param int $pageNo The page number\r
174      * @param string $boxName The boundary box to use when transforming the page into a form XObject\r
175      * @param boolean $groupXObject Define the form XObject as a group XObject to support transparency (if used)\r
176      * @return int An id of the imported page/template to use with e.g. fpdf_tpl::useTemplate()\r
177      * @throws LogicException|InvalidArgumentException\r
178      * @see getLastUsedPageBox()\r
179      */\r
180     public function importPage($pageNo, $boxName = 'CropBox', $groupXObject = true)\r
181     {\r
182         if ($this->_inTpl) {\r
183             throw new LogicException('Please import the desired pages before creating a new template.');\r
184         }\r
185         \r
186         $fn = $this->currentFilename;\r
187         $boxName = '/' . ltrim($boxName, '/');\r
188 \r
189         // check if page already imported\r
190         $pageKey = $fn . '-' . ((int)$pageNo) . $boxName;\r
191         if (isset($this->_importedPages[$pageKey])) {\r
192             return $this->_importedPages[$pageKey];\r
193         }\r
194         \r
195         $parser = $this->parsers[$fn];\r
196         $parser->setPageNo($pageNo);\r
197 \r
198         if (!in_array($boxName, $parser->availableBoxes)) {\r
199             throw new InvalidArgumentException(sprintf('Unknown box: %s', $boxName));\r
200         }\r
201             \r
202         $pageBoxes = $parser->getPageBoxes($pageNo, $this->k);\r
203         \r
204         /**\r
205          * MediaBox\r
206          * CropBox: Default -> MediaBox\r
207          * BleedBox: Default -> CropBox\r
208          * TrimBox: Default -> CropBox\r
209          * ArtBox: Default -> CropBox\r
210          */\r
211         if (!isset($pageBoxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox'))\r
212             $boxName = '/CropBox';\r
213         if (!isset($pageBoxes[$boxName]) && $boxName == '/CropBox')\r
214             $boxName = '/MediaBox';\r
215         \r
216         if (!isset($pageBoxes[$boxName]))\r
217             return false;\r
218             \r
219         $this->lastUsedPageBox = $boxName;\r
220         \r
221         $box = $pageBoxes[$boxName];\r
222         \r
223         $this->tpl++;\r
224         $this->_tpls[$this->tpl] = array();\r
225         $tpl =& $this->_tpls[$this->tpl];\r
226         $tpl['parser'] = $parser;\r
227         $tpl['resources'] = $parser->getPageResources();\r
228         $tpl['buffer'] = $parser->getContent();\r
229         $tpl['box'] = $box;\r
230         $tpl['groupXObject'] = $groupXObject;\r
231         if ($groupXObject) {\r
232             $this->setPdfVersion(max($this->getPdfVersion(), 1.4));\r
233         }\r
234 \r
235         // To build an array that can be used by PDF_TPL::useTemplate()\r
236         $this->_tpls[$this->tpl] = array_merge($this->_tpls[$this->tpl], $box);\r
237         \r
238         // An imported page will start at 0,0 all the time. Translation will be set in _putformxobjects()\r
239         $tpl['x'] = 0;\r
240         $tpl['y'] = 0;\r
241         \r
242         // handle rotated pages\r
243         $rotation = $parser->getPageRotation($pageNo);\r
244         $tpl['_rotationAngle'] = 0;\r
245         if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) {\r
246                 $steps = $angle / 90;\r
247                 \r
248             $_w = $tpl['w'];\r
249             $_h = $tpl['h'];\r
250             $tpl['w'] = $steps % 2 == 0 ? $_w : $_h;\r
251             $tpl['h'] = $steps % 2 == 0 ? $_h : $_w;\r
252             \r
253             if ($angle < 0)\r
254                 $angle += 360;\r
255             \r
256                 $tpl['_rotationAngle'] = $angle * -1;\r
257         }\r
258         \r
259         $this->_importedPages[$pageKey] = $this->tpl;\r
260         \r
261         return $this->tpl;\r
262     }\r
263     \r
264     /**\r
265      * Returns the last used page boundary box.\r
266      *\r
267      * @return string The used boundary box: MediaBox, CropBox, BleedBox, TrimBox or ArtBox\r
268      */\r
269     public function getLastUsedPageBox()\r
270     {\r
271         return $this->lastUsedPageBox;\r
272     }\r
273 \r
274     /**\r
275      * Use a template or imported page in current page or other template.\r
276      *\r
277      * You can use a template in a page or in another template.\r
278      * You can give the used template a new size. All parameters are optional.\r
279      * The width or height is calculated automatically if one is given. If no\r
280      * parameter is given the origin size as defined in beginTemplate() or of\r
281      * the imported page is used.\r
282      *\r
283      * The calculated or used width and height are returned as an array.\r
284      *\r
285      * @param int $tplIdx A valid template-id\r
286      * @param int $x The x-position\r
287      * @param int $y The y-position\r
288      * @param int $w The new width of the template\r
289      * @param int $h The new height of the template\r
290      * @param boolean $adjustPageSize If set to true the current page will be resized to fit the dimensions\r
291      *                                of the template\r
292      *\r
293      * @return array The height and width of the template (array('w' => ..., 'h' => ...))\r
294      * @throws LogicException|InvalidArgumentException\r
295      */\r
296     public function useTemplate($tplIdx, $x = null, $y = null, $w = 0, $h = 0, $adjustPageSize = false)\r
297     {\r
298         if ($adjustPageSize == true && is_null($x) && is_null($y)) {\r
299             $size = $this->getTemplateSize($tplIdx, $w, $h);\r
300             $orientation = $size['w'] > $size['h'] ? 'L' : 'P';\r
301             $size = array($size['w'], $size['h']);\r
302             \r
303             if (is_subclass_of($this, 'TCPDF')) {\r
304                 $this->setPageFormat($size, $orientation);\r
305             } else {\r
306                 $size = $this->_getpagesize($size);\r
307                 \r
308                 if($orientation != $this->CurOrientation ||\r
309                     $size[0] != $this->CurPageSize[0] ||\r
310                     $size[1] != $this->CurPageSize[1]\r
311                 ) {\r
312                                         // New size or orientation\r
313                                         if ($orientation=='P') {\r
314                                                 $this->w = $size[0];\r
315                                                 $this->h = $size[1];\r
316                                         } else {\r
317                                                 $this->w = $size[1];\r
318                                                 $this->h = $size[0];\r
319                                         }\r
320                                         $this->wPt = $this->w * $this->k;\r
321                                         $this->hPt = $this->h * $this->k;\r
322                                         $this->PageBreakTrigger = $this->h - $this->bMargin;\r
323                                         $this->CurOrientation = $orientation;\r
324                                         $this->CurPageSize = $size;\r
325                                         $this->PageSizes[$this->page] = array($this->wPt, $this->hPt);\r
326                                 }\r
327             } \r
328         }\r
329         \r
330         $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values\r
331         $size = parent::useTemplate($tplIdx, $x, $y, $w, $h);\r
332         $this->_out('Q');\r
333         \r
334         return $size;\r
335     }\r
336     \r
337     /**\r
338      * Copy all imported objects to the resulting document.\r
339      */\r
340     protected function _putimportedobjects()\r
341     {\r
342         foreach($this->parsers AS $filename => $p) {\r
343             $this->currentParser = $p;\r
344             if (!isset($this->_objStack[$filename]) || !is_array($this->_objStack[$filename])) {\r
345                 continue;\r
346             }\r
347             while(($n = key($this->_objStack[$filename])) !== null) {\r
348                 try {\r
349                     $nObj = $this->currentParser->resolveObject($this->_objStack[$filename][$n][1]);\r
350                 } catch (Exception $e) {\r
351                     $nObj = array(pdf_parser::TYPE_OBJECT, pdf_parser::TYPE_NULL);\r
352                 }\r
353 \r
354                 $this->_newobj($this->_objStack[$filename][$n][0]);\r
355 \r
356                 if ($nObj[0] == pdf_parser::TYPE_STREAM) {\r
357                     $this->_writeValue($nObj);\r
358                 } else {\r
359                     $this->_writeValue($nObj[1]);\r
360                 }\r
361 \r
362                 $this->_out("\nendobj");\r
363                 $this->_objStack[$filename][$n] = null; // free memory\r
364                 unset($this->_objStack[$filename][$n]);\r
365                 reset($this->_objStack[$filename]);\r
366             }\r
367         }\r
368     }\r
369 \r
370     /**\r
371      * Writes the form XObjects to the PDF document.\r
372      */\r
373     protected function _putformxobjects()\r
374     {\r
375         $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';\r
376             reset($this->_tpls);\r
377         foreach($this->_tpls AS $tplIdx => $tpl) {\r
378             $this->_newobj();\r
379                 $currentN = $this->n; // TCPDF/Protection: rem current "n"\r
380                 \r
381                 $this->_tpls[$tplIdx]['n'] = $this->n;\r
382                 $this->_out('<<' . $filter . '/Type /XObject');\r
383             $this->_out('/Subtype /Form');\r
384             $this->_out('/FormType 1');\r
385             \r
386             $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]', \r
387                 (isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k,\r
388                 (isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k,\r
389                 (isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k,\r
390                 (isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k\r
391             ));\r
392             \r
393             $c = 1;\r
394             $s = 0;\r
395             $tx = 0;\r
396             $ty = 0;\r
397             \r
398             if (isset($tpl['box'])) {\r
399                 $tx = -$tpl['box']['llx'];\r
400                 $ty = -$tpl['box']['lly']; \r
401                 \r
402                 if ($tpl['_rotationAngle'] <> 0) {\r
403                     $angle = $tpl['_rotationAngle'] * M_PI/180;\r
404                     $c = cos($angle);\r
405                     $s = sin($angle);\r
406                     \r
407                     switch($tpl['_rotationAngle']) {\r
408                         case -90:\r
409                            $tx = -$tpl['box']['lly'];\r
410                            $ty = $tpl['box']['urx'];\r
411                            break;\r
412                         case -180:\r
413                             $tx = $tpl['box']['urx'];\r
414                             $ty = $tpl['box']['ury'];\r
415                             break;\r
416                         case -270:\r
417                                 $tx = $tpl['box']['ury'];\r
418                             $ty = -$tpl['box']['llx'];\r
419                             break;\r
420                     }\r
421                 }\r
422             } else if ($tpl['x'] != 0 || $tpl['y'] != 0) {\r
423                 $tx = -$tpl['x'] * 2;\r
424                 $ty = $tpl['y'] * 2;\r
425             }\r
426             \r
427             $tx *= $this->k;\r
428             $ty *= $this->k;\r
429             \r
430             if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) {\r
431                 $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]',\r
432                     $c, $s, -$s, $c, $tx, $ty\r
433                 ));\r
434             }\r
435             \r
436             $this->_out('/Resources ');\r
437 \r
438             if (isset($tpl['resources'])) {\r
439                 $this->currentParser = $tpl['parser'];\r
440                 $this->_writeValue($tpl['resources']); // "n" will be changed\r
441             } else {\r
442 \r
443                 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');\r
444                 if (isset($this->_res['tpl'][$tplIdx])) {\r
445                     $res = $this->_res['tpl'][$tplIdx];\r
446 \r
447                     if (isset($res['fonts']) && count($res['fonts'])) {\r
448                         $this->_out('/Font <<');\r
449                         foreach ($res['fonts'] as $font)\r
450                             $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');\r
451                         $this->_out('>>');\r
452                     }\r
453                     if (isset($res['images']) && count($res['images']) ||\r
454                        isset($res['tpls']) && count($res['tpls']))\r
455                     {\r
456                         $this->_out('/XObject <<');\r
457                         if (isset($res['images'])) {\r
458                             foreach ($res['images'] as $image)\r
459                                 $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');\r
460                         }\r
461                         if (isset($res['tpls'])) {\r
462                             foreach ($res['tpls'] as $i => $_tpl)\r
463                                 $this->_out($this->tplPrefix . $i . ' ' . $_tpl['n'] . ' 0 R');\r
464                         }\r
465                         $this->_out('>>');\r
466                     }\r
467                     $this->_out('>>');\r
468                 }\r
469             }\r
470 \r
471             if (isset($tpl['groupXObject']) && $tpl['groupXObject']) {\r
472                 $this->_out('/Group <</Type/Group/S/Transparency>>');\r
473             }\r
474 \r
475             $newN = $this->n; // TCPDF: rem new "n"\r
476             $this->n = $currentN; // TCPDF: reset to current "n"\r
477 \r
478             $buffer = ($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];\r
479 \r
480             if (is_subclass_of($this, 'TCPDF')) {\r
481                 $buffer = $this->_getrawstream($buffer);\r
482                 $this->_out('/Length ' . strlen($buffer) . ' >>');\r
483                 $this->_out("stream\n" . $buffer . "\nendstream");\r
484             } else {\r
485                     $this->_out('/Length ' . strlen($buffer) . ' >>');\r
486                         $this->_putstream($buffer);\r
487             }\r
488                 $this->_out('endobj');\r
489                 $this->n = $newN; // TCPDF: reset to new "n"\r
490         }\r
491         \r
492         $this->_putimportedobjects();\r
493     }\r
494 \r
495     /**\r
496      * Creates and optionally write the object definition to the document.\r
497      *\r
498      * Rewritten to handle existing own defined objects\r
499      *\r
500      * @param bool $objId\r
501      * @param bool $onlyNewObj\r
502      * @return bool|int\r
503      */\r
504     public function _newobj($objId = false, $onlyNewObj = false)\r
505     {\r
506         if (!$objId) {\r
507             $objId = ++$this->n;\r
508         }\r
509 \r
510         //Begin a new object\r
511         if (!$onlyNewObj) {\r
512             $this->offsets[$objId] = is_subclass_of($this, 'TCPDF') ? $this->bufferlen : strlen($this->buffer);\r
513             $this->_out($objId . ' 0 obj');\r
514             $this->_currentObjId = $objId; // for later use with encryption\r
515         }\r
516         \r
517         return $objId;\r
518     }\r
519 \r
520     /**\r
521      * Writes a PDF value to the resulting document.\r
522      *\r
523      * Needed to rebuild the source document\r
524      *\r
525      * @param mixed $value A PDF-Value. Structure of values see cases in this method\r
526      */\r
527     protected function _writeValue(&$value)\r
528     {\r
529         if (is_subclass_of($this, 'TCPDF')) {\r
530             parent::_prepareValue($value);\r
531         }\r
532         \r
533         switch ($value[0]) {\r
534 \r
535                 case pdf_parser::TYPE_TOKEN:\r
536                 $this->_straightOut($value[1] . ' ');\r
537                         break;\r
538                     case pdf_parser::TYPE_NUMERIC:\r
539                 case pdf_parser::TYPE_REAL:\r
540                 if (is_float($value[1]) && $value[1] != 0) {\r
541                             $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ');\r
542                         } else {\r
543                                 $this->_straightOut($value[1] . ' ');\r
544                         }\r
545                         break;\r
546                         \r
547                 case pdf_parser::TYPE_ARRAY:\r
548 \r
549                         // An array. Output the proper\r
550                         // structure and move on.\r
551 \r
552                         $this->_straightOut('[');\r
553                 for ($i = 0; $i < count($value[1]); $i++) {\r
554                                 $this->_writeValue($value[1][$i]);\r
555                         }\r
556 \r
557                         $this->_out(']');\r
558                         break;\r
559 \r
560                 case pdf_parser::TYPE_DICTIONARY:\r
561 \r
562                         // A dictionary.\r
563                         $this->_straightOut('<<');\r
564 \r
565                         reset ($value[1]);\r
566 \r
567                         while (list($k, $v) = each($value[1])) {\r
568                                 $this->_straightOut($k . ' ');\r
569                                 $this->_writeValue($v);\r
570                         }\r
571 \r
572                         $this->_straightOut('>>');\r
573                         break;\r
574 \r
575                 case pdf_parser::TYPE_OBJREF:\r
576 \r
577                         // An indirect object reference\r
578                         // Fill the object stack if needed\r
579                         $cpfn =& $this->currentParser->filename;\r
580                         if (!isset($this->_doneObjStack[$cpfn][$value[1]])) {\r
581                             $this->_newobj(false, true);\r
582                             $this->_objStack[$cpfn][$value[1]] = array($this->n, $value);\r
583                     $this->_doneObjStack[$cpfn][$value[1]] = array($this->n, $value);\r
584                 }\r
585                 $objId = $this->_doneObjStack[$cpfn][$value[1]][0];\r
586 \r
587                         $this->_out($objId . ' 0 R');\r
588                         break;\r
589 \r
590                 case pdf_parser::TYPE_STRING:\r
591 \r
592                         // A string.\r
593                 $this->_straightOut('(' . $value[1] . ')');\r
594 \r
595                         break;\r
596 \r
597                 case pdf_parser::TYPE_STREAM:\r
598 \r
599                         // A stream. First, output the\r
600                         // stream dictionary, then the\r
601                         // stream data itself.\r
602                 $this->_writeValue($value[1]);\r
603                         $this->_out('stream');\r
604                         $this->_out($value[2][1]);\r
605                         $this->_straightOut("endstream");\r
606                         break;\r
607                         \r
608             case pdf_parser::TYPE_HEX:\r
609                 $this->_straightOut('<' . $value[1] . '>');\r
610                 break;\r
611 \r
612             case pdf_parser::TYPE_BOOLEAN:\r
613                     $this->_straightOut($value[1] ? 'true ' : 'false ');\r
614                     break;\r
615             \r
616                 case pdf_parser::TYPE_NULL:\r
617                 // The null object.\r
618 \r
619                         $this->_straightOut('null ');\r
620                         break;\r
621         }\r
622     }\r
623     \r
624     \r
625     /**\r
626      * Modified _out() method so not each call will add a newline to the output.\r
627      */\r
628     protected function _straightOut($s)\r
629     {\r
630         if (!is_subclass_of($this, 'TCPDF')) {\r
631             if ($this->state == 2) {\r
632                         $this->pages[$this->page] .= $s;\r
633             } else {\r
634                         $this->buffer .= $s;\r
635             }\r
636 \r
637         } else {\r
638             if ($this->state == 2) {\r
639                                 if ($this->inxobj) {\r
640                                         // we are inside an XObject template\r
641                                         $this->xobjects[$this->xobjid]['outdata'] .= $s;\r
642                                 } else if ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {\r
643                                         // puts data before page footer\r
644                                         $pagebuff = $this->getPageBuffer($this->page);\r
645                                         $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);\r
646                                         $footer = substr($pagebuff, -$this->footerlen[$this->page]);\r
647                                         $this->setPageBuffer($this->page, $page . $s . $footer);\r
648                                         // update footer position\r
649                                         $this->footerpos[$this->page] += strlen($s);\r
650                                 } else {\r
651                                         // set page data\r
652                                         $this->setPageBuffer($this->page, $s, true);\r
653                                 }\r
654                         } else if ($this->state > 0) {\r
655                                 // set general data\r
656                                 $this->setBuffer($s);\r
657                         }\r
658         }\r
659     }\r
660 \r
661     /**\r
662      * Ends the document\r
663      *\r
664      * Overwritten to close opened parsers\r
665      */\r
666     public function _enddoc()\r
667     {\r
668         parent::_enddoc();\r
669         $this->_closeParsers();\r
670     }\r
671     \r
672     /**\r
673      * Close all files opened by parsers.\r
674      *\r
675      * @return boolean\r
676      */\r
677     protected function _closeParsers()\r
678     {\r
679         if ($this->state > 2) {\r
680                 $this->cleanUp();\r
681             return true;\r
682         }\r
683 \r
684         return false;\r
685     }\r
686     \r
687     /**\r
688      * Removes cycled references and closes the file handles of the parser objects.\r
689      */\r
690     public function cleanUp()\r
691     {\r
692         while (($parser = array_pop($this->parsers)) !== null) {\r
693             /**\r
694              * @var fpdi_pdf_parser $parser\r
695              */\r
696             $parser->closeFile();\r
697         }\r
698     }\r