MDL-42023 assign: Edit PDF plugin - Damyon's contributions
[moodle.git] / mod / assign / feedback / editpdf / fpdi / fpdi.php
CommitLineData
5c386472
DW
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
20define('FPDI_VERSION', '1.4.4');\r
21\r
22// Check for TCPDF and remap TCPDF to FPDF\r
23if (class_exists('TCPDF', false)) {\r
24 require_once('fpdi2tcpdf_bridge.php');\r
25}\r
26\r
27require_once('fpdf_tpl.php');\r
28require_once('fpdi_pdf_parser.php');\r
29\r
30\r
31class 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
588}