See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Library code for manipulating PDFs 19 * 20 * @package assignfeedback_editpdf 21 * @copyright 2012 Davo Smith 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace assignfeedback_editpdf; 26 use setasign\Fpdi\TcpdfFpdi; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 global $CFG; 31 require_once($CFG->libdir.'/pdflib.php'); 32 require_once($CFG->dirroot.'/mod/assign/feedback/editpdf/fpdi/autoload.php'); 33 34 /** 35 * Library code for manipulating PDFs 36 * 37 * @package assignfeedback_editpdf 38 * @copyright 2012 Davo Smith 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class pdf extends TcpdfFpdi { 42 43 /** @var int the number of the current page in the PDF being processed */ 44 protected $currentpage = 0; 45 /** @var int the total number of pages in the PDF being processed */ 46 protected $pagecount = 0; 47 /** @var float used to scale the pixel position of annotations (in the database) to the position in the final PDF */ 48 protected $scale = 0.0; 49 /** @var string the path in which to store generated page images */ 50 protected $imagefolder = null; 51 /** @var string the path to the PDF currently being processed */ 52 protected $filename = null; 53 54 /** No errors */ 55 const GSPATH_OK = 'ok'; 56 /** Not set */ 57 const GSPATH_EMPTY = 'empty'; 58 /** Does not exist */ 59 const GSPATH_DOESNOTEXIST = 'doesnotexist'; 60 /** Is a dir */ 61 const GSPATH_ISDIR = 'isdir'; 62 /** Not executable */ 63 const GSPATH_NOTEXECUTABLE = 'notexecutable'; 64 /** Test file missing */ 65 const GSPATH_NOTESTFILE = 'notestfile'; 66 /** Any other error */ 67 const GSPATH_ERROR = 'error'; 68 /** Min. width an annotation should have */ 69 const MIN_ANNOTATION_WIDTH = 5; 70 /** Min. height an annotation should have */ 71 const MIN_ANNOTATION_HEIGHT = 5; 72 /** Blank PDF file used during error. */ 73 const BLANK_PDF = '/mod/assign/feedback/editpdf/fixtures/blank.pdf'; 74 /** Page image file name prefix*/ 75 const IMAGE_PAGE = 'image_page'; 76 /** 77 * Get the name of the font to use in generated PDF files. 78 * If $CFG->pdfexportfont is set - use it, otherwise use "freesans" as this 79 * open licensed font has wide support for different language charsets. 80 * 81 * @return string 82 */ 83 private function get_export_font_name() { 84 global $CFG; 85 86 $fontname = 'freesans'; 87 if (!empty($CFG->pdfexportfont)) { 88 $fontname = $CFG->pdfexportfont; 89 } 90 return $fontname; 91 } 92 93 /** 94 * Combine the given PDF files into a single PDF. Optionally add a coversheet and coversheet fields. 95 * @param string[] $pdflist the filenames of the files to combine 96 * @param string $outfilename the filename to write to 97 * @return int the number of pages in the combined PDF 98 */ 99 public function combine_pdfs($pdflist, $outfilename) { 100 101 raise_memory_limit(MEMORY_EXTRA); 102 $olddebug = error_reporting(0); 103 104 $this->setPageUnit('pt'); 105 $this->setPrintHeader(false); 106 $this->setPrintFooter(false); 107 $this->scale = 72.0 / 100.0; 108 // Use font supporting the widest range of characters. 109 $this->SetFont($this->get_export_font_name(), '', 16.0 * $this->scale, '', true); 110 $this->SetTextColor(0, 0, 0); 111 112 $totalpagecount = 0; 113 114 foreach ($pdflist as $file) { 115 $pagecount = $this->setSourceFile($file); 116 $totalpagecount += $pagecount; 117 for ($i = 1; $i<=$pagecount; $i++) { 118 $this->create_page_from_source($i); 119 } 120 } 121 122 $this->save_pdf($outfilename); 123 error_reporting($olddebug); 124 125 return $totalpagecount; 126 } 127 128 /** 129 * The number of the current page in the PDF being processed 130 * @return int 131 */ 132 public function current_page() { 133 return $this->currentpage; 134 } 135 136 /** 137 * The total number of pages in the PDF being processed 138 * @return int 139 */ 140 public function page_count() { 141 return $this->pagecount; 142 } 143 144 /** 145 * Load the specified PDF and set the initial output configuration 146 * Used when processing comments and outputting a new PDF 147 * @param string $filename the path to the PDF to load 148 * @return int the number of pages in the PDF 149 */ 150 public function load_pdf($filename) { 151 raise_memory_limit(MEMORY_EXTRA); 152 $olddebug = error_reporting(0); 153 154 $this->setPageUnit('pt'); 155 $this->scale = 72.0 / 100.0; 156 $this->SetFont($this->get_export_font_name(), '', 16.0 * $this->scale, '', true); 157 $this->SetFillColor(255, 255, 176); 158 $this->SetDrawColor(0, 0, 0); 159 $this->SetLineWidth(1.0 * $this->scale); 160 $this->SetTextColor(0, 0, 0); 161 $this->setPrintHeader(false); 162 $this->setPrintFooter(false); 163 $this->pagecount = $this->setSourceFile($filename); 164 $this->filename = $filename; 165 166 error_reporting($olddebug); 167 return $this->pagecount; 168 } 169 170 /** 171 * Sets the name of the PDF to process, but only loads the file if the 172 * pagecount is zero (in order to count the number of pages) 173 * Used when generating page images (but not a new PDF) 174 * @param string $filename the path to the PDF to process 175 * @param int $pagecount optional the number of pages in the PDF, if known 176 * @return int the number of pages in the PDF 177 */ 178 public function set_pdf($filename, $pagecount = 0) { 179 if ($pagecount == 0) { 180 return $this->load_pdf($filename); 181 } else { 182 $this->filename = $filename; 183 $this->pagecount = $pagecount; 184 return $pagecount; 185 } 186 } 187 188 /** 189 * Copy the next page from the source file and set it as the current page 190 * @return bool true if successful 191 */ 192 public function copy_page() { 193 if (!$this->filename) { 194 return false; 195 } 196 if ($this->currentpage>=$this->pagecount) { 197 return false; 198 } 199 $this->currentpage++; 200 $this->create_page_from_source($this->currentpage); 201 return true; 202 } 203 204 /** 205 * Create a page from a source PDF. 206 * 207 * @param int $pageno 208 */ 209 protected function create_page_from_source($pageno) { 210 // Get the size (and deduce the orientation) of the next page. 211 $template = $this->importPage($pageno); 212 $size = $this->getTemplateSize($template); 213 214 // Create a page of the required size / orientation. 215 $this->AddPage($size['orientation'], array($size['width'], $size['height'])); 216 // Prevent new page creation when comments are at the bottom of a page. 217 $this->setPageOrientation($size['orientation'], false, 0); 218 // Fill in the page with the original contents from the student. 219 $this->useTemplate($template); 220 } 221 222 /** 223 * Copy all the remaining pages in the file 224 */ 225 public function copy_remaining_pages() { 226 $morepages = true; 227 while ($morepages) { 228 $morepages = $this->copy_page(); 229 } 230 } 231 232 /** 233 * Append all comments to the end of the document. 234 * 235 * @param array $allcomments All comments, indexed by page number (starting at 0). 236 * @return array|bool An array of links to comments, or false. 237 */ 238 public function append_comments($allcomments) { 239 if (!$this->filename) { 240 return false; 241 } 242 243 $this->SetFontSize(12 * $this->scale); 244 $this->SetMargins(100 * $this->scale, 120 * $this->scale, -1, true); 245 $this->SetAutoPageBreak(true, 100 * $this->scale); 246 $this->setHeaderFont(array($this->get_export_font_name(), '', 24 * $this->scale, '', true)); 247 $this->setHeaderMargin(24 * $this->scale); 248 $this->setHeaderData('', 0, '', get_string('commentindex', 'assignfeedback_editpdf')); 249 250 // Add a new page to the document with an appropriate header. 251 $this->setPrintHeader(true); 252 $this->AddPage(); 253 254 // Add the comments. 255 $commentlinks = array(); 256 foreach ($allcomments as $pageno => $comments) { 257 foreach ($comments as $index => $comment) { 258 // Create a link to the current location, which will be added to the marker. 259 $commentlink = $this->AddLink(); 260 $this->SetLink($commentlink, -1); 261 $commentlinks[$pageno][$index] = $commentlink; 262 // Also create a link back to the marker, which will be added here. 263 $markerlink = $this->AddLink(); 264 $this->SetLink($markerlink, $comment->y * $this->scale, $pageno + 1); 265 $label = get_string('commentlabel', 'assignfeedback_editpdf', array('pnum' => $pageno + 1, 'cnum' => $index + 1)); 266 $this->Cell(50 * $this->scale, 0, $label, 0, 0, '', false, $markerlink); 267 $this->MultiCell(0, 0, $comment->rawtext, 0, 'L'); 268 $this->Ln(12 * $this->scale); 269 } 270 // Add an extra line break between pages. 271 $this->Ln(12 * $this->scale); 272 } 273 274 return $commentlinks; 275 } 276 277 /** 278 * Add a comment marker to the specified page. 279 * 280 * @param int $pageno The page number to add markers to (starting at 0). 281 * @param int $index The comment index. 282 * @param int $x The x-coordinate of the marker (in pixels). 283 * @param int $y The y-coordinate of the marker (in pixels). 284 * @param int $link The link identifier pointing to the full comment text. 285 * @param string $colour The fill colour of the marker (red, yellow, green, blue, white, clear). 286 * @return bool Success status. 287 */ 288 public function add_comment_marker($pageno, $index, $x, $y, $link, $colour = 'yellow') { 289 if (!$this->filename) { 290 return false; 291 } 292 293 $fill = ''; 294 $fillopacity = 0.9; 295 switch ($colour) { 296 case 'red': 297 $fill = 'rgb(249, 181, 179)'; 298 break; 299 case 'green': 300 $fill = 'rgb(214, 234, 178)'; 301 break; 302 case 'blue': 303 $fill = 'rgb(203, 217, 237)'; 304 break; 305 case 'white': 306 $fill = 'rgb(255, 255, 255)'; 307 break; 308 case 'clear': 309 $fillopacity = 0; 310 break; 311 default: /* Yellow */ 312 $fill = 'rgb(255, 236, 174)'; 313 } 314 $marker = '@<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 12 12" preserveAspectRatio="xMinYMin meet">' . 315 '<path d="M11 0H1C.4 0 0 .4 0 1v6c0 .6.4 1 1 1h1v4l4-4h5c.6 0 1-.4 1-1V1c0-.6-.4-1-1-1z" fill="' . $fill . '" ' . 316 'fill-opacity="' . $fillopacity . '" stroke="rgb(153, 153, 153)" stroke-width="0.5"/></svg>'; 317 $label = get_string('commentlabel', 'assignfeedback_editpdf', array('pnum' => $pageno + 1, 'cnum' => $index + 1)); 318 319 $x *= $this->scale; 320 $y *= $this->scale; 321 $size = 24 * $this->scale; 322 $this->SetDrawColor(51, 51, 51); 323 $this->SetFontSize(10 * $this->scale); 324 $this->setPage($pageno + 1); 325 326 // Add the marker image. 327 $this->ImageSVG($marker, $x - 0.5, $y - 0.5, $size, $size, $link); 328 329 // Add the label. 330 $this->MultiCell($size * 0.95, 0, $label, 0, 'C', false, 1, $x, $y, true, 0, false, true, $size * 0.60, 'M', true); 331 332 return true; 333 } 334 335 /** 336 * Add a comment to the current page 337 * @param string $text the text of the comment 338 * @param int $x the x-coordinate of the comment (in pixels) 339 * @param int $y the y-coordinate of the comment (in pixels) 340 * @param int $width the width of the comment (in pixels) 341 * @param string $colour optional the background colour of the comment (red, yellow, green, blue, white, clear) 342 * @return bool true if successful (always) 343 */ 344 public function add_comment($text, $x, $y, $width, $colour = 'yellow') { 345 if (!$this->filename) { 346 return false; 347 } 348 $this->SetDrawColor(51, 51, 51); 349 switch ($colour) { 350 case 'red': 351 $this->SetFillColor(249, 181, 179); 352 break; 353 case 'green': 354 $this->SetFillColor(214, 234, 178); 355 break; 356 case 'blue': 357 $this->SetFillColor(203, 217, 237); 358 break; 359 case 'white': 360 $this->SetFillColor(255, 255, 255); 361 break; 362 default: /* Yellow */ 363 $this->SetFillColor(255, 236, 174); 364 break; 365 } 366 367 $x *= $this->scale; 368 $y *= $this->scale; 369 $width *= $this->scale; 370 $text = str_replace('<', '<', $text); 371 $text = str_replace('>', '>', $text); 372 // Draw the text with a border, but no background colour (using a background colour would cause the fill to 373 // appear behind any existing content on the page, hence the extra filled rectangle drawn below). 374 $this->MultiCell($width, 1.0, $text, 0, 'L', 0, 4, $x, $y); /* width, height, text, border, justify, fill, ln, x, y */ 375 if ($colour != 'clear') { 376 $newy = $this->GetY(); 377 // Now we know the final size of the comment, draw a rectangle with the background colour. 378 $this->Rect($x, $y, $width, $newy - $y, 'DF'); 379 // Re-draw the text over the top of the background rectangle. 380 $this->MultiCell($width, 1.0, $text, 0, 'L', 0, 4, $x, $y); /* width, height, text, border, justify, fill, ln, x, y */ 381 } 382 return true; 383 } 384 385 /** 386 * Add an annotation to the current page 387 * @param int $sx starting x-coordinate (in pixels) 388 * @param int $sy starting y-coordinate (in pixels) 389 * @param int $ex ending x-coordinate (in pixels) 390 * @param int $ey ending y-coordinate (in pixels) 391 * @param string $colour optional the colour of the annotation (red, yellow, green, blue, white, black) 392 * @param string $type optional the type of annotation (line, oval, rectangle, highlight, pen, stamp) 393 * @param int[]|string $path optional for 'pen' annotations this is an array of x and y coordinates for 394 * the line, for 'stamp' annotations it is the name of the stamp file (without the path) 395 * @param string $imagefolder - Folder containing stamp images. 396 * @return bool true if successful (always) 397 */ 398 public function add_annotation($sx, $sy, $ex, $ey, $colour = 'yellow', $type = 'line', $path, $imagefolder) { 399 global $CFG; 400 if (!$this->filename) { 401 return false; 402 } 403 switch ($colour) { 404 case 'yellow': 405 $colourarray = array(255, 207, 53); 406 break; 407 case 'green': 408 $colourarray = array(153, 202, 62); 409 break; 410 case 'blue': 411 $colourarray = array(125, 159, 211); 412 break; 413 case 'white': 414 $colourarray = array(255, 255, 255); 415 break; 416 case 'black': 417 $colourarray = array(51, 51, 51); 418 break; 419 default: /* Red */ 420 $colour = 'red'; 421 $colourarray = array(239, 69, 64); 422 break; 423 } 424 $this->SetDrawColorArray($colourarray); 425 426 $sx *= $this->scale; 427 $sy *= $this->scale; 428 $ex *= $this->scale; 429 $ey *= $this->scale; 430 431 $this->SetLineWidth(3.0 * $this->scale); 432 switch ($type) { 433 case 'oval': 434 $rx = abs($sx - $ex) / 2; 435 $ry = abs($sy - $ey) / 2; 436 $sx = min($sx, $ex) + $rx; 437 $sy = min($sy, $ey) + $ry; 438 439 // $rx and $ry should be >= min width and height 440 if ($rx < self::MIN_ANNOTATION_WIDTH) { 441 $rx = self::MIN_ANNOTATION_WIDTH; 442 } 443 if ($ry < self::MIN_ANNOTATION_HEIGHT) { 444 $ry = self::MIN_ANNOTATION_HEIGHT; 445 } 446 447 $this->Ellipse($sx, $sy, $rx, $ry); 448 break; 449 case 'rectangle': 450 $w = abs($sx - $ex); 451 $h = abs($sy - $ey); 452 $sx = min($sx, $ex); 453 $sy = min($sy, $ey); 454 455 // Width or height should be >= min width and height 456 if ($w < self::MIN_ANNOTATION_WIDTH) { 457 $w = self::MIN_ANNOTATION_WIDTH; 458 } 459 if ($h < self::MIN_ANNOTATION_HEIGHT) { 460 $h = self::MIN_ANNOTATION_HEIGHT; 461 } 462 $this->Rect($sx, $sy, $w, $h); 463 break; 464 case 'highlight': 465 $w = abs($sx - $ex); 466 $h = 8.0 * $this->scale; 467 $sx = min($sx, $ex); 468 $sy = min($sy, $ey) + ($h * 0.5); 469 $this->SetAlpha(0.5, 'Normal', 0.5, 'Normal'); 470 $this->SetLineWidth(8.0 * $this->scale); 471 472 // width should be >= min width 473 if ($w < self::MIN_ANNOTATION_WIDTH) { 474 $w = self::MIN_ANNOTATION_WIDTH; 475 } 476 477 $this->Rect($sx, $sy, $w, $h); 478 $this->SetAlpha(1.0, 'Normal', 1.0, 'Normal'); 479 break; 480 case 'pen': 481 if ($path) { 482 $scalepath = array(); 483 $points = preg_split('/[,:]/', $path); 484 foreach ($points as $point) { 485 $scalepath[] = intval($point) * $this->scale; 486 } 487 488 if (!empty($scalepath)) { 489 $this->PolyLine($scalepath, 'S'); 490 } 491 } 492 break; 493 case 'stamp': 494 $imgfile = $imagefolder . '/' . clean_filename($path); 495 $w = abs($sx - $ex); 496 $h = abs($sy - $ey); 497 $sx = min($sx, $ex); 498 $sy = min($sy, $ey); 499 500 // Stamp is always more than 40px, so no need to check width/height. 501 $this->Image($imgfile, $sx, $sy, $w, $h); 502 break; 503 default: // Line. 504 $this->Line($sx, $sy, $ex, $ey); 505 break; 506 } 507 $this->SetDrawColor(0, 0, 0); 508 $this->SetLineWidth(1.0 * $this->scale); 509 510 return true; 511 } 512 513 /** 514 * Save the completed PDF to the given file 515 * @param string $filename the filename for the PDF (including the full path) 516 */ 517 public function save_pdf($filename) { 518 $olddebug = error_reporting(0); 519 $this->Output($filename, 'F'); 520 error_reporting($olddebug); 521 } 522 523 /** 524 * Set the path to the folder in which to generate page image files 525 * @param string $folder 526 */ 527 public function set_image_folder($folder) { 528 $this->imagefolder = $folder; 529 } 530 531 /** 532 * Generate an image of the specified page in the PDF 533 * @param int $pageno the page to generate the image of 534 * @throws \moodle_exception 535 * @throws \coding_exception 536 * @return string the filename of the generated image 537 */ 538 public function get_image($pageno) { 539 global $CFG; 540 541 if (!$this->filename) { 542 throw new \coding_exception('Attempting to generate a page image without first setting the PDF filename'); 543 } 544 545 if (!$this->imagefolder) { 546 throw new \coding_exception('Attempting to generate a page image without first specifying the image output folder'); 547 } 548 549 if (!is_dir($this->imagefolder)) { 550 throw new \coding_exception('The specified image output folder is not a valid folder'); 551 } 552 553 $imagefile = $this->imagefolder . '/' . self::IMAGE_PAGE . $pageno . '.png'; 554 $generate = true; 555 if (file_exists($imagefile)) { 556 if (filemtime($imagefile) > filemtime($this->filename)) { 557 // Make sure the image is newer than the PDF file. 558 $generate = false; 559 } 560 } 561 562 if ($generate) { 563 // Use ghostscript to generate an image of the specified page. 564 $gsexec = \escapeshellarg($CFG->pathtogs); 565 $imageres = \escapeshellarg(100); 566 $imagefilearg = \escapeshellarg($imagefile); 567 $filename = \escapeshellarg($this->filename); 568 $pagenoinc = \escapeshellarg($pageno + 1); 569 $command = "$gsexec -q -sDEVICE=png16m -dSAFER -dBATCH -dNOPAUSE -r$imageres -dFirstPage=$pagenoinc -dLastPage=$pagenoinc ". 570 "-dDOINTERPOLATE -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -sOutputFile=$imagefilearg $filename"; 571 572 $output = null; 573 $result = exec($command, $output); 574 if (!file_exists($imagefile)) { 575 $fullerror = '<pre>'.get_string('command', 'assignfeedback_editpdf')."\n"; 576 $fullerror .= $command . "\n\n"; 577 $fullerror .= get_string('result', 'assignfeedback_editpdf')."\n"; 578 $fullerror .= htmlspecialchars($result) . "\n\n"; 579 $fullerror .= get_string('output', 'assignfeedback_editpdf')."\n"; 580 $fullerror .= htmlspecialchars(implode("\n", $output)) . '</pre>'; 581 throw new \moodle_exception('errorgenerateimage', 'assignfeedback_editpdf', '', $fullerror); 582 } 583 } 584 585 return self::IMAGE_PAGE . $pageno . '.png'; 586 } 587 588 /** 589 * Check to see if PDF is version 1.4 (or below); if not: use ghostscript to convert it 590 * 591 * @param stored_file $file 592 * @return string path to copy or converted pdf (false == fail) 593 */ 594 public static function ensure_pdf_compatible(\stored_file $file) { 595 global $CFG; 596 597 // Copy the stored_file to local disk for checking. 598 $temparea = make_request_directory(); 599 $tempsrc = $temparea . "/source.pdf"; 600 $file->copy_content_to($tempsrc); 601 602 return self::ensure_pdf_file_compatible($tempsrc); 603 } 604 605 /** 606 * Check to see if PDF is version 1.4 (or below); if not: use ghostscript to convert it 607 * 608 * @param string $tempsrc The path to the file on disk. 609 * @return string path to copy or converted pdf (false == fail) 610 */ 611 public static function ensure_pdf_file_compatible($tempsrc) { 612 global $CFG; 613 614 $pdf = new pdf(); 615 $pagecount = 0; 616 try { 617 $pagecount = $pdf->load_pdf($tempsrc); 618 } catch (\Exception $e) { 619 // PDF was not valid - try running it through ghostscript to clean it up. 620 $pagecount = 0; 621 } 622 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed. 623 624 if ($pagecount > 0) { 625 // PDF is already valid and can be read by tcpdf. 626 return $tempsrc; 627 } 628 629 $temparea = make_request_directory(); 630 $tempdst = $temparea . "/target.pdf"; 631 632 $gsexec = \escapeshellarg($CFG->pathtogs); 633 $tempdstarg = \escapeshellarg($tempdst); 634 $tempsrcarg = \escapeshellarg($tempsrc); 635 $command = "$gsexec -q -sDEVICE=pdfwrite -dSAFER -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg"; 636 exec($command); 637 if (!file_exists($tempdst)) { 638 // Something has gone wrong in the conversion. 639 return false; 640 } 641 642 $pdf = new pdf(); 643 $pagecount = 0; 644 try { 645 $pagecount = $pdf->load_pdf($tempdst); 646 } catch (\Exception $e) { 647 // PDF was not valid - try running it through ghostscript to clean it up. 648 $pagecount = 0; 649 } 650 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed. 651 652 if ($pagecount <= 0) { 653 // Could not parse the converted pdf. 654 return false; 655 } 656 657 return $tempdst; 658 } 659 660 /** 661 * Generate an localised error image for the given pagenumber. 662 * 663 * @param string $errorimagefolder path of the folder where error image needs to be created. 664 * @param int $pageno page number for which error image needs to be created. 665 * 666 * @return string File name 667 * @throws \coding_exception 668 */ 669 public static function get_error_image($errorimagefolder, $pageno) { 670 global $CFG; 671 672 $errorfile = $CFG->dirroot . self::BLANK_PDF; 673 if (!file_exists($errorfile)) { 674 throw new \coding_exception("Blank PDF not found", "File path" . $errorfile); 675 } 676 677 $tmperrorimagefolder = make_request_directory(); 678 679 $pdf = new pdf(); 680 $pdf->set_pdf($errorfile); 681 $pdf->copy_page(); 682 $pdf->add_comment(get_string('errorpdfpage', 'assignfeedback_editpdf'), 250, 300, 200, "red"); 683 $generatedpdf = $tmperrorimagefolder . '/' . 'error.pdf'; 684 $pdf->save_pdf($generatedpdf); 685 686 $pdf = new pdf(); 687 $pdf->set_pdf($generatedpdf); 688 $pdf->set_image_folder($tmperrorimagefolder); 689 $image = $pdf->get_image(0); 690 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed. 691 $newimg = self::IMAGE_PAGE . $pageno . '.png'; 692 693 copy($tmperrorimagefolder . '/' . $image, $errorimagefolder . '/' . $newimg); 694 return $newimg; 695 } 696 697 /** 698 * Test that the configured path to ghostscript is correct and working. 699 * @param bool $generateimage - If true - a test image will be generated to verify the install. 700 * @return \stdClass 701 */ 702 public static function test_gs_path($generateimage = true) { 703 global $CFG; 704 705 $ret = (object)array( 706 'status' => self::GSPATH_OK, 707 'message' => null, 708 ); 709 $gspath = $CFG->pathtogs; 710 if (empty($gspath)) { 711 $ret->status = self::GSPATH_EMPTY; 712 return $ret; 713 } 714 if (!file_exists($gspath)) { 715 $ret->status = self::GSPATH_DOESNOTEXIST; 716 return $ret; 717 } 718 if (is_dir($gspath)) { 719 $ret->status = self::GSPATH_ISDIR; 720 return $ret; 721 } 722 if (!is_executable($gspath)) { 723 $ret->status = self::GSPATH_NOTEXECUTABLE; 724 return $ret; 725 } 726 727 if (!$generateimage) { 728 return $ret; 729 } 730 731 $testfile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/testgs.pdf'; 732 if (!file_exists($testfile)) { 733 $ret->status = self::GSPATH_NOTESTFILE; 734 return $ret; 735 } 736 737 $testimagefolder = \make_temp_directory('assignfeedback_editpdf_test'); 738 $filepath = $testimagefolder . '/' . self::IMAGE_PAGE . '0.png'; 739 // Delete any previous test images, if they exist. 740 if (file_exists($filepath)) { 741 unlink($filepath); 742 } 743 744 $pdf = new pdf(); 745 $pdf->set_pdf($testfile); 746 $pdf->set_image_folder($testimagefolder); 747 try { 748 $pdf->get_image(0); 749 } catch (\moodle_exception $e) { 750 $ret->status = self::GSPATH_ERROR; 751 $ret->message = $e->getMessage(); 752 } 753 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed. 754 755 return $ret; 756 } 757 758 /** 759 * If the test image has been generated correctly - send it direct to the browser. 760 */ 761 public static function send_test_image() { 762 global $CFG; 763 header('Content-type: image/png'); 764 require_once($CFG->libdir.'/filelib.php'); 765 766 $testimagefolder = \make_temp_directory('assignfeedback_editpdf_test'); 767 $testimage = $testimagefolder . '/' . self::IMAGE_PAGE . '0.png'; 768 send_file($testimage, basename($testimage), 0); 769 die(); 770 } 771 772 /** 773 * This function add an image file to PDF page. 774 * @param \stored_file $imagestoredfile Image file to be added 775 */ 776 public function add_image_page($imagestoredfile) { 777 $imageinfo = $imagestoredfile->get_imageinfo(); 778 $imagecontent = $imagestoredfile->get_content(); 779 $this->currentpage++; 780 $template = $this->importPage($this->currentpage); 781 $size = $this->getTemplateSize($template); 782 783 if ($imageinfo["width"] > $imageinfo["height"]) { 784 if ($size['width'] < $size['height']) { 785 $temp = $size['width']; 786 $size['width'] = $size['height']; 787 $size['height'] = $temp; 788 } 789 } else if ($imageinfo["width"] < $imageinfo["height"]) { 790 if ($size['width'] > $size['height']) { 791 $temp = $size['width']; 792 $size['width'] = $size['height']; 793 $size['height'] = $temp; 794 } 795 } 796 $orientation = $size['orientation']; 797 $this->SetHeaderMargin(0); 798 $this->SetFooterMargin(0); 799 $this->SetMargins(0, 0, 0, true); 800 $this->setPrintFooter(false); 801 $this->setPrintHeader(false); 802 803 $this->AddPage($orientation, $size); 804 $this->SetAutoPageBreak(false, 0); 805 $this->Image('@' . $imagecontent, 0, 0, $size['w'], $size['h'], 806 '', '', '', false, null, '', false, false, 0); 807 } 808 } 809
title
Description
Body
title
Description
Body
title
Description
Body
title
Body