8cae46cf27232108ce78fbaf278c1c76fdb67b58
[moodle.git] / mod / assign / feedback / editpdf / fpdi / fpdi.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 define('FPDI_VERSION', '1.4.4');\r
21 \r
22 // Check for TCPDF and remap TCPDF to FPDF\r
23 if (class_exists('TCPDF', false)) {\r
24     require_once('fpdi2tcpdf_bridge.php');\r
25 }\r
26 \r
27 require_once('fpdf_tpl.php');\r
28 require_once('fpdi_pdf_parser.php');\r
29 \r
30 \r
31 class FPDI extends FPDF_TPL {\r
32     /**\r
33      * Actual filename\r
34      * @var string\r
35      */\r
36     var $current_filename;\r
37 \r
38     /**\r
39      * Parser-Objects\r
40      * @var array\r
41      */\r
42     var $parsers;\r
43     \r
44     /**\r
45      * Current parser\r
46      * @var object\r
47      */\r
48     var $current_parser;\r
49     \r
50     /**\r
51      * object stack\r
52      * @var array\r
53      */\r
54     var $_obj_stack;\r
55     \r
56     /**\r
57      * done object stack\r
58      * @var array\r
59      */\r
60     var $_don_obj_stack;\r
61 \r
62     /**\r
63      * Current Object Id.\r
64      * @var integer\r
65      */\r
66     var $_current_obj_id;\r
67     \r
68     /**\r
69      * The name of the last imported page box\r
70      * @var string\r
71      */\r
72     var $lastUsedPageBox;\r
73     \r
74     /**\r
75      * Cache for imported pages/template ids\r
76      * @var array\r
77      */\r
78     var $_importedPages = array();\r
79     \r
80     /**\r
81      * Set a source-file\r
82      *\r
83      * @param string $filename a valid filename\r
84      * @return int number of available pages\r
85      */\r
86     function setSourceFile($filename) {\r
87         $this->current_filename = $filename;\r
88         \r
89         if (!isset($this->parsers[$filename]))\r
90             $this->parsers[$filename] = $this->_getPdfParser($filename);\r
91         $this->current_parser =& $this->parsers[$filename];\r
92         \r
93         return $this->parsers[$filename]->getPageCount();\r
94     }\r
95     \r
96     /**\r
97      * Returns a PDF parser object\r
98      *\r
99      * @param string $filename\r
100      * @return fpdi_pdf_parser\r
101      */\r
102     function _getPdfParser($filename) {\r
103         return new fpdi_pdf_parser($filename, $this);\r
104     }\r
105     \r
106     /**\r
107      * Get the current PDF version\r
108      *\r
109      * @return string\r
110      */\r
111     function getPDFVersion() {\r
112                 return $this->PDFVersion;\r
113         }\r
114     \r
115         /**\r
116      * Set the PDF version\r
117      *\r
118      * @return string\r
119      */\r
120         function setPDFVersion($version = '1.3') {\r
121                 $this->PDFVersion = $version;\r
122         }\r
123         \r
124     /**\r
125      * Import a page\r
126      *\r
127      * @param int $pageno pagenumber\r
128      * @return int Index of imported page - to use with fpdf_tpl::useTemplate()\r
129      */\r
130     function importPage($pageno, $boxName = '/CropBox') {\r
131         if ($this->_intpl) {\r
132             return $this->error('Please import the desired pages before creating a new template.');\r
133         }\r
134         \r
135         $fn = $this->current_filename;\r
136         \r
137         // check if page already imported\r
138         $pageKey = $fn . '-' . ((int)$pageno) . $boxName;\r
139         if (isset($this->_importedPages[$pageKey]))\r
140             return $this->_importedPages[$pageKey];\r
141         \r
142         $parser =& $this->parsers[$fn];\r
143         $parser->setPageno($pageno);\r
144 \r
145         if (!in_array($boxName, $parser->availableBoxes))\r
146             return $this->Error(sprintf('Unknown box: %s', $boxName));\r
147             \r
148         $pageboxes = $parser->getPageBoxes($pageno, $this->k);\r
149         \r
150         /**\r
151          * MediaBox\r
152          * CropBox: Default -> MediaBox\r
153          * BleedBox: Default -> CropBox\r
154          * TrimBox: Default -> CropBox\r
155          * ArtBox: Default -> CropBox\r
156          */\r
157         if (!isset($pageboxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox'))\r
158             $boxName = '/CropBox';\r
159         if (!isset($pageboxes[$boxName]) && $boxName == '/CropBox')\r
160             $boxName = '/MediaBox';\r
161         \r
162         if (!isset($pageboxes[$boxName]))\r
163             return false;\r
164             \r
165         $this->lastUsedPageBox = $boxName;\r
166         \r
167         $box = $pageboxes[$boxName];\r
168         \r
169         $this->tpl++;\r
170         $this->tpls[$this->tpl] = array();\r
171         $tpl =& $this->tpls[$this->tpl];\r
172         $tpl['parser'] =& $parser;\r
173         $tpl['resources'] = $parser->getPageResources();\r
174         $tpl['buffer'] = $parser->getContent();\r
175         $tpl['box'] = $box;\r
176         \r
177         // To build an array that can be used by PDF_TPL::useTemplate()\r
178         $this->tpls[$this->tpl] = array_merge($this->tpls[$this->tpl], $box);\r
179         \r
180         // An imported page will start at 0,0 everytime. Translation will be set in _putformxobjects()\r
181         $tpl['x'] = 0;\r
182         $tpl['y'] = 0;\r
183         \r
184         // handle rotated pages\r
185         $rotation = $parser->getPageRotation($pageno);\r
186         $tpl['_rotationAngle'] = 0;\r
187         if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) {\r
188                 $steps = $angle / 90;\r
189                 \r
190             $_w = $tpl['w'];\r
191             $_h = $tpl['h'];\r
192             $tpl['w'] = $steps % 2 == 0 ? $_w : $_h;\r
193             $tpl['h'] = $steps % 2 == 0 ? $_h : $_w;\r
194             \r
195             if ($angle < 0)\r
196                 $angle += 360;\r
197             \r
198                 $tpl['_rotationAngle'] = $angle * -1;\r
199         }\r
200         \r
201         $this->_importedPages[$pageKey] = $this->tpl;\r
202         \r
203         return $this->tpl;\r
204     }\r
205     \r
206     /**\r
207      * Returns the last used page box\r
208      *\r
209      * @return string\r
210      */\r
211     function getLastUsedPageBox() {\r
212         return $this->lastUsedPageBox;\r
213     }\r
214     \r
215     \r
216     function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0, $adjustPageSize = false) {\r
217         if ($adjustPageSize == true && is_null($_x) && is_null($_y)) {\r
218             $size = $this->getTemplateSize($tplidx, $_w, $_h);\r
219             $orientation = $size['w'] > $size['h'] ? 'L' : 'P';\r
220             $size = array($size['w'], $size['h']);\r
221             \r
222             if (is_subclass_of($this, 'TCPDF')) {\r
223                 $this->setPageFormat($size, $orientation);\r
224             } else {\r
225                 $size = $this->_getpagesize($size);\r
226                 \r
227                 if($orientation!=$this->CurOrientation || $size[0]!=$this->CurPageSize[0] || $size[1]!=$this->CurPageSize[1])\r
228                                 {\r
229                                         // New size or orientation\r
230                                         if($orientation=='P')\r
231                                         {\r
232                                                 $this->w = $size[0];\r
233                                                 $this->h = $size[1];\r
234                                         }\r
235                                         else\r
236                                         {\r
237                                                 $this->w = $size[1];\r
238                                                 $this->h = $size[0];\r
239                                         }\r
240                                         $this->wPt = $this->w*$this->k;\r
241                                         $this->hPt = $this->h*$this->k;\r
242                                         $this->PageBreakTrigger = $this->h-$this->bMargin;\r
243                                         $this->CurOrientation = $orientation;\r
244                                         $this->CurPageSize = $size;\r
245                                         $this->PageSizes[$this->page] = array($this->wPt, $this->hPt);\r
246                                 }\r
247             } \r
248         }\r
249         \r
250         $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values\r
251         $s = parent::useTemplate($tplidx, $_x, $_y, $_w, $_h);\r
252         $this->_out('Q');\r
253         \r
254         return $s;\r
255     }\r
256     \r
257     /**\r
258      * Private method, that rebuilds all needed objects of source files\r
259      */\r
260     function _putimportedobjects() {\r
261         if (is_array($this->parsers) && count($this->parsers) > 0) {\r
262             foreach($this->parsers AS $filename => $p) {\r
263                 $this->current_parser =& $this->parsers[$filename];\r
264                 if (isset($this->_obj_stack[$filename]) && is_array($this->_obj_stack[$filename])) {\r
265                     while(($n = key($this->_obj_stack[$filename])) !== null) {\r
266                         $nObj = $this->current_parser->pdf_resolve_object($this->current_parser->c, $this->_obj_stack[$filename][$n][1]);\r
267                                                 \r
268                         $this->_newobj($this->_obj_stack[$filename][$n][0]);\r
269                         \r
270                         if ($nObj[0] == PDF_TYPE_STREAM) {\r
271                                                         $this->pdf_write_value($nObj);\r
272                         } else {\r
273                             $this->pdf_write_value($nObj[1]);\r
274                         }\r
275                         \r
276                         $this->_out('endobj');\r
277                         $this->_obj_stack[$filename][$n] = null; // free memory\r
278                         unset($this->_obj_stack[$filename][$n]);\r
279                         reset($this->_obj_stack[$filename]);\r
280                     }\r
281                 }\r
282             }\r
283         }\r
284     }\r
285     \r
286     \r
287     /**\r
288      * Private Method that writes the form xobjects\r
289      */\r
290     function _putformxobjects() {\r
291         $filter=($this->compress) ? '/Filter /FlateDecode ' : '';\r
292             reset($this->tpls);\r
293         foreach($this->tpls AS $tplidx => $tpl) {\r
294             $p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];\r
295                 $this->_newobj();\r
296                 $cN = $this->n; // TCPDF/Protection: rem current "n"\r
297                 \r
298                 $this->tpls[$tplidx]['n'] = $this->n;\r
299                 $this->_out('<<' . $filter . '/Type /XObject');\r
300             $this->_out('/Subtype /Form');\r
301             $this->_out('/FormType 1');\r
302             \r
303             $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]', \r
304                 (isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k,\r
305                 (isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k,\r
306                 (isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k,\r
307                 (isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k\r
308             ));\r
309             \r
310             $c = 1;\r
311             $s = 0;\r
312             $tx = 0;\r
313             $ty = 0;\r
314             \r
315             if (isset($tpl['box'])) {\r
316                 $tx = -$tpl['box']['llx'];\r
317                 $ty = -$tpl['box']['lly']; \r
318                 \r
319                 if ($tpl['_rotationAngle'] <> 0) {\r
320                     $angle = $tpl['_rotationAngle'] * M_PI/180;\r
321                     $c=cos($angle);\r
322                     $s=sin($angle);\r
323                     \r
324                     switch($tpl['_rotationAngle']) {\r
325                         case -90:\r
326                            $tx = -$tpl['box']['lly'];\r
327                            $ty = $tpl['box']['urx'];\r
328                            break;\r
329                         case -180:\r
330                             $tx = $tpl['box']['urx'];\r
331                             $ty = $tpl['box']['ury'];\r
332                             break;\r
333                         case -270:\r
334                                 $tx = $tpl['box']['ury'];\r
335                             $ty = -$tpl['box']['llx'];\r
336                             break;\r
337                     }\r
338                 }\r
339             } elseif ($tpl['x'] != 0 || $tpl['y'] != 0) {\r
340                 $tx = -$tpl['x'] * 2;\r
341                 $ty = $tpl['y'] * 2;\r
342             }\r
343             \r
344             $tx *= $this->k;\r
345             $ty *= $this->k;\r
346             \r
347             if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) {\r
348                 $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]',\r
349                     $c, $s, -$s, $c, $tx, $ty\r
350                 ));\r
351             }\r
352             \r
353             $this->_out('/Resources ');\r
354 \r
355             if (isset($tpl['resources'])) {\r
356                 $this->current_parser =& $tpl['parser'];\r
357                 $this->pdf_write_value($tpl['resources']); // "n" will be changed\r
358             } else {\r
359                 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');\r
360                 if (isset($this->_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) {\r
361                         $this->_out('/Font <<');\r
362                     foreach($this->_res['tpl'][$tplidx]['fonts'] as $font)\r
363                                 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');\r
364                         $this->_out('>>');\r
365                 }\r
366                 if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) || \r
367                    isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls']))\r
368                 {\r
369                     $this->_out('/XObject <<');\r
370                     if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) {\r
371                         foreach($this->_res['tpl'][$tplidx]['images'] as $image)\r
372                                         $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');\r
373                     }\r
374                     if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) {\r
375                         foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl)\r
376                             $this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R');\r
377                     }\r
378                     $this->_out('>>');\r
379                 }\r
380                 $this->_out('>>');\r
381             }\r
382             \r
383             $this->_out('/Group <</Type/Group/S/Transparency>>');\r
384             \r
385             $nN = $this->n; // TCPDF: rem new "n"\r
386             $this->n = $cN; // TCPDF: reset to current "n"\r
387             if (is_subclass_of($this, 'TCPDF')) {\r
388                 $p = $this->_getrawstream($p);\r
389                 $this->_out('/Length ' . strlen($p) . ' >>');\r
390                 $this->_out("stream\n" . $p . "\nendstream");\r
391             } else {\r
392                     $this->_out('/Length ' . strlen($p) . ' >>');\r
393                         $this->_putstream($p);\r
394             }\r
395                 $this->_out('endobj');\r
396                 $this->n = $nN; // TCPDF: reset to new "n"\r
397         }\r
398         \r
399         $this->_putimportedobjects();\r
400     }\r
401 \r
402     /**\r
403      * Rewritten to handle existing own defined objects\r
404      */\r
405     function _newobj($obj_id = false, $onlynewobj = false) {\r
406         if (!$obj_id) {\r
407             $obj_id = ++$this->n;\r
408         }\r
409 \r
410         //Begin a new object\r
411         if (!$onlynewobj) {\r
412             $this->offsets[$obj_id] = is_subclass_of($this, 'TCPDF') ? $this->bufferlen : strlen($this->buffer);\r
413             $this->_out($obj_id . ' 0 obj');\r
414             $this->_current_obj_id = $obj_id; // for later use with encryption\r
415         }\r
416         \r
417         return $obj_id;\r
418     }\r
419 \r
420     /**\r
421      * Writes a value\r
422      * Needed to rebuild the source document\r
423      *\r
424      * @param mixed $value A PDF-Value. Structure of values see cases in this method\r
425      */\r
426     function pdf_write_value(&$value)\r
427     {\r
428         if (is_subclass_of($this, 'TCPDF')) {\r
429             parent::pdf_write_value($value);\r
430         }\r
431         \r
432         switch ($value[0]) {\r
433 \r
434                 case PDF_TYPE_TOKEN:\r
435                 $this->_straightOut($value[1] . ' ');\r
436                         break;\r
437                     case PDF_TYPE_NUMERIC:\r
438                 case PDF_TYPE_REAL:\r
439                 if (is_float($value[1]) && $value[1] != 0) {\r
440                             $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ');\r
441                         } else {\r
442                                 $this->_straightOut($value[1] . ' ');\r
443                         }\r
444                         break;\r
445                         \r
446                 case PDF_TYPE_ARRAY:\r
447 \r
448                         // An array. Output the proper\r
449                         // structure and move on.\r
450 \r
451                         $this->_straightOut('[');\r
452                 for ($i = 0; $i < count($value[1]); $i++) {\r
453                                 $this->pdf_write_value($value[1][$i]);\r
454                         }\r
455 \r
456                         $this->_out(']');\r
457                         break;\r
458 \r
459                 case PDF_TYPE_DICTIONARY:\r
460 \r
461                         // A dictionary.\r
462                         $this->_straightOut('<<');\r
463 \r
464                         reset ($value[1]);\r
465 \r
466                         while (list($k, $v) = each($value[1])) {\r
467                                 $this->_straightOut($k . ' ');\r
468                                 $this->pdf_write_value($v);\r
469                         }\r
470 \r
471                         $this->_straightOut('>>');\r
472                         break;\r
473 \r
474                 case PDF_TYPE_OBJREF:\r
475 \r
476                         // An indirect object reference\r
477                         // Fill the object stack if needed\r
478                         $cpfn =& $this->current_parser->filename;\r
479                         \r
480                         if (!isset($this->_don_obj_stack[$cpfn][$value[1]])) {\r
481                             $this->_newobj(false, true);\r
482                             $this->_obj_stack[$cpfn][$value[1]] = array($this->n, $value);\r
483                     $this->_don_obj_stack[$cpfn][$value[1]] = array($this->n, $value); // Value is maybee obsolete!!!\r
484                 }\r
485                 $objid = $this->_don_obj_stack[$cpfn][$value[1]][0];\r
486 \r
487                         $this->_out($objid . ' 0 R');\r
488                         break;\r
489 \r
490                 case PDF_TYPE_STRING:\r
491 \r
492                         // A string.\r
493                 $this->_straightOut('(' . $value[1] . ')');\r
494 \r
495                         break;\r
496 \r
497                 case PDF_TYPE_STREAM:\r
498 \r
499                         // A stream. First, output the\r
500                         // stream dictionary, then the\r
501                         // stream data itself.\r
502                 $this->pdf_write_value($value[1]);\r
503                         $this->_out('stream');\r
504                         $this->_out($value[2][1]);\r
505                         $this->_out('endstream');\r
506                         break;\r
507                         \r
508             case PDF_TYPE_HEX:\r
509                 $this->_straightOut('<' . $value[1] . '>');\r
510                 break;\r
511 \r
512             case PDF_TYPE_BOOLEAN:\r
513                     $this->_straightOut($value[1] ? 'true ' : 'false ');\r
514                     break;\r
515             \r
516                 case PDF_TYPE_NULL:\r
517                 // The null object.\r
518 \r
519                         $this->_straightOut('null ');\r
520                         break;\r
521         }\r
522     }\r
523     \r
524     \r
525     /**\r
526      * Modified so not each call will add a newline to the output.\r
527      */\r
528     function _straightOut($s) {\r
529         if (!is_subclass_of($this, 'TCPDF')) {\r
530             if($this->state==2)\r
531                         $this->pages[$this->page] .= $s;\r
532                 else\r
533                         $this->buffer .= $s;\r
534         } else {\r
535             if ($this->state == 2) {\r
536                                 if ($this->inxobj) {\r
537                                         // we are inside an XObject template\r
538                                         $this->xobjects[$this->xobjid]['outdata'] .= $s;\r
539                                 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {\r
540                                         // puts data before page footer\r
541                                         $pagebuff = $this->getPageBuffer($this->page);\r
542                                         $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);\r
543                                         $footer = substr($pagebuff, -$this->footerlen[$this->page]);\r
544                                         $this->setPageBuffer($this->page, $page.$s.$footer);\r
545                                         // update footer position\r
546                                         $this->footerpos[$this->page] += strlen($s);\r
547                                 } else {\r
548                                         // set page data\r
549                                         $this->setPageBuffer($this->page, $s, true);\r
550                                 }\r
551                         } elseif ($this->state > 0) {\r
552                                 // set general data\r
553                                 $this->setBuffer($s);\r
554                         }\r
555         }\r
556     }\r
557 \r
558     /**\r
559      * rewritten to close opened parsers\r
560      *\r
561      */\r
562     function _enddoc() {\r
563         parent::_enddoc();\r
564         $this->_closeParsers();\r
565     }\r
566     \r
567     /**\r
568      * close all files opened by parsers\r
569      */\r
570     function _closeParsers() {\r
571         if ($this->state > 2 && count($this->parsers) > 0) {\r
572                 $this->cleanUp();\r
573             return true;\r
574         }\r
575         return false;\r
576     }\r
577     \r
578     /**\r
579      * Removes cylced references and closes the file handles of the parser objects\r
580      */\r
581     function cleanUp() {\r
582         foreach ($this->parsers as $k => $_){\r
583                 $this->parsers[$k]->cleanUp();\r
584                 $this->parsers[$k] = null;\r
585                 unset($this->parsers[$k]);\r
586         }\r
587     }\r