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