1<?php 2/** 3 * Based on the FPDF class by Olivier Plathey (http://www.fpdf.org/). 4 * 5 * Minimal conversion to PHP 5 by Maintainable Software 6 * (http://maintainable.com). 7 * 8 * Copyright 2001-2003 Olivier Plathey <olivier@fpdf.org> 9 * Copyright 2003-2017 Horde LLC (http://www.horde.org/) 10 * 11 * @author Olivier Plathey <olivier@fpdf.org> 12 * @author Marko Djukic <marko@oblo.com> 13 * @author Jan Schneider <jan@horde.org> 14 * @license http://www.horde.org/licenses/lgpl21 15 * @category Horde 16 * @package Pdf 17 */ 18 19/** 20 * The Horde_Pdf_Writer class provides a PHP-only implementation of a PDF 21 * library. No external libs or PHP extensions are required. 22 * 23 * @category Horde 24 * @package Pdf 25 */ 26class Horde_Pdf_Writer 27{ 28 /** 29 * Current page number. 30 * 31 * @var integer 32 */ 33 protected $_page = 0; 34 35 /** 36 * Current object number. 37 * 38 * @var integer 39 */ 40 protected $_n = 2; 41 42 /** 43 * Array of object offsets. 44 * 45 * @var array 46 */ 47 protected $_offsets = array(); 48 49 /** 50 * Buffer holding in-memory Pdf. 51 * 52 * @var string 53 */ 54 protected $_buffer = ''; 55 56 /** 57 * Buffer length, including already flushed content. 58 * 59 * @var integer 60 */ 61 protected $_buflen = 0; 62 63 /** 64 * Whether the buffer has been flushed already. 65 * 66 * @var boolean 67 */ 68 protected $_flushed = false; 69 70 /** 71 * Array containing the pages. 72 * 73 * @var array 74 */ 75 protected $_pages = array(); 76 77 /** 78 * Current document state.<pre> 79 * 0 - initial state 80 * 1 - document opened 81 * 2 - page opened 82 * 3 - document closed 83 * </pre> 84 * 85 * @var integer 86 */ 87 protected $_state = 0; 88 89 /** 90 * Flag indicating if PDF file is to be compressed or not. 91 * 92 * @var boolean 93 */ 94 protected $_compress; 95 96 /** 97 * The default page orientation. 98 * 99 * @var string 100 */ 101 protected $_default_orientation; 102 103 /** 104 * The current page orientation. 105 * 106 * @var string 107 */ 108 protected $_current_orientation; 109 110 /** 111 * Array indicating orientation changes. 112 * 113 * @var array 114 */ 115 protected $_orientation_changes = array(); 116 117 /** 118 * Current width of page format in points. 119 * 120 * @var float 121 */ 122 public $fwPt; 123 124 /** 125 * Current height of page format in points. 126 * 127 * @var float 128 */ 129 public $fhPt; 130 131 /** 132 * Current width of page format in user units. 133 * 134 * @var float 135 */ 136 public $fw; 137 138 /** 139 * Current height of page format in user units. 140 * 141 * @var float 142 */ 143 public $fh; 144 145 /** 146 * Current width of page in points. 147 * 148 * @var float 149 */ 150 public $wPt; 151 152 /** 153 * Current height of page in points. 154 * 155 * @var float 156 */ 157 public $hPt; 158 159 /** 160 * Current width of page in user units 161 * 162 * @var float 163 */ 164 public $w; 165 166 /** 167 * Current height of page in user units 168 * 169 * @var float 170 */ 171 public $h; 172 173 /** 174 * Scale factor (number of points in user units). 175 * 176 * @var float 177 */ 178 protected $_scale; 179 180 /** 181 * Left page margin size. 182 * 183 * @var float 184 */ 185 protected $_left_margin; 186 187 /** 188 * Top page margin size. 189 * 190 * @var float 191 */ 192 protected $_top_margin; 193 194 /** 195 * Right page margin size. 196 * 197 * @var float 198 */ 199 protected $_right_margin; 200 201 /** 202 * Break page margin size, the bottom margin which triggers a page break. 203 * 204 * @var float 205 */ 206 protected $_break_margin; 207 208 /** 209 * Cell margin size. 210 * 211 * @var float 212 */ 213 protected $_cell_margin; 214 215 /** 216 * The current horizontal position for cell positioning. 217 * Value is set in user units and is calculated from the top left corner 218 * as origin. 219 * 220 * @var float 221 */ 222 public $x; 223 224 /** 225 * The current vertical position for cell positioning. 226 * Value is set in user units and is calculated from the top left corner 227 * as origin. 228 * 229 * @var float 230 */ 231 public $y; 232 233 /** 234 * The height of the last cell printed. 235 * 236 * @var float 237 */ 238 protected $_last_height; 239 240 /** 241 * Line width in user units. 242 * 243 * @var float 244 */ 245 protected $_line_width; 246 247 /** 248 * An array of standard font names. 249 * 250 * @var array 251 */ 252 protected $_core_fonts = array('courier' => 'Courier', 253 'courierB' => 'Courier-Bold', 254 'courierI' => 'Courier-Oblique', 255 'courierBI' => 'Courier-BoldOblique', 256 'helvetica' => 'Helvetica', 257 'helveticaB' => 'Helvetica-Bold', 258 'helveticaI' => 'Helvetica-Oblique', 259 'helveticaBI' => 'Helvetica-BoldOblique', 260 'times' => 'Times-Roman', 261 'timesB' => 'Times-Bold', 262 'timesI' => 'Times-Italic', 263 'timesBI' => 'Times-BoldItalic', 264 'symbol' => 'Symbol', 265 'zapfdingbats' => 'ZapfDingbats'); 266 267 /** 268 * An array of used fonts. 269 * 270 * @var array 271 */ 272 protected $_fonts = array(); 273 274 /** 275 * An array of font files. 276 * 277 * @var array 278 */ 279 protected $_font_files = array(); 280 281 /** 282 * Widths of specific font files 283 * 284 * @var array 285 */ 286 protected static $_font_widths = array(); 287 288 /** 289 * An array of encoding differences. 290 * 291 * @var array 292 */ 293 protected $_diffs = array(); 294 295 /** 296 * An array of used images. 297 * 298 * @var array 299 */ 300 protected $_images = array(); 301 302 /** 303 * An array of links in pages. 304 * 305 * @var array 306 */ 307 protected $_page_links; 308 309 /** 310 * An array of internal links. 311 * 312 * @var array 313 */ 314 protected $_links = array(); 315 316 /** 317 * Current font family. 318 * 319 * @var string 320 */ 321 protected $_font_family = ''; 322 323 /** 324 * Current font style. 325 * 326 * @var string 327 */ 328 protected $_font_style = ''; 329 330 /** 331 * Underlining flag. 332 * 333 * @var boolean 334 */ 335 protected $_underline = false; 336 337 /** 338 * An array containing current font info. 339 * 340 * @var array 341 */ 342 protected $_current_font; 343 344 /** 345 * Current font size in points. 346 * 347 * @var float 348 */ 349 protected $_font_size_pt = 12; 350 351 /** 352 * Current font size in user units. 353 * 354 * @var float 355 */ 356 protected $_font_size = 12; 357 358 /** 359 * Commands for filling color. 360 * 361 * @var string 362 */ 363 protected $_fill_color = '0 g'; 364 365 /** 366 * Commands for text color. 367 * 368 * @var string 369 */ 370 protected $_text_color = '0 g'; 371 372 /** 373 * Whether text color is different from fill color. 374 * 375 * @var boolean 376 */ 377 protected $_color_flag = false; 378 379 /** 380 * Commands for drawing color. 381 * 382 * @var string 383 */ 384 protected $_draw_color = '0 G'; 385 386 /** 387 * Word spacing. 388 * 389 * @var integer 390 */ 391 protected $_word_spacing = 0; 392 393 /** 394 * Automatic page breaking. 395 * 396 * @var boolean 397 */ 398 protected $_auto_page_break; 399 400 /** 401 * Threshold used to trigger page breaks. 402 * 403 * @var float 404 */ 405 protected $_page_break_trigger; 406 407 /** 408 * Flag set when processing footer. 409 * 410 * @var boolean 411 */ 412 protected $_in_footer = false; 413 414 /** 415 * Zoom display mode. 416 * 417 * @var string 418 */ 419 protected $_zoom_mode; 420 421 /** 422 * Layout display mode. 423 * 424 * @var string 425 */ 426 protected $_layout_mode; 427 428 /** 429 * An array containing the document info, consisting of: 430 * - title 431 * - subject 432 * - author 433 * - keywords 434 * - creator 435 * 436 * @var array 437 */ 438 protected $_info = array(); 439 440 /** 441 * Alias for total number of pages. 442 * 443 * @var string 444 */ 445 protected $_alias_nb_pages = '{nb}'; 446 447 /** 448 * Constructor 449 * 450 * It allows to set up the page format, the orientation and the units of 451 * measurement used in all the methods (except for the font sizes). 452 * 453 * Example: 454 * <code> 455 * $pdf = new Horde_Pdf_Writer(array('orientation' => 'P', 456 * 'unit' => 'mm', 457 * 'format' => 'A4')); 458 * </code> 459 * 460 * @param array $params A hash with parameters for the created PDF object. 461 * Possible parameters are: 462 * - orientation - Default page orientation. Possible 463 * values are (case insensitive): 464 * - P or Portrait (default) 465 * - L or Landscape 466 * - unit - User measure units. Possible values 467 * values are: 468 * - pt: point 469 * - mm: millimeter (default) 470 * - cm: centimeter 471 * - in: inch 472 * A point equals 1/72 of inch, that is to say 473 * about 0.35 mm (an inch being 2.54 cm). This is a 474 * very common unit in typography; font sizes are 475 * expressed in that unit. 476 * - format - The format used for pages. It can be 477 * either one of the following values (case 478 * insensitive): 479 * - A3 480 * - A4 (default) 481 * - A5 482 * - Letter 483 * - Legal 484 * or a custom format in the form of a two-element 485 * array containing the width and the height 486 * (expressed in the unit given by the unit 487 * parameter). 488 */ 489 public function __construct($params = array()) 490 { 491 /* Default parameters. */ 492 $defaults = array('orientation' => 'P', 'unit' => 'mm', 'format' => 'A4'); 493 $params = array_merge($defaults, $params); 494 495 /* Scale factor. */ 496 if ($params['unit'] == 'pt') { 497 $this->_scale = 1; 498 } elseif ($params['unit'] == 'mm') { 499 $this->_scale = 72 / 25.4; 500 } elseif ($params['unit'] == 'cm') { 501 $this->_scale = 72 / 2.54; 502 } elseif ($params['unit'] == 'in') { 503 $this->_scale = 72; 504 } else { 505 throw new Horde_Pdf_Exception(sprintf('Incorrect units: %s', $params['unit'])); 506 } 507 /* Page format. */ 508 if (is_string($params['format'])) { 509 $params['format'] = Horde_String::lower($params['format']); 510 if ($params['format'] == 'a3') { 511 $params['format'] = array(841.89, 1190.55); 512 } elseif ($params['format'] == 'a4') { 513 $params['format'] = array(595.28, 841.89); 514 } elseif ($params['format'] == 'a5') { 515 $params['format'] = array(420.94, 595.28); 516 } elseif ($params['format'] == 'letter') { 517 $params['format'] = array(612, 792); 518 } elseif ($params['format'] == 'legal') { 519 $params['format'] = array(612, 1008); 520 } else { 521 throw new Horde_Pdf_Exception(sprintf('Unknown page format: %s', $params['format'])); 522 } 523 $this->fwPt = $params['format'][0]; 524 $this->fhPt = $params['format'][1]; 525 } else { 526 $this->fwPt = $params['format'][0] * $this->_scale; 527 $this->fhPt = $params['format'][1] * $this->_scale; 528 } 529 $this->fw = $this->fwPt / $this->_scale; 530 $this->fh = $this->fhPt / $this->_scale; 531 532 /* Page orientation. */ 533 $params['orientation'] = Horde_String::lower($params['orientation']); 534 if ($params['orientation'] == 'p' || $params['orientation'] == 'portrait') { 535 $this->_default_orientation = 'P'; 536 $this->wPt = $this->fwPt; 537 $this->hPt = $this->fhPt; 538 } elseif ($params['orientation'] == 'l' || $params['orientation'] == 'landscape') { 539 $this->_default_orientation = 'L'; 540 $this->wPt = $this->fhPt; 541 $this->hPt = $this->fwPt; 542 } else { 543 throw new Horde_Pdf_Exception(sprintf('Incorrect orientation: %s', $params['orientation'])); 544 } 545 $this->_current_orientation = $this->_default_orientation; 546 $this->w = $this->wPt / $this->_scale; 547 $this->h = $this->hPt / $this->_scale; 548 549 /* Page margins (1 cm) */ 550 $margin = 28.35 / $this->_scale; 551 $this->setMargins($margin, $margin); 552 553 /* Interior cell margin (1 mm) */ 554 $this->_cell_margin = $margin / 10; 555 556 /* Line width (0.2 mm) */ 557 $this->_line_width = .567 / $this->_scale; 558 559 /* Automatic page break */ 560 $this->setAutoPageBreak(true, 2 * $margin); 561 562 /* Full width display mode */ 563 $this->setDisplayMode('fullwidth'); 564 565 /* Compression */ 566 $this->setCompression(true); 567 } 568 569 /** 570 * Defines the left, top and right margins. 571 * 572 * By default, they equal 1 cm. Call this method to change them. 573 * 574 * @param float $left Left margin. 575 * @param float $top Top margin. 576 * @param float $right Right margin. If not specified default to the value 577 * of the left one. 578 * 579 * @see setAutoPageBreak() 580 * @see setLeftMargin() 581 * @see setRightMargin() 582 * @see setTopMargin() 583 */ 584 public function setMargins($left, $top, $right = null) 585 { 586 /* Set left and top margins. */ 587 $this->_left_margin = $left; 588 $this->_top_margin = $top; 589 /* If no right margin set default to same as left. */ 590 $this->_right_margin = (is_null($right) ? $left : $right); 591 } 592 593 /** 594 * Defines the left margin. 595 * 596 * The method can be called before creating the first page. If the 597 * current abscissa gets out of page, it is brought back to the margin. 598 * 599 * @param float $margin The margin. 600 * 601 * @see setAutoPageBreak() 602 * @see setMargins() 603 * @see setRightMargin() 604 * @see setTopMargin() 605 */ 606 public function setLeftMargin($margin) 607 { 608 $this->_left_margin = $margin; 609 /* If there is a current page and the current X position is less than 610 * margin set the X position to the margin value. */ 611 if ($this->_page > 0 && $this->x < $margin) { 612 $this->x = $margin; 613 } 614 } 615 616 /** 617 * Defines the top margin. 618 * 619 * The method can be called before creating the first page. 620 * 621 * @param float $margin The margin. 622 */ 623 public function setTopMargin($margin) 624 { 625 $this->_top_margin = $margin; 626 } 627 628 /** 629 * Defines the right margin. 630 * 631 * The method can be called before creating the first page. 632 * 633 * @param float $margin The margin. 634 */ 635 public function setRightMargin($margin) 636 { 637 $this->_right_margin = $margin; 638 } 639 640 /** 641 * Returns the actual page width. 642 * 643 * @return float The page width. 644 */ 645 public function getPageWidth() 646 { 647 return ($this->w - $this->_right_margin - $this->_left_margin); 648 } 649 650 /** 651 * Returns the actual page height. 652 * 653 * @return float The page height. 654 */ 655 public function getPageHeight() 656 { 657 return ($this->h - $this->_top_margin - $this->_break_margin); 658 } 659 660 /** 661 * Enables or disables the automatic page breaking mode. 662 * 663 * When enabling, the second parameter is the distance from the bottom of 664 * the page that defines the triggering limit. By default, the mode is on 665 * and the margin is 2 cm. 666 * 667 * @param boolean $auto Boolean indicating if mode should be on or off. 668 * @param float $margin Distance from the bottom of the page. 669 */ 670 public function setAutoPageBreak($auto, $margin = 0) 671 { 672 $this->_auto_page_break = $auto; 673 $this->_break_margin = $margin; 674 $this->_page_break_trigger = $this->h - $margin; 675 } 676 677 /** 678 * Defines the way the document is to be displayed by the viewer. 679 * 680 * The zoom level can be set: pages can be displayed entirely on screen, 681 * occupy the full width of the window, use real size, be scaled by a 682 * specific zooming factor or use viewer default (configured in the 683 * Preferences menu of Acrobat). The page layout can be specified too: 684 * single at once, continuous display, two columns or viewer default. By 685 * default, documents use the full width mode with continuous display. 686 * 687 * @param mixed $zoom The zoom to use. It can be one of the following 688 * string values: 689 * - fullpage: entire page on screen 690 * - fullwidth: maximum width of window 691 * - real: uses real size (100% zoom) 692 * - default: uses viewer default mode 693 * or a number indicating the zooming factor. 694 * @param string layout The page layout. Possible values are: 695 * - single: one page at once 696 * - continuous: pages in continuously 697 * - two: two pages on two columns 698 * - default: uses viewer default mode 699 * Default value is continuous. 700 */ 701 public function setDisplayMode($zoom, $layout = 'continuous') 702 { 703 $zoom = Horde_String::lower($zoom); 704 if ($zoom == 'fullpage' || $zoom == 'fullwidth' || $zoom == 'real' 705 || $zoom == 'default' || !is_string($zoom)) { 706 $this->_zoom_mode = $zoom; 707 } elseif ($zoom == 'zoom') { 708 $this->_zoom_mode = $layout; 709 } else { 710 throw new Horde_Pdf_Exception(sprintf('Incorrect zoom display mode: %s', $zoom)); 711 } 712 713 $layout = Horde_String::lower($layout); 714 if ($layout == 'single' || $layout == 'continuous' || $layout == 'two' 715 || $layout == 'default') { 716 $this->_layout_mode = $layout; 717 } elseif ($zoom != 'zoom') { 718 throw new Horde_Pdf_Exception(sprintf('Incorrect layout display mode: %s', $layout)); 719 } 720 } 721 722 /** 723 * Activates or deactivates page compression. 724 * 725 * When activated, the internal representation of each page is compressed, 726 * which leads to a compression ratio of about 2 for the resulting 727 * document. Compression is on by default. 728 * 729 * Note: the {@link http://www.php.net/zlib/ zlib extension} is required 730 * for this feature. If not present, compression will be turned off. 731 * 732 * @param boolean $compress Boolean indicating if compression must be 733 * enabled or not. 734 */ 735 public function setCompression($compress) 736 { 737 /* If no gzcompress function is available then default to false. */ 738 $this->_compress = (function_exists('gzcompress') ? $compress : false); 739 } 740 741 /** 742 * Set the info to a document. 743 * 744 * Possible info settings are: 745 * - title 746 * - subject 747 * - author 748 * - keywords 749 * - creator 750 * 751 * @param array|string $info If passed as an array then the complete hash 752 * containing the info to be inserted into the 753 * document. Otherwise the name of setting to be 754 * set. 755 * @param string $value The value of the setting. 756 */ 757 public function setInfo($info, $value = '') 758 { 759 if (is_array($info)) { 760 $this->_info = $info; 761 } else { 762 $this->_info[$info] = $value; 763 } 764 } 765 766 /** 767 * Defines an alias for the total number of pages. 768 * 769 * It will be substituted as the document is closed. 770 * 771 * Example: 772 * <code> 773 * class My_Pdf extends Horde_Pdf_Writer { 774 * function footer() 775 * { 776 * // Go to 1.5 cm from bottom 777 * $this->setY(-15); 778 * // Select Arial italic 8 779 * $this->setFont('Arial', 'I', 8); 780 * // Print current and total page numbers 781 * $this->cell(0, 10, 'Page ' . $this->getPageNo() . '/{nb}', 0, 782 * 0, 'C'); 783 * } 784 * } 785 * $pdf = new My_Pdf(); 786 * $pdf->aliasNbPages(); 787 * </code> 788 * 789 * @param string $alias The alias. 790 * 791 * @see getPageNo() 792 * @see footer() 793 */ 794 public function aliasNbPages($alias = '{nb}') 795 { 796 $this->_alias_nb_pages = $alias; 797 } 798 799 /** 800 * This method begins the generation of the PDF document; it must be 801 * called before any output commands. 802 * 803 * No page is created by this method, therefore it is necessary to call 804 * {@link addPage()}. 805 * 806 * @see addPage() 807 * @see close() 808 */ 809 public function open() 810 { 811 $this->_beginDoc(); 812 } 813 814 /** 815 * Terminates the PDF document. 816 * 817 * If the document contains no page, {@link addPage()} is called to 818 * prevent from getting an invalid document. 819 * 820 * @see open() 821 */ 822 public function close() 823 { 824 // Terminate document 825 if ($this->_page == 0) { 826 $this->addPage(); 827 } 828 829 // Page footer 830 $this->_in_footer = true; 831 $this->x = $this->_left_margin; 832 $this->footer(); 833 $this->_in_footer = false; 834 835 // Close page and document 836 $this->_endPage(); 837 $this->_endDoc(); 838 } 839 840 /** 841 * Adds a new page to the document. 842 * 843 * If a page is already present, the {@link footer()} method is called 844 * first to output the footer. Then the page is added, the current 845 * position set to the top-left corner according to the left and top 846 * margins, and {@link header()} is called to display the header. 847 * 848 * The font which was set before calling is automatically restored. There 849 * is no need to call {@link setFont()} again if you want to continue with 850 * the same font. The same is true for colors and line width. The origin 851 * of the coordinate system is at the top-left corner and increasing 852 * ordinates go downwards. 853 * 854 * @param string $orientation Page orientation. Possible values 855 * are (case insensitive): 856 * - P or Portrait 857 * - L or Landscape 858 * The default value is the one passed to the 859 * constructor. 860 * 861 * @see header() 862 * @see footer() 863 * @see setMargins() 864 */ 865 public function addPage($orientation = '') 866 { 867 /* For good measure make sure this is called. */ 868 $this->_beginDoc(); 869 870 /* Save style settings so that they are not overridden by 871 * footer() or header(). */ 872 $lw = $this->_line_width; 873 $dc = $this->_draw_color; 874 $fc = $this->_fill_color; 875 $tc = $this->_text_color; 876 $cf = $this->_color_flag; 877 $font_family = $this->_font_family; 878 $font_style = $this->_font_style . ($this->_underline ? 'U' : ''); 879 $font_size = $this->_font_size_pt; 880 881 if ($this->_page > 0) { 882 /* Page footer. */ 883 $this->_in_footer = true; 884 $this->x = $this->_left_margin; 885 $this->footer(); 886 $this->_in_footer = false; 887 888 /* Close page. */ 889 $this->_endPage(); 890 } 891 892 /* Start new page. */ 893 $this->_beginPage($orientation); 894 895 /* Set line cap style to square. */ 896 $this->_out('2 J'); 897 898 /* Set line width. */ 899 $this->_line_width = $lw; 900 $this->_out(sprintf('%.2F w', $lw * $this->_scale)); 901 902 /* Force the setting of the font. Each new page requires a new 903 * call. */ 904 if ($font_family) { 905 $this->setFont($font_family, $font_style, $font_size, true); 906 } 907 908 /* Restore styles. */ 909 if ($this->_fill_color != $fc) { 910 $this->_fill_color = $fc; 911 $this->_out($this->_fill_color); 912 } 913 if ($this->_draw_color != $dc) { 914 $this->_draw_color = $dc; 915 $this->_out($this->_draw_color); 916 } 917 $this->_text_color = $tc; 918 $this->_color_flag = $cf; 919 920 /* Page header. */ 921 $this->header(); 922 923 /* Restore styles. */ 924 if ($this->_line_width != $lw) { 925 $this->_line_width = $lw; 926 $this->_out(sprintf('%.2F w', $lw * $this->_scale)); 927 } 928 $this->setFont($font_family, $font_style, $font_size); 929 if ($this->_fill_color != $fc) { 930 $this->_fill_color = $fc; 931 $this->_out($this->_fill_color); 932 } 933 if ($this->_draw_color != $dc) { 934 $this->_draw_color = $dc; 935 $this->_out($this->_draw_color); 936 } 937 $this->_text_color = $tc; 938 $this->_color_flag = $cf; 939 } 940 941 /** 942 * This method is used to render the page header. 943 * 944 * It is automatically called by {@link addPage()} and should not be 945 * called directly by the application. The implementation in Horde_Pdf_Writer is 946 * empty, so you have to subclass it and override the method if you want a 947 * specific processing. 948 * 949 * Example: 950 * <code> 951 * class My_Pdf extends Horde_Pdf_Writer { 952 * function header() 953 * { 954 * // Select Arial bold 15 955 * $this->setFont('Arial', 'B', 15); 956 * // Move to the right 957 * $this->cell(80); 958 * // Framed title 959 * $this->cell(30, 10, 'Title', 1, 0, 'C'); 960 * // Line break 961 * $this->newLine(20); 962 * } 963 * } 964 * </code> 965 * 966 * @see footer() 967 */ 968 public function header() 969 { 970 /* To be implemented in your own inherited class. */ 971 } 972 973 /** 974 * This method is used to render the page footer. 975 * 976 * It is automatically called by {@link addPage()} and {@link close()} and 977 * should not be called directly by the application. The implementation in 978 * Horde_Pdf_Writer is empty, so you have to subclass it and override the method 979 * if you want a specific processing. 980 * 981 * Example: 982 * <code> 983 * class My_Pdf extends Horde_Pdf_Writer { 984 * function footer() 985 * { 986 * // Go to 1.5 cm from bottom 987 * $this->setY(-15); 988 * // Select Arial italic 8 989 * $this->setFont('Arial', 'I', 8); 990 * // Print centered page number 991 * $this->cell(0, 10, 'Page ' . $this->getPageNo(), 0, 0, 'C'); 992 * } 993 * } 994 * </code> 995 * 996 * @see header() 997 */ 998 public function footer() 999 { 1000 /* To be implemented in your own inherited class. */ 1001 } 1002 1003 /** 1004 * Returns the current page number. 1005 * 1006 * @return integer 1007 * 1008 * @see aliasNbPages() 1009 */ 1010 public function getPageNo() 1011 { 1012 return $this->_page; 1013 } 1014 1015 /** 1016 * Sets the fill color. 1017 * 1018 * Depending on the colorspace called, the number of color component 1019 * parameters required can be either 1, 3 or 4. The method can be called 1020 * before the first page is created and the color is retained from page to 1021 * page. 1022 * 1023 * @param string $cs Indicates the colorspace which can be either 'rgb', 1024 * 'hex', 'cmyk', or 'gray'. Defaults to 'rgb'. 1025 * @param float $c1 First color component, floating point value between 0 1026 * and 1. Required for gray, rgb and cmyk. 1027 * @param float $c2 Second color component, floating point value 1028 * between 0 and 1. Required for rgb and cmyk. 1029 * @param float $c3 Third color component, floating point value between 0 1030 * and 1. Required for rgb and cmyk. 1031 * @param float $c4 Fourth color component, floating point value 1032 * between 0 and 1. Required for cmyk. 1033 * 1034 * @see setTextColor() 1035 * @see setDrawColor() 1036 * @see rect() 1037 * @see cell() 1038 * @see multiCell() 1039 */ 1040 public function setFillColor($cs = 'rgb', $c1 = 0, $c2 = 0, $c3 = 0, $c4 = 0) 1041 { 1042 $cs = Horde_String::lower($cs); 1043 1044 // convert hex to rgb 1045 if ($cs == 'hex') { 1046 $cs = 'rgb'; 1047 list($c1, $c2, $c3) = $this->_hexToRgb($c1); 1048 } 1049 1050 if ($cs == 'rgb') { 1051 $this->_fill_color = sprintf('%.3F %.3F %.3F rg', $c1, $c2, $c3); 1052 } elseif ($cs == 'cmyk') { 1053 $this->_fill_color = sprintf('%.3F %.3F %.3F %.3F k', $c1, $c2, $c3, $c4); 1054 } else { 1055 $this->_fill_color = sprintf('%.3F g', $c1); 1056 } 1057 if ($this->_page > 0) { 1058 $this->_out($this->_fill_color); 1059 } 1060 $this->_color_flag = $this->_fill_color != $this->_text_color; 1061 } 1062 1063 /** 1064 * Get the fill color 1065 * 1066 * @return string 1067 */ 1068 public function getFillColor() 1069 { 1070 return $this->_fill_color; 1071 } 1072 1073 /** 1074 * Sets the text color. 1075 * 1076 * Depending on the colorspace called, the number of color component 1077 * parameters required can be either 1, 3 or 4. The method can be called 1078 * before the first page is created and the color is retained from page to 1079 * page. 1080 * 1081 * @param string $cs Indicates the colorspace which can be either 'rgb', 1082 * 'hex', 'cmyk' or 'gray'. Defaults to 'rgb'. 1083 * @param float $c1 First color component, floating point value between 0 1084 * and 1. Required for gray, rgb and cmyk. 1085 * @param float $c2 Second color component, floating point value 1086 * between 0 and 1. Required for rgb and cmyk. 1087 * @param float $c3 Third color component, floating point value between 0 1088 * and 1. Required for rgb and cmyk. 1089 * @param float $c4 Fourth color component, floating point value 1090 * between 0 and 1. Required for cmyk. 1091 * 1092 * @see setFillColor() 1093 * @see setDrawColor() 1094 * @see rect() 1095 * @see cell() 1096 * @see multiCell() 1097 */ 1098 public function setTextColor($cs = 'rgb', $c1 = 0, $c2 = 0, $c3 = 0, $c4 = 0) 1099 { 1100 $cs = Horde_String::lower($cs); 1101 1102 // convert hex to rgb 1103 if ($cs == 'hex') { 1104 $cs = 'rgb'; 1105 list($c1, $c2, $c3) = $this->_hexToRgb($c1); 1106 } 1107 1108 if ($cs == 'rgb') { 1109 $this->_text_color = sprintf('%.3F %.3F %.3F rg', $c1, $c2, $c3); 1110 } elseif ($cs == 'cmyk') { 1111 $this->_text_color = sprintf('%.3F %.3F %.3F %.3F k', $c1, $c2, $c3, $c4); 1112 } else { 1113 $this->_text_color = sprintf('%.3F g', $c1); 1114 } 1115 1116 $this->_color_flag = $this->_fill_color != $this->_text_color; 1117 } 1118 1119 /** 1120 * Get the text color 1121 * 1122 * @return string 1123 */ 1124 public function getTextColor() 1125 { 1126 return $this->_text_color; 1127 } 1128 1129 /** 1130 * Sets the draw color, used when drawing lines. 1131 * 1132 * Depending on the colorspace called, the number of color component 1133 * parameters required can be either 1, 3 or 4. The method can be called 1134 * before the first page is created and the color is retained from page to 1135 * page. 1136 * 1137 * @param string $cs Indicates the colorspace which can be either 'rgb', 1138 * 'hex', 'cmyk' or 'gray'. Defaults to 'rgb'. 1139 * @param float $c1 First color component, floating point value between 0 1140 * and 1. Required for gray, rgb and cmyk. 1141 * @param float $c2 Second color component, floating point value 1142 * between 0 and 1. Required for rgb and cmyk. 1143 * @param float $c3 Third color component, floating point value between 0 1144 * and 1. Required for rgb and cmyk. 1145 * @param float $c4 Fourth color component, floating point value 1146 * between 0 and 1. Required for cmyk. 1147 * 1148 * @see setFillColor() 1149 * @see line() 1150 * @see rect() 1151 * @see cell() 1152 * @see multiCell() 1153 */ 1154 public function setDrawColor($cs = 'rgb', $c1 = 0, $c2 = 0, $c3 = 0, $c4 = 0) 1155 { 1156 $cs = Horde_String::lower($cs); 1157 1158 // convert hex to rgb 1159 if ($cs == 'hex') { 1160 $cs = 'rgb'; 1161 list($c1, $c2, $c3) = $this->_hexToRgb($c1); 1162 } 1163 1164 if ($cs == 'rgb') { 1165 $this->_draw_color = sprintf('%.3F %.3F %.3F RG', $c1, $c2, $c3); 1166 } elseif ($cs == 'cmyk') { 1167 $this->_draw_color = sprintf('%.3F %.3F %.3F %.3F K', $c1, $c2, $c3, $c4); 1168 } else { 1169 $this->_draw_color = sprintf('%.3F G', $c1); 1170 } 1171 if ($this->_page > 0) { 1172 $this->_out($this->_draw_color); 1173 } 1174 } 1175 1176 /** 1177 * Get the draw color 1178 * 1179 * @return string 1180 */ 1181 public function getDrawColor() 1182 { 1183 return $this->_draw_color; 1184 } 1185 1186 /** 1187 * Returns the length of a text string. A font must be selected. 1188 * 1189 * @param string $text The text whose length is to be computed. 1190 * @param boolean $pt Whether the width should be returned in points or 1191 * user units. 1192 * 1193 * @return float 1194 */ 1195 public function getStringWidth($text, $pt = false) 1196 { 1197 $text = (string)$text; 1198 $width = 0; 1199 $length = strlen($text); 1200 for ($i = 0; $i < $length; $i++) { 1201 $width += $this->_current_font['cw'][$text[$i]]; 1202 } 1203 1204 /* Adjust for word spacing. */ 1205 $width += $this->_word_spacing * substr_count($text, ' ') * $this->_current_font['cw'][' ']; 1206 1207 if ($pt) { 1208 return $width * $this->_font_size_pt / 1000; 1209 } else { 1210 return $width * $this->_font_size / 1000; 1211 } 1212 } 1213 1214 /** 1215 * Defines the line width. 1216 * 1217 * By default, the value equals 0.2 mm. The method can be called before 1218 * the first page is created and the value is retained from page to page. 1219 * 1220 * @param float $width The width. 1221 * 1222 * @see line() 1223 * @see rect() 1224 * @see cell() 1225 * @see multiCell() 1226 */ 1227 public function setLineWidth($width) 1228 { 1229 $this->_line_width = $width; 1230 if ($this->_page > 0) { 1231 $this->_out(sprintf('%.2F w', $width * $this->_scale)); 1232 } 1233 } 1234 1235 /** 1236 * P (portrait) or L (landscape) 1237 * 1238 * @return string 1239 */ 1240 public function getDefaultOrientation() 1241 { 1242 return $this->_default_orientation; 1243 } 1244 1245 /** 1246 * @return integer 1247 */ 1248 public function getScale() 1249 { 1250 return $this->_scale; 1251 } 1252 1253 /** 1254 * @return float 1255 */ 1256 public function getFormatHeight() 1257 { 1258 return $this->_default_orientation == 'P' ? $this->fhPt : $this->fwPt; 1259 } 1260 1261 /** 1262 * @return float 1263 */ 1264 public function getFormatWidth() 1265 { 1266 return $this->_default_orientation == 'P' ? $this->fwPt : $this->fhPt; 1267 } 1268 1269 /** 1270 * Draws a line between two points. 1271 * 1272 * All coordinates can be negative to provide values from the right or 1273 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 1274 * 1275 * @param float $x1 Abscissa of first point. 1276 * @param float $y1 Ordinate of first point. 1277 * @param float $x2 Abscissa of second point. 1278 * @param float $y2 Ordinate of second point. 1279 * 1280 * @see setLineWidth() 1281 * @see setDrawColor() 1282 */ 1283 public function line($x1, $y1, $x2, $y2) 1284 { 1285 if ($x1 < 0) { 1286 $x1 += $this->w; 1287 } 1288 if ($y1 < 0) { 1289 $y1 += $this->h; 1290 } 1291 if ($x2 < 0) { 1292 $x2 += $this->w; 1293 } 1294 if ($y2 < 0) { 1295 $y2 += $this->h; 1296 } 1297 1298 $this->_out(sprintf('%.2F %.2F m %.2F %.2F l S', $x1 * $this->_scale, ($this->h - $y1) * $this->_scale, $x2 * $this->_scale, ($this->h - $y2) * $this->_scale)); 1299 } 1300 1301 /** 1302 * Outputs a rectangle. 1303 * 1304 * It can be drawn (border only), filled (with no border) or both. 1305 * 1306 * All coordinates can be negative to provide values from the right or 1307 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 1308 * 1309 * @param float $x Abscissa of upper-left corner. 1310 * @param float $y Ordinate of upper-left corner. 1311 * @param float $width Width. 1312 * @param float $height Height. 1313 * @param float $style Style of rendering. Possible values are: 1314 * - D or empty string: draw (default) 1315 * - F: fill 1316 * - DF or FD: draw and fill 1317 * 1318 * @see setLineWidth() 1319 * @see setDrawColor() 1320 * @see setFillColor() 1321 */ 1322 public function rect($x, $y, $width, $height, $style = '') 1323 { 1324 if ($x < 0) { 1325 $x += $this->w; 1326 } 1327 if ($y < 0) { 1328 $y += $this->h; 1329 } 1330 1331 $style = Horde_String::upper($style); 1332 if ($style == 'F') { 1333 $op = 'f'; 1334 } elseif ($style == 'FD' || $style == 'DF') { 1335 $op = 'B'; 1336 } else { 1337 $op = 'S'; 1338 } 1339 1340 $x = $this->_toPt($x); 1341 $y = $this->_toPt($y); 1342 $width = $this->_toPt($width); 1343 $height = $this->_toPt($height); 1344 1345 $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s', $x, $this->hPt - $y, $width, -$height, $op)); 1346 } 1347 1348 /** 1349 * Outputs a circle. It can be drawn (border only), filled (with no 1350 * border) or both. 1351 * 1352 * All coordinates can be negative to provide values from the right or 1353 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 1354 * 1355 * @param float $x Abscissa of the center of the circle. 1356 * @param float $y Ordinate of the center of the circle. 1357 * @param float $r Circle radius. 1358 * @param string $style Style of rendering. Possible values are: 1359 * - D or empty string: draw (default) 1360 * - F: fill 1361 * - DF or FD: draw and fill 1362 */ 1363 public function circle($x, $y, $r, $style = '') 1364 { 1365 if ($x < 0) { 1366 $x += $this->w; 1367 } 1368 if ($y < 0) { 1369 $y += $this->h; 1370 } 1371 1372 $style = Horde_String::lower($style); 1373 if ($style == 'f') { 1374 $op = 'f'; // Style is fill only. 1375 } elseif ($style == 'fd' || $style == 'df') { 1376 $op = 'B'; // Style is fill and stroke. 1377 } else { 1378 $op = 'S'; // Style is stroke only. 1379 } 1380 1381 $x = $this->_toPt($x); 1382 $y = $this->_toPt($y); 1383 $r = $this->_toPt($r); 1384 1385 /* Invert the y scale. */ 1386 $y = $this->hPt - $y; 1387 /* Length of the Bezier control. */ 1388 $b = $r * 0.552; 1389 1390 /* Move from the given origin and set the current point 1391 * to the start of the first Bezier curve. */ 1392 $c = sprintf('%.2F %.2F m', $x - $r, $y); 1393 $x = $x - $r; 1394 /* First circle quarter. */ 1395 $c .= sprintf(' %.2F %.2F %.2F %.2F %.2F %.2F c', 1396 $x, $y + $b, // First control point. 1397 $x + $r - $b, $y + $r, // Second control point. 1398 $x + $r, $y + $r); // Final point. 1399 /* Set x/y to the final point. */ 1400 $x = $x + $r; 1401 $y = $y + $r; 1402 /* Second circle quarter. */ 1403 $c .= sprintf(' %.2F %.2F %.2F %.2F %.2F %.2F c', 1404 $x + $b, $y, 1405 $x + $r, $y - $r + $b, 1406 $x + $r, $y - $r); 1407 /* Set x/y to the final point. */ 1408 $x = $x + $r; 1409 $y = $y - $r; 1410 /* Third circle quarter. */ 1411 $c .= sprintf(' %.2F %.2F %.2F %.2F %.2F %.2F c', 1412 $x, $y - $b, 1413 $x - $r + $b, $y - $r, 1414 $x - $r, $y - $r); 1415 /* Set x/y to the final point. */ 1416 $x = $x - $r; 1417 $y = $y - $r; 1418 /* Fourth circle quarter. */ 1419 $c .= sprintf(' %.2F %.2F %.2F %.2F %.2F %.2F c %s', 1420 $x - $b, $y, 1421 $x - $r, $y + $r - $b, 1422 $x - $r, $y + $r, 1423 $op); 1424 /* Output the whole string. */ 1425 $this->_out($c); 1426 } 1427 1428 /** 1429 * Imports a TrueType or Type1 font and makes it available. It is 1430 * necessary to generate a font definition file first with the 1431 * makefont.php utility. 1432 * The location of the definition file (and the font file itself when 1433 * embedding) must be found at the full path name included. 1434 * 1435 * Example: 1436 * <code> 1437 * $pdf->addFont('Comic', 'I'); 1438 * is equivalent to: 1439 * $pdf->addFont('Comic', 'I', 'comici.php'); 1440 * </code> 1441 * 1442 * @param string $family Font family. The name can be chosen arbitrarily. 1443 * If it is a standard family name, it will 1444 * override the corresponding font. 1445 * @param string $style Font style. Possible values are (case 1446 * insensitive): 1447 * - empty string: regular (default) 1448 * - B: bold 1449 * - I: italic 1450 * - BI or IB: bold italic 1451 * @param string $file The font definition file. By default, the name is 1452 * built from the family and style, in lower case 1453 * with no space. 1454 * 1455 * @see setFont() 1456 * @todo Fonts use a class instead of a definition file 1457 */ 1458 public function addFont($family, $style = '', $file = '') 1459 { 1460 $family = Horde_String::lower($family); 1461 if ($family == 'arial') { 1462 $family = 'helvetica'; 1463 } 1464 1465 $style = Horde_String::upper($style); 1466 if ($style == 'IB') { 1467 $style = 'BI'; 1468 } 1469 if (isset($this->_fonts[$family . $style])) { 1470 throw new Horde_Pdf_Exception(sprintf('Font already added: %s %s', $family, $style)); 1471 } 1472 if ($file == '') { 1473 $file = str_replace(' ', '', $family) . Horde_String::lower($style) . '.php'; 1474 } 1475 include $file; 1476 if (!isset($name)) { 1477 throw new Horde_Pdf_Exception('Could not include font definition file'); 1478 } 1479 $i = count($this->_fonts) + 1; 1480 $this->_fonts[$family . $style] = array('i' => $i, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'enc' => $enc, 'file' => $file); 1481 if ($diff) { 1482 /* Search existing encodings. */ 1483 $d = 0; 1484 $nb = count($this->_diffs); 1485 for ($i = 1; $i <= $nb; $i++) { 1486 if ($this->_diffs[$i] == $diff) { 1487 $d = $i; 1488 break; 1489 } 1490 } 1491 if ($d == 0) { 1492 $d = $nb + 1; 1493 $this->_diffs[$d] = $diff; 1494 } 1495 $this->_fonts[$family . $style]['diff'] = $d; 1496 } 1497 if ($file) { 1498 if ($type == 'TrueType') { 1499 $this->_font_files[$file] = array('length1' => $originalsize); 1500 } else { 1501 $this->_font_files[$file] = array('length1' => $size1, 'length2' => $size2); 1502 } 1503 } 1504 } 1505 1506 /** 1507 * Sets the font used to print character strings. 1508 * 1509 * It is mandatory to call this method at least once before printing text 1510 * or the resulting document would not be valid. The font can be either a 1511 * standard one or a font added via the {@link addFont()} method. Standard 1512 * fonts use Windows encoding cp1252 (Western Europe). 1513 * 1514 * The method can be called before the first page is created and the font 1515 * is retained from page to page. 1516 * 1517 * If you just wish to change the current font size, it is simpler to call 1518 * {@link setFontSize()}. 1519 * 1520 * @param string $family Family font. It can be either a name defined by 1521 * {@link addFont()} or one of the standard families 1522 * (case insensitive): 1523 * - Courier (fixed-width) 1524 * - Helvetica or Arial (sans serif) 1525 * - Times (serif) 1526 * - Symbol (symbolic) 1527 * - ZapfDingbats (symbolic) 1528 * It is also possible to pass an empty string. In 1529 * that case, the current family is retained. 1530 * @param string $style Font style. Possible values are (case 1531 * insensitive): 1532 * - empty string: regular 1533 * - B: bold 1534 * - I: italic 1535 * - U: underline 1536 * or any combination. Bold and italic styles do not 1537 * apply to Symbol and ZapfDingbats. 1538 * @param integer $size Font size in points. The default value is the 1539 * current size. If no size has been specified since 1540 * the beginning of the document, the value taken 1541 * is 12. 1542 * @param boolean $force Force the setting of the font. Each new page will 1543 * require a new call to {@link setFont()} and 1544 * setting this to true will make sure that the 1545 * checks for same font calls will be skipped. 1546 * 1547 * @see addFont() 1548 * @see setFontSize() 1549 * @see cell() 1550 * @see multiCell() 1551 * @see write() 1552 */ 1553 public function setFont($family, $style = '', $size = null, $force = false) 1554 { 1555 $family = Horde_String::lower($family); 1556 if (empty($family)) { 1557 $family = $this->_font_family; 1558 } 1559 if ($family == 'arial') { 1560 /* Use helvetica instead of arial. */ 1561 $family = 'helvetica'; 1562 } elseif ($family == 'symbol' || $family == 'zapfdingbats') { 1563 /* These two fonts do not have styles available. */ 1564 $style = ''; 1565 } 1566 1567 $style = Horde_String::upper($style); 1568 1569 /* Underline is handled separately, if specified in the style var 1570 * remove it from the style and set the underline flag. */ 1571 if (strpos($style, 'U') !== false) { 1572 $this->_underline = true; 1573 $style = str_replace('U', '', $style); 1574 } else { 1575 $this->_underline = false; 1576 } 1577 1578 if ($style == 'IB') { 1579 $style = 'BI'; 1580 } 1581 1582 /* If no size specified, use current size. */ 1583 if (is_null($size)) { 1584 $size = $this->_font_size_pt; 1585 } 1586 1587 /* If font requested is already the current font and no force setting 1588 * of the font is requested (eg. when adding a new page) don't bother 1589 * with the rest of the function and simply return. */ 1590 if ($this->_font_family == $family && $this->_font_style == $style && 1591 $this->_font_size_pt == $size && !$force) { 1592 return; 1593 } 1594 1595 /* Set the font key. */ 1596 $fontkey = $family . $style; 1597 1598 /* Test if already cached. */ 1599 if (!isset($this->_fonts[$fontkey])) { 1600 /* Get the character width definition file. */ 1601 $font_widths = self::_getFontFile($fontkey); 1602 1603 $i = count($this->_fonts) + 1; 1604 $this->_fonts[$fontkey] = array( 1605 'i' => $i, 1606 'type' => 'core', 1607 'name' => $this->_core_fonts[$fontkey], 1608 'up' => -100, 1609 'ut' => 50, 1610 'cw' => $font_widths[$fontkey]); 1611 } 1612 1613 /* Store font information as current font. */ 1614 $this->_font_family = $family; 1615 $this->_font_style = $style; 1616 $this->_font_size_pt = $size; 1617 $this->_font_size = $size / $this->_scale; 1618 $this->_current_font = $this->_fonts[$fontkey]; 1619 1620 /* Output font information if at least one page has been defined. */ 1621 if ($this->_page > 0) { 1622 $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->_current_font['i'], $this->_font_size_pt)); 1623 } 1624 } 1625 1626 /** 1627 * Defines the size of the current font. 1628 * 1629 * @param float $size The size (in points). 1630 * 1631 * @see setFont() 1632 */ 1633 public function setFontSize($size) 1634 { 1635 /* If the font size is already the current font size, just return. */ 1636 if ($this->_font_size_pt == $size) { 1637 return; 1638 } 1639 /* Set the current font size, both in points and scaled to user 1640 * units. */ 1641 $this->_font_size_pt = $size; 1642 $this->_font_size = $size / $this->_scale; 1643 1644 /* Output font information if at least one page has been defined. */ 1645 if ($this->_page > 0) { 1646 $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->_current_font['i'], $this->_font_size_pt)); 1647 } 1648 } 1649 1650 /** 1651 * Defines the style of the current font. 1652 * 1653 * @param string $style The font style. 1654 * 1655 * @see setFont() 1656 */ 1657 public function setFontStyle($style) 1658 { 1659 $this->setFont($this->_font_family, $style); 1660 } 1661 1662 /** 1663 * Creates a new internal link and returns its identifier. 1664 * 1665 * An internal link is a clickable area which directs to another place 1666 * within the document. 1667 * 1668 * The identifier can then be passed to {@link cell()}, {@link()} write, 1669 * {@link image()} or {@link link()}. The destination is defined with 1670 * {@link setLink()}. 1671 * 1672 * @see cell() 1673 * @see write() 1674 * @see image() 1675 * @see link() 1676 * @see setLink() 1677 */ 1678 public function addLink() 1679 { 1680 $n = count($this->_links) + 1; 1681 $this->_links[$n] = array(0, 0); 1682 return $n; 1683 } 1684 1685 /** 1686 * Defines the page and position a link points to. 1687 * 1688 * @param integer $link The link identifier returned by {@link addLink()}. 1689 * @param float $y Ordinate of target position; -1 indicates the 1690 * current position. The default value is 0 (top of 1691 * page). 1692 * @param integer $page Number of target page; -1 indicates the current 1693 * page. 1694 * 1695 * @see addLink() 1696 */ 1697 public function setLink($link, $y = 0, $page = -1) 1698 { 1699 if ($y == -1) { 1700 $y = $this->y; 1701 } 1702 if ($page == -1) { 1703 $page = $this->_page; 1704 } 1705 $this->_links[$link] = array($page, $y); 1706 } 1707 1708 /** 1709 * Puts a link on a rectangular area of the page. 1710 * 1711 * Text or image links are generally put via {@link cell()}, {@link 1712 * write()} or {@link image()}, but this method can be useful for instance 1713 * to define a clickable area inside an image. 1714 * 1715 * All coordinates can be negative to provide values from the right or 1716 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 1717 * 1718 * @param float $x Abscissa of the upper-left corner of the 1719 * rectangle. 1720 * @param float $y Ordinate of the upper-left corner of the 1721 * rectangle. 1722 * @param float $width Width of the rectangle. 1723 * @param float $height Height of the rectangle. 1724 * @param mixed $link URL or identifier returned by {@link addLink()}. 1725 * 1726 * @see addLink() 1727 * @see cell() 1728 * @see write() 1729 * @see image() 1730 */ 1731 public function link($x, $y, $width, $height, $link) 1732 { 1733 if ($x < 0) { 1734 $x += $this->w; 1735 } 1736 if ($y < 0) { 1737 $y += $this->h; 1738 } 1739 1740 /* Set up the coordinates with correct scaling in pt. */ 1741 $x = $this->_toPt($x); 1742 $y = $this->hPt - $this->_toPt($y); 1743 $width = $this->_toPt($width); 1744 $height = $this->_toPt($height); 1745 1746 /* Save link to page links array. */ 1747 $this->_link($x, $y, $width, $height, $link); 1748 } 1749 1750 /** 1751 * Prints a character string. 1752 * 1753 * The origin is on the left of the first character, on the baseline. This 1754 * method allows to place a string precisely on the page, but it is 1755 * usually easier to use {@link cell()}, {@link multiCell()} or {@link 1756 * write()} which are the standard methods to print text. 1757 * 1758 * All coordinates can be negative to provide values from the right or 1759 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 1760 * 1761 * @param float $x Abscissa of the origin. 1762 * @param float $y Ordinate of the origin. 1763 * @param string $text String to print. 1764 * 1765 * @see setFont() 1766 * @see cell() 1767 * @see multiCell() 1768 * @see write() 1769 */ 1770 public function text($x, $y, $text) 1771 { 1772 if ($x < 0) { 1773 $x += $this->w; 1774 } 1775 if ($y < 0) { 1776 $y += $this->h; 1777 } 1778 1779 /* Scale coordinates into points and set correct Y position. */ 1780 $x = $this->_toPt($x); 1781 $y = $this->hPt - $this->_toPt($y); 1782 1783 /* Escape any potentially harmful characters. */ 1784 $text = $this->_escape($text); 1785 1786 $out = sprintf('BT %.2F %.2F Td (%s) Tj ET', $x, $y, $text); 1787 if ($this->_underline && $text != '') { 1788 $out .= ' ' . $this->_doUnderline($x, $y, $text); 1789 } 1790 if ($this->_color_flag) { 1791 $out = sprintf('q %s %s Q', $this->_text_color, $out); 1792 } 1793 $this->_out($out); 1794 } 1795 1796 /** 1797 * Whenever a page break condition is met, the method is called, and the 1798 * break is issued or not depending on the returned value. The default 1799 * implementation returns a value according to the mode selected by 1800 * {@link setAutoPageBreak()}. 1801 * This method is called automatically and should not be called directly 1802 * by the application. 1803 * 1804 * @return boolean 1805 * 1806 * @see setAutoPageBreak() 1807 */ 1808 public function acceptPageBreak() 1809 { 1810 return $this->_auto_page_break; 1811 } 1812 1813 /** 1814 * Prints a cell (rectangular area) with optional borders, background 1815 * color and character string. 1816 * 1817 * The upper-left corner of the cell corresponds to the current 1818 * position. The text can be aligned or centered. After the call, the 1819 * current position moves to the right or to the next line. It is possible 1820 * to put a link on the text. If automatic page breaking is enabled and 1821 * the cell goes beyond the limit, a page break is done before outputting. 1822 * 1823 * @param float $width Cell width. If 0, the cell extends up to the right 1824 * margin. 1825 * @param float $height Cell height. 1826 * @param string $text String to print. 1827 * @param mixed $border Indicates if borders must be drawn around the 1828 * cell. The value can be either a number: 1829 * - 0: no border (default) 1830 * - 1: frame 1831 * or a string containing some or all of the 1832 * following characters (in any order): 1833 * - L: left 1834 * - T: top 1835 * - R: right 1836 * - B: bottom 1837 * @param integer $ln Indicates where the current position should go 1838 * after the call. Possible values are: 1839 * - 0: to the right (default) 1840 * - 1: to the beginning of the next line 1841 * - 2: below 1842 * Putting 1 is equivalent to putting 0 and calling 1843 * {@link newLine()} just after. 1844 * @param string $align Allows to center or align the text. Possible 1845 * values are: 1846 * - L or empty string: left (default) 1847 * - C: center 1848 * - R: right 1849 * @param integer $fill Indicates if the cell fill type. Possible values 1850 * are: 1851 * - 0: transparent (default) 1852 * - 1: painted 1853 * @param string $link URL or identifier returned by {@link addLink()}. 1854 * 1855 * @see setFont() 1856 * @see setDrawColor() 1857 * @see setFillColor() 1858 * @see setLineWidth() 1859 * @see addLink() 1860 * @see newLine() 1861 * @see multiCell() 1862 * @see write() 1863 * @see setAutoPageBreak() 1864 */ 1865 public function cell($width, $height = 0, $text = '', $border = 0, $ln = 0, 1866 $align = '', $fill = 0, $link = '') 1867 { 1868 $k = $this->_scale; 1869 if ($this->y + $height > $this->_page_break_trigger && 1870 !$this->_in_footer && $this->acceptPageBreak()) { 1871 $x = $this->x; 1872 $ws = $this->_word_spacing; 1873 if ($ws > 0) { 1874 $this->_word_spacing = 0; 1875 $this->_out('0 Tw'); 1876 } 1877 $this->addPage($this->_current_orientation); 1878 $this->x = $x; 1879 if ($ws > 0) { 1880 $this->_word_spacing = $ws; 1881 $this->_out(sprintf('%.3F Tw', $ws * $k)); 1882 } 1883 } 1884 if ($width == 0) { 1885 $width = $this->w - $this->_right_margin - $this->x; 1886 } 1887 $s = ''; 1888 if ($fill == 1 || $border == 1) { 1889 if ($fill == 1) { 1890 $op = ($border == 1) ? 'B' : 'f'; 1891 } else { 1892 $op = 'S'; 1893 } 1894 $s = sprintf('%.2F %.2F %.2F %.2F re %s ', $this->x * $k, ($this->h - $this->y) * $k, $width * $k, -$height * $k, $op); 1895 } 1896 if (is_string($border)) { 1897 if (strpos($border, 'L') !== false) { 1898 $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $this->x * $k, ($this->h - $this->y) * $k, $this->x * $k, ($this->h - ($this->y + $height)) * $k); 1899 } 1900 if (strpos($border, 'T') !== false) { 1901 $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $this->x * $k, ($this->h - $this->y) * $k, ($this->x + $width) * $k, ($this->h - $this->y) * $k); 1902 } 1903 if (strpos($border, 'R') !== false) { 1904 $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', ($this->x + $width) * $k, ($this->h - $this->y) * $k, ($this->x + $width) * $k, ($this->h - ($this->y + $height)) * $k); 1905 } 1906 if (strpos($border, 'B') !== false) { 1907 $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $this->x * $k, ($this->h - ($this->y + $height)) * $k, ($this->x + $width) * $k, ($this->h - ($this->y + $height)) * $k); 1908 } 1909 } 1910 if ($text != '') { 1911 if ($align == 'R') { 1912 $dx = $width - $this->_cell_margin - $this->getStringWidth($text); 1913 } elseif ($align == 'C') { 1914 $dx = ($width - $this->getStringWidth($text)) / 2; 1915 } else { 1916 $dx = $this->_cell_margin; 1917 } 1918 if ($this->_color_flag) { 1919 $s .= 'q ' . $this->_text_color . ' '; 1920 } 1921 $text = str_replace(')', '\\)', str_replace('(', '\\(', str_replace('\\', '\\\\', $text))); 1922 $test2 = ((.5 * $height) + (.3 * $this->_font_size)); 1923 $test1 = $this->fhPt - (($this->y + $test2) * $k); 1924 $x = ($this->x + $dx) * $k; 1925 $y = ($this->h - ($this->y + .5 * $height + .3 * $this->_font_size)) * $k; 1926 $s .= sprintf('BT %.2F %.2F Td (%s) Tj ET', $x, $y, $text); 1927 if ($this->_underline) { 1928 $s .= ' ' . $this->_doUnderline($x, $y, $text); 1929 } 1930 if ($this->_color_flag) { 1931 $s .= ' Q'; 1932 } 1933 if ($link) { 1934 $this->link($this->x + $dx, $this->y + .5 * $height- .5 * $this->_font_size, $this->getStringWidth($text), $this->_font_size, $link); 1935 } 1936 } 1937 if ($s) { 1938 $this->_out($s); 1939 } 1940 $this->_last_height = $height; 1941 if ($ln > 0) { 1942 // Go to next line. 1943 $this->y += $height; 1944 if ($ln == 1) { 1945 $this->x = $this->_left_margin; 1946 } 1947 } else { 1948 $this->x += $width; 1949 } 1950 } 1951 1952 /** 1953 * This method allows printing text with line breaks. 1954 * 1955 * They can be automatic (as soon as the text reaches the right border of 1956 * the cell) or explicit (via the \n character). As many cells as 1957 * necessary are output, one below the other. Text can be aligned, 1958 * centered or justified. The cell block can be framed and the background 1959 * painted. 1960 * 1961 * @param float $width Width of cells. If 0, they extend up to the right 1962 * margin of the page. 1963 * @param float $height Height of cells. 1964 * @param string $text String to print. 1965 * @param mixed $border Indicates if borders must be drawn around the cell 1966 * block. The value can be either a number: 1967 * - 0: no border (default) 1968 * - 1: frame 1969 * or a string containing some or all of the 1970 * following characters (in any order): 1971 * - L: left 1972 * - T: top 1973 * - R: right 1974 * - B: bottom 1975 * @param string $align Sets the text alignment. Possible values are: 1976 * - L: left alignment 1977 * - C: center 1978 * - R: right alignment 1979 * - J: justification (default value) 1980 * @param integer $fill Indicates if the cell background must: 1981 * - 0: transparent (default) 1982 * - 1: painted 1983 * 1984 * @see setFont() 1985 * @see setDrawColor() 1986 * @see setFillColor() 1987 * @see setLineWidth() 1988 * @see cell() 1989 * @see write() 1990 * @see setAutoPageBreak() 1991 */ 1992 public function multiCell($width, $height, $text, $border = 0, $align = 'J', 1993 $fill = 0) 1994 { 1995 $cw = $this->_current_font['cw']; 1996 if ($width == 0) { 1997 $width = $this->w - $this->_right_margin - $this->x; 1998 } 1999 $wmax = ($width-2 * $this->_cell_margin) * 1000 / $this->_font_size; 2000 $s = str_replace("\r", '', $text); 2001 $nb = strlen($s); 2002 if ($nb > 0 && $s[$nb-1] == "\n") { 2003 $nb--; 2004 } 2005 $b = 0; 2006 if ($border) { 2007 if ($border == 1) { 2008 $border = 'LTRB'; 2009 $b = 'LRT'; 2010 $b2 = 'LR'; 2011 } else { 2012 $b2 = ''; 2013 if (strpos($border, 'L') !== false) { 2014 $b2 .= 'L'; 2015 } 2016 if (strpos($border, 'R') !== false) { 2017 $b2 .= 'R'; 2018 } 2019 $b = (strpos($border, 'T') !== false) ? $b2 . 'T' : $b2; 2020 } 2021 } 2022 $sep = -1; 2023 $i = 0; 2024 $j = 0; 2025 $l = 0; 2026 $ns = 0; 2027 $nl = 1; 2028 while ($i < $nb) { 2029 // Get next character. 2030 $c = $s[$i]; 2031 if ($c == "\n") { 2032 // Explicit line break. 2033 if ($this->_word_spacing > 0) { 2034 $this->_word_spacing = 0; 2035 $this->_out('0 Tw'); 2036 } 2037 $this->cell($width, $height, substr($s, $j, $i-$j), $b, 2, $align, $fill); 2038 $i++; 2039 $sep = -1; 2040 $j = $i; 2041 $l = 0; 2042 $ns = 0; 2043 $nl++; 2044 if ($border && $nl == 2) { 2045 $b = $b2; 2046 } 2047 continue; 2048 } 2049 if ($c == ' ') { 2050 $sep = $i; 2051 $ls = $l; 2052 $ns++; 2053 } 2054 $l += $cw[$c]; 2055 if ($l > $wmax) { 2056 // Automatic line break. 2057 if ($sep == -1) { 2058 if ($i == $j) { 2059 $i++; 2060 } 2061 if ($this->_word_spacing > 0) { 2062 $this->_word_spacing = 0; 2063 $this->_out('0 Tw'); 2064 } 2065 $this->cell($width, $height, substr($s, $j, $i - $j), $b, 2, $align, $fill); 2066 } else { 2067 if ($align == 'J') { 2068 $this->_word_spacing = ($ns>1) ? ($wmax - $ls)/1000 * $this->_font_size / ($ns - 1) : 0; 2069 $this->_out(sprintf('%.3F Tw', $this->_word_spacing * $this->_scale)); 2070 } 2071 $this->cell($width, $height, substr($s, $j, $sep - $j), $b, 2, $align, $fill); 2072 $i = $sep + 1; 2073 } 2074 $sep = -1; 2075 $j = $i; 2076 $l = 0; 2077 $ns = 0; 2078 $nl++; 2079 if ($border && $nl == 2) { 2080 $b = $b2; 2081 } 2082 } else { 2083 $i++; 2084 } 2085 } 2086 // Last chunk. 2087 if ($this->_word_spacing > 0) { 2088 $this->_word_spacing = 0; 2089 $this->_out('0 Tw'); 2090 } 2091 if ($border && strpos($border, 'B') !== false) { 2092 $b .= 'B'; 2093 } 2094 $this->cell($width, $height, substr($s, $j, $i), $b, 2, $align, $fill); 2095 $this->x = $this->_left_margin; 2096 } 2097 2098 /** 2099 * This method prints text from the current position. 2100 * 2101 * When the right margin is reached (or the \n character is met) a line 2102 * break occurs and text continues from the left margin. Upon method exit, 2103 * the current position is left just at the end of the text. 2104 * 2105 * It is possible to put a link on the text. 2106 * 2107 * Example: 2108 * <code> 2109 * // Begin with regular font 2110 * $pdf->setFont('Arial', '', 14); 2111 * $pdf->write(5, 'Visit '); 2112 * // Then put a blue underlined link 2113 * $pdf->setTextColor(0, 0, 255); 2114 * $pdf->setFont('', 'U'); 2115 * $pdf->write(5, 'www.fpdf.org', 'http://www.fpdf.org'); 2116 * </code> 2117 * 2118 * @param float $height Line height. 2119 * @param string $text String to print. 2120 * @param mixed $link URL or identifier returned by {@link addLink()}. 2121 * 2122 * @see setFont() 2123 * @see addLink() 2124 * @see multiCell() 2125 * @see setAutoPageBreak() 2126 */ 2127 public function write($height, $text, $link = '') 2128 { 2129 $cw = $this->_current_font['cw']; 2130 $width = $this->w - $this->_right_margin - $this->x; 2131 $wmax = ($width - 2 * $this->_cell_margin) * 1000 / $this->_font_size; 2132 $s = str_replace("\r", '', $text); 2133 $nb = strlen($s); 2134 $sep = -1; 2135 $i = 0; 2136 $j = 0; 2137 $l = 0; 2138 $nl = 1; 2139 while ($i < $nb) { 2140 // Get next character. 2141 $c = $s[$i]; 2142 if ($c == "\n") { 2143 // Explicit line break. 2144 $this->cell($width, $height, substr($s, $j, $i - $j), 0, 2, '', 0, $link); 2145 $i++; 2146 $sep = -1; 2147 $j = $i; 2148 $l = 0; 2149 if ($nl == 1) { 2150 $this->x = $this->_left_margin; 2151 $width = $this->w - $this->_right_margin - $this->x; 2152 $wmax = ($width - 2 * $this->_cell_margin) * 1000 / $this->_font_size; 2153 } 2154 $nl++; 2155 continue; 2156 } 2157 if ($c == ' ') { 2158 $sep = $i; 2159 $ls = $l; 2160 } 2161 $l += (isset($cw[$c]) ? $cw[$c] : 0); 2162 if ($l > $wmax) { 2163 // Automatic line break. 2164 if ($sep == -1) { 2165 if ($this->x > $this->_left_margin) { 2166 // Move to next line. 2167 $this->x = $this->_left_margin; 2168 $this->y += $height; 2169 $width = $this->w - $this->_right_margin - $this->x; 2170 $wmax = ($width - 2 * $this->_cell_margin) * 1000 / $this->_font_size; 2171 $i++; 2172 $nl++; 2173 continue; 2174 } 2175 if ($i == $j) { 2176 $i++; 2177 } 2178 $this->cell($width, $height, substr($s, $j, $i - $j), 0, 2, '', 0, $link); 2179 } else { 2180 $this->cell($width, $height, substr($s, $j, $sep - $j), 0, 2, '', 0, $link); 2181 $i = $sep + 1; 2182 } 2183 $sep = -1; 2184 $j = $i; 2185 $l = 0; 2186 if ($nl == 1) { 2187 $this->x = $this->_left_margin; 2188 $width = $this->w - $this->_right_margin - $this->x; 2189 $wmax = ($width - 2 * $this->_cell_margin) * 1000 / $this->_font_size; 2190 } 2191 $nl++; 2192 } else { 2193 $i++; 2194 } 2195 } 2196 // Last chunk. 2197 if ($i != $j) { 2198 $this->cell($l / 1000 * $this->_font_size, $height, substr($s, $j, $i), 0, 0, '', 0, $link); 2199 } 2200 } 2201 2202 /** 2203 * Writes text at an angle. 2204 * 2205 * All coordinates can be negative to provide values from the right or 2206 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 2207 * 2208 * @param integer $x X coordinate. 2209 * @param integer $y Y coordinate. 2210 * @param string $text Text to write. 2211 * @param float $text_angle Angle to rotate (Eg. 90 = bottom to top). 2212 * @param float $font_angle Rotate characters as well as text. 2213 * 2214 * @see setFont() 2215 */ 2216 public function writeRotated($x, $y, $text, $text_angle, $font_angle = 0) 2217 { 2218 if ($x < 0) { 2219 $x += $this->w; 2220 } 2221 if ($y < 0) { 2222 $y += $this->h; 2223 } 2224 2225 // Escape text. 2226 $text = $this->_escape($text); 2227 2228 $font_angle += 90 + $text_angle; 2229 $text_angle *= M_PI / 180; 2230 $font_angle *= M_PI / 180; 2231 2232 $text_dx = cos($text_angle); 2233 $text_dy = sin($text_angle); 2234 $font_dx = cos($font_angle); 2235 $font_dy = sin($font_angle); 2236 2237 $s= sprintf('BT %.2F %.2F %.2F %.2F %.2F %.2F Tm (%s) Tj ET', 2238 $text_dx, $text_dy, $font_dx, $font_dy, 2239 $x * $this->_scale, ($this->h-$y) * $this->_scale, $text); 2240 2241 if ($this->_draw_color) { 2242 $s = 'q ' . $this->_draw_color . ' ' . $s . ' Q'; 2243 } 2244 $this->_out($s); 2245 } 2246 2247 /** 2248 * Prints an image in the page. 2249 * 2250 * The upper-left corner and at least one of the dimensions must be 2251 * specified; the height or the width can be calculated automatically in 2252 * order to keep the image proportions. Supported formats are JPEG and 2253 * PNG. 2254 * 2255 * All coordinates can be negative to provide values from the right or 2256 * bottom edge of the page (since File_Pdf 0.2.0, Horde 3.2). 2257 * 2258 * For JPEG, all flavors are allowed: 2259 * - gray scales 2260 * - true colors (24 bits) 2261 * - CMYK (32 bits) 2262 * 2263 * For PNG, are allowed: 2264 * - gray scales on at most 8 bits (256 levels) 2265 * - indexed colors 2266 * - true colors (24 bits) 2267 * but are not supported: 2268 * - Interlacing 2269 * - Alpha channel 2270 * 2271 * If a transparent color is defined, it will be taken into account (but 2272 * will be only interpreted by Acrobat 4 and above). 2273 * The format can be specified explicitly or inferred from the file 2274 * extension. 2275 * It is possible to put a link on the image. 2276 * 2277 * Remark: if an image is used several times, only one copy will be 2278 * embedded in the file. 2279 * 2280 * @param string $file Name of the file containing the image. 2281 * @param float $x Abscissa of the upper-left corner. 2282 * @param float $y Ordinate of the upper-left corner. 2283 * @param float $width Width of the image in the page. If equal to zero, 2284 * it is automatically calculated to keep the 2285 * original proportions. 2286 * @param float $height Height of the image in the page. If not specified 2287 * or equal to zero, it is automatically calculated 2288 * to keep the original proportions. 2289 * @param string $type Image format. Possible values are (case 2290 * insensitive): JPG, JPEG, PNG. If not specified, 2291 * the type is inferred from the file extension. 2292 * @param mixed $link URL or identifier returned by {@link addLink()}. 2293 * 2294 * @see addLink() 2295 */ 2296 public function image($file, $x, $y, $width = 0, $height = 0, $type = '', 2297 $link = '') 2298 { 2299 if ($x < 0) { 2300 $x += $this->w; 2301 } 2302 if ($y < 0) { 2303 $y += $this->h; 2304 } 2305 2306 if (!isset($this->_images[$file])) { 2307 // First use of image, get some file info. 2308 if ($type == '') { 2309 $pos = strrpos($file, '.'); 2310 if ($pos === false) { 2311 throw new Horde_Pdf_Exception(sprintf('Image file has no extension and no type was specified: %s', $file)); 2312 } 2313 $type = substr($file, $pos + 1); 2314 } 2315 2316 $mqr = function_exists("get_magic_quotes_runtime") ? @get_magic_quotes_runtime() : 0; 2317 if ($mqr) { set_magic_quotes_runtime(0); } 2318 2319 $type = Horde_String::lower($type); 2320 if ($type == 'jpg' || $type == 'jpeg') { 2321 $info = $this->_parseJPG($file); 2322 } elseif ($type == 'png') { 2323 $info = $this->_parsePNG($file); 2324 } else { 2325 throw new Horde_Pdf_Exception(sprintf('Unsupported image file type: %s', $type)); 2326 } 2327 2328 if ($mqr) { set_magic_quotes_runtime($mqr); } 2329 2330 $info['i'] = count($this->_images) + 1; 2331 $this->_images[$file] = $info; 2332 } else { 2333 $info = $this->_images[$file]; 2334 } 2335 2336 // Make sure all vars are converted to pt scale. 2337 $x = $this->_toPt($x); 2338 $y = $this->hPt - $this->_toPt($y); 2339 $width = $this->_toPt($width); 2340 $height = $this->_toPt($height); 2341 2342 // If not specified do automatic width and height calculations. 2343 if (empty($width) && empty($height)) { 2344 $width = $info['w']; 2345 $height = $info['h']; 2346 } elseif (empty($width)) { 2347 $width = $height * $info['w'] / $info['h']; 2348 } elseif (empty($height)) { 2349 $height = $width * $info['h'] / $info['w']; 2350 } 2351 2352 $this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q', $width, $height, $x, $y - $height, $info['i'])); 2353 2354 // Set any link if requested. 2355 if ($link) { 2356 $this->_link($x, $y, $width, $height, $link); 2357 } 2358 } 2359 2360 /** 2361 * Performs a line break. 2362 * 2363 * The current abscissa goes back to the left margin and the ordinate 2364 * increases by the amount passed in parameter. 2365 * 2366 * @param float $height The height of the break. By default, the value 2367 * equals the height of the last printed cell. 2368 * 2369 * @see cell() 2370 */ 2371 public function newLine($height = '') 2372 { 2373 $this->x = $this->_left_margin; 2374 if (is_string($height)) { 2375 $this->y += $this->_last_height; 2376 } else { 2377 $this->y += $height; 2378 } 2379 } 2380 2381 /** 2382 * Get the current page 2383 * 2384 * @return integer 2385 */ 2386 public function getPage() 2387 { 2388 return $this->_page; 2389 } 2390 2391 /** 2392 * Set the current page 2393 * @param integer $page 2394 */ 2395 public function setPage($page) 2396 { 2397 $this->_page = $page; 2398 } 2399 2400 /** 2401 * Returns the abscissa of the current position in user units. 2402 * 2403 * @return float 2404 * 2405 * @see setX() 2406 * @see getY() 2407 * @see setY() 2408 */ 2409 public function getX() 2410 { 2411 return $this->x; 2412 } 2413 2414 /** 2415 * Defines the abscissa of the current position. 2416 * 2417 * If the passed value is negative, it is relative to the right of the 2418 * page. 2419 * 2420 * @param float $x The value of the abscissa. 2421 * 2422 * @see getX() 2423 * @see getY() 2424 * @see setY() 2425 * @see setXY() 2426 */ 2427 public function setX($x) 2428 { 2429 if ($x >= 0) { 2430 // Absolute value. 2431 $this->x = $x; 2432 } else { 2433 // Negative, so relative to right edge of the page. 2434 $this->x = $this->w + $x; 2435 } 2436 } 2437 2438 /** 2439 * Returns the ordinate of the current position in user units. 2440 * 2441 * @return float 2442 * 2443 * @see setY() 2444 * @see getX() 2445 * @see setX() 2446 */ 2447 public function getY() 2448 { 2449 return $this->y; 2450 } 2451 2452 /** 2453 * Defines the ordinate of the current position. 2454 * 2455 * If the passed value is negative, it is relative to the bottom of the 2456 * page. 2457 * 2458 * @param float $y The value of the ordinate. 2459 * 2460 * @see getX() 2461 * @see getY() 2462 * @see setY() 2463 * @see setXY() 2464 */ 2465 public function setY($y) 2466 { 2467 if ($y >= 0) { 2468 // Absolute value. 2469 $this->y = $y; 2470 } else { 2471 // Negative, so relative to bottom edge of the page. 2472 $this->y = $this->h + $y; 2473 } 2474 } 2475 2476 /** 2477 * Defines the abscissa and ordinate of the current position. 2478 * 2479 * If the passed values are negative, they are relative respectively to 2480 * the right and bottom of the page. 2481 * 2482 * @param float $x The value of the abscissa. 2483 * @param float $y The value of the ordinate. 2484 * 2485 * @see setX() 2486 * @see setY() 2487 */ 2488 public function setXY($x, $y) 2489 { 2490 $this->setY($y); 2491 $this->setX($x); 2492 } 2493 2494 /** 2495 * Returns the current buffer content and resets the buffer. 2496 * 2497 * Use this method when creating large files to avoid memory problems. 2498 * This method doesn't work in combination with the save() method, 2499 * use getOutput() at the end. Calling this method doubles the 2500 * memory usage during the call. 2501 * 2502 * @see getOutput() 2503 */ 2504 public function flush() 2505 { 2506 // Make sure we have the file header. 2507 $this->_beginDoc(); 2508 2509 $buffer = $this->_buffer; 2510 $this->_buffer = ''; 2511 $this->_flushed = true; 2512 $this->_buflen += strlen($buffer); 2513 2514 return $buffer; 2515 } 2516 2517 /** 2518 * Returns the raw Pdf file. 2519 * 2520 * @see flush() 2521 */ 2522 public function getOutput() 2523 { 2524 // Check whether file has been closed. 2525 if ($this->_state < 3) { 2526 $this->close(); 2527 } 2528 2529 return $this->_buffer; 2530 } 2531 2532 /** 2533 * Saves the PDF file on the filesystem. 2534 * 2535 * @param string $filename The filename for the output file. 2536 */ 2537 public function save($filename = 'unknown.pdf') 2538 { 2539 // Check whether the buffer has been flushed already. 2540 if ($this->_flushed) { 2541 throw new Horde_Pdf_Exception('The buffer has been flushed already, don\'t use save() in combination with flush().'); 2542 } 2543 2544 // Check whether file has been closed. 2545 if ($this->_state < 3) { 2546 $this->close(); 2547 } 2548 2549 $f = fopen($filename, 'wb'); 2550 if (!$f) { 2551 throw new Horde_Pdf_Exception(sprintf('Unable to save Pdf file: %s', $filename)); 2552 } 2553 fwrite($f, $this->_buffer, strlen($this->_buffer)); 2554 fclose($f); 2555 } 2556 2557 /** 2558 * Scale a value. 2559 * 2560 * @param integer $val Value 2561 * @return integer Value multiplied by scale 2562 */ 2563 protected function _toPt($val) 2564 { 2565 return $val * $this->_scale; 2566 } 2567 2568 /** 2569 * Load information about a font from its key name. 2570 * 2571 * @param string $fontkey Font name key 2572 * @return array Array of all font widths, including this font. 2573 */ 2574 protected static function _getFontFile($fontkey) 2575 { 2576 if (!isset(self::$_font_widths[$fontkey])) { 2577 $fontClass = 'Horde_Pdf_Font_' . Horde_String::ucfirst(Horde_String::lower($fontkey)); 2578 if (!class_exists($fontClass)) { 2579 throw new Horde_Pdf_Exception(sprintf('Could not include font metric class: %s', $fontClass)); 2580 } 2581 2582 $font = new $fontClass; 2583 2584 self::$_font_widths = array_merge(self::$_font_widths, $font->getWidths()); 2585 if (!isset(self::$_font_widths[$fontkey])) { 2586 throw new Horde_Pdf_Exception(sprintf('Could not include font metric class: %s', $fontClass)); 2587 } 2588 } 2589 2590 return self::$_font_widths; 2591 } 2592 2593 /** 2594 * Save link to page links array. 2595 * 2596 * @param integer $x X-coordinate 2597 * @param integer $y Y-coordinate 2598 * @param integer $width Width 2599 * @param integer $height Height 2600 * @param string $link Link 2601 * @return void 2602 */ 2603 protected function _link($x, $y, $width, $height, $link) 2604 { 2605 $this->_page_links[$this->_page][] = array($x, $y, $width, $height, $link); 2606 } 2607 2608 /** 2609 * Begin the PDF document. 2610 * 2611 * @return void 2612 */ 2613 protected function _beginDoc() 2614 { 2615 // Start document, but only if not yet started. 2616 if ($this->_state < 1) { 2617 $this->_state = 1; 2618 $this->_out('%PDF-1.3'); 2619 } 2620 } 2621 2622 /** 2623 * Write the PDF pages. 2624 * 2625 * @return void 2626 */ 2627 protected function _putPages() 2628 { 2629 $nb = $this->_page; 2630 if (!empty($this->_alias_nb_pages)) { 2631 // Replace number of pages. 2632 for ($n = 1; $n <= $nb; $n++) { 2633 $this->_pages[$n] = str_replace($this->_alias_nb_pages, $nb, $this->_pages[$n]); 2634 } 2635 } 2636 if ($this->_default_orientation == 'P') { 2637 $wPt = $this->fwPt; 2638 $hPt = $this->fhPt; 2639 } else { 2640 $wPt = $this->fhPt; 2641 $hPt = $this->fwPt; 2642 } 2643 $filter = ($this->_compress) ? '/Filter /FlateDecode ' : ''; 2644 for ($n = 1; $n <= $nb; $n++) { 2645 // Page 2646 $this->_newobj(); 2647 $this->_out('<</Type /Page'); 2648 $this->_out('/Parent 1 0 R'); 2649 if (isset($this->_orientation_changes[$n])) { 2650 $this->_out(sprintf('/MediaBox [0 0 %.2F %.2F]', $hPt, $wPt)); 2651 } 2652 $this->_out('/Resources 2 0 R'); 2653 if (isset($this->_page_links[$n])) { 2654 // Links 2655 $annots = '/Annots ['; 2656 foreach ($this->_page_links[$n] as $pl) { 2657 $rect = sprintf('%.2F %.2F %.2F %.2F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3]); 2658 $annots .= '<</Type /Annot /Subtype /Link /Rect [' . $rect . '] /Border [0 0 0] '; 2659 if (is_string($pl[4])) { 2660 $annots .= '/A <</S /URI /URI ' . $this->_textString($pl[4]) . '>>>>'; 2661 } else { 2662 $l = $this->_links[$pl[4]]; 2663 $height = isset($this->_orientation_changes[$l[0]]) ? $wPt : $hPt; 2664 $annots .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>', 1 + 2 * $l[0], $height - $l[1] * $this->_scale); 2665 } 2666 } 2667 $this->_out($annots . ']'); 2668 } 2669 $this->_out('/Contents ' . ($this->_n + 1) . ' 0 R>>'); 2670 $this->_out('endobj'); 2671 // Page content 2672 $p = ($this->_compress) ? gzcompress($this->_pages[$n]) : $this->_pages[$n]; 2673 $this->_newobj(); 2674 $this->_out('<<' . $filter . '/Length ' . strlen($p) . '>>'); 2675 $this->_putStream($p); 2676 $this->_out('endobj'); 2677 } 2678 // Pages root 2679 $this->_offsets[1] = $this->_buflen + strlen($this->_buffer); 2680 $this->_out('1 0 obj'); 2681 $this->_out('<</Type /Pages'); 2682 $kids = '/Kids ['; 2683 for ($i = 0; $i < $nb; $i++) { 2684 $kids .= (3 + 2 * $i) . ' 0 R '; 2685 } 2686 $this->_out($kids . ']'); 2687 $this->_out('/Count ' . $nb); 2688 $this->_out(sprintf('/MediaBox [0 0 %.2F %.2F]', $wPt, $hPt)); 2689 $this->_out('>>'); 2690 $this->_out('endobj'); 2691 } 2692 2693 /** 2694 * Write the PDF fonts. 2695 * 2696 * @return void 2697 */ 2698 protected function _putFonts() 2699 { 2700 $nf = $this->_n; 2701 foreach ($this->_diffs as $diff) { 2702 // Encodings 2703 $this->_newobj(); 2704 $this->_out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' . $diff . ']>>'); 2705 $this->_out('endobj'); 2706 } 2707 2708 $mqr = function_exists("get_magic_quotes_runtime") ? @get_magic_quotes_runtime() : 0; 2709 if ($mqr) { set_magic_quotes_runtime(0); } 2710 2711 foreach ($this->_font_files as $file => $info) { 2712 // Font file embedding. 2713 $this->_newobj(); 2714 $this->_font_files[$file]['n'] = $this->_n; 2715 $size = filesize($file); 2716 if (!$size) { 2717 throw new Horde_Pdf_Exception('Font file not found'); 2718 } 2719 $this->_out('<</Length ' . $size); 2720 if (substr($file, -2) == '.z') { 2721 $this->_out('/Filter /FlateDecode'); 2722 } 2723 $this->_out('/Length1 ' . $info['length1']); 2724 if (isset($info['length2'])) { 2725 $this->_out('/Length2 ' . $info['length2'] . ' /Length3 0'); 2726 } 2727 $this->_out('>>'); 2728 $f = fopen($file, 'rb'); 2729 $this->_putStream(fread($f, $size)); 2730 fclose($f); 2731 $this->_out('endobj'); 2732 } 2733 2734 if ($mqr) { set_magic_quotes_runtime($mqr); } 2735 2736 foreach ($this->_fonts as $k => $font) { 2737 // Font objects 2738 $this->_newobj(); 2739 $this->_fonts[$k]['n'] = $this->_n; 2740 $name = $font['name']; 2741 $this->_out('<</Type /Font'); 2742 $this->_out('/BaseFont /' . $name); 2743 if ($font['type'] == 'core') { 2744 // Standard font. 2745 $this->_out('/Subtype /Type1'); 2746 if ($name != 'Symbol' && $name != 'ZapfDingbats') { 2747 $this->_out('/Encoding /WinAnsiEncoding'); 2748 } 2749 } else { 2750 // Additional font. 2751 $this->_out('/Subtype /' . $font['type']); 2752 $this->_out('/FirstChar 32'); 2753 $this->_out('/LastChar 255'); 2754 $this->_out('/Widths ' . ($this->_n + 1) . ' 0 R'); 2755 $this->_out('/FontDescriptor ' . ($this->_n + 2) . ' 0 R'); 2756 if ($font['enc']) { 2757 if (isset($font['diff'])) { 2758 $this->_out('/Encoding ' . ($nf + $font['diff']) . ' 0 R'); 2759 } else { 2760 $this->_out('/Encoding /WinAnsiEncoding'); 2761 } 2762 } 2763 } 2764 $this->_out('>>'); 2765 $this->_out('endobj'); 2766 if ($font['type'] != 'core') { 2767 // Widths. 2768 $this->_newobj(); 2769 $cw = $font['cw']; 2770 $s = '['; 2771 for ($i = 32; $i <= 255; $i++) { 2772 $s .= $cw[chr($i)] . ' '; 2773 } 2774 $this->_out($s . ']'); 2775 $this->_out('endobj'); 2776 // Descriptor. 2777 $this->_newobj(); 2778 $s = '<</Type /FontDescriptor /FontName /' . $name; 2779 foreach ($font['desc'] as $k => $v) { 2780 $s .= ' /' . $k . ' ' . $v; 2781 } 2782 $file = $font['file']; 2783 if ($file) { 2784 $s .= ' /FontFile' . ($font['type'] == 'Type1' ? '' : '2') . ' ' . $this->_font_files[$file]['n'] . ' 0 R'; 2785 } 2786 $this->_out($s . '>>'); 2787 $this->_out('endobj'); 2788 } 2789 } 2790 } 2791 2792 /** 2793 * Write the PDF images. 2794 * 2795 * @return void 2796 */ 2797 protected function _putImages() 2798 { 2799 $filter = ($this->_compress) ? '/Filter /FlateDecode ' : ''; 2800 foreach ($this->_images as $file => $info) { 2801 $this->_newobj(); 2802 $this->_images[$file]['n'] = $this->_n; 2803 $this->_out('<</Type /XObject'); 2804 $this->_out('/Subtype /Image'); 2805 $this->_out('/Width ' . $info['w']); 2806 $this->_out('/Height ' . $info['h']); 2807 if ($info['cs'] == 'Indexed') { 2808 $this->_out('/ColorSpace [/Indexed /DeviceRGB ' . (strlen($info['pal'])/3 - 1) . ' ' . ($this->_n + 1) . ' 0 R]'); 2809 } else { 2810 $this->_out('/ColorSpace /' . $info['cs']); 2811 if ($info['cs'] == 'DeviceCMYK') { 2812 $this->_out('/Decode [1 0 1 0 1 0 1 0]'); 2813 } 2814 } 2815 $this->_out('/BitsPerComponent ' . $info['bpc']); 2816 $this->_out('/Filter /' . $info['f']); 2817 if (isset($info['parms'])) { 2818 $this->_out($info['parms']); 2819 } 2820 if (isset($info['trns']) && is_array($info['trns'])) { 2821 $trns = ''; 2822 $i_max = count($info['trns']); 2823 for ($i = 0; $i < $i_max; $i++) { 2824 $trns .= $info['trns'][$i] . ' ' . $info['trns'][$i] . ' '; 2825 } 2826 $this->_out('/Mask [' . $trns . ']'); 2827 } 2828 $this->_out('/Length ' . strlen($info['data']) . '>>'); 2829 $this->_putStream($info['data']); 2830 $this->_out('endobj'); 2831 2832 // Palette. 2833 if ($info['cs'] == 'Indexed') { 2834 $this->_newobj(); 2835 $pal = ($this->_compress) ? gzcompress($info['pal']) : $info['pal']; 2836 $this->_out('<<' . $filter . '/Length ' . strlen($pal) . '>>'); 2837 $this->_putStream($pal); 2838 $this->_out('endobj'); 2839 } 2840 } 2841 } 2842 2843 /** 2844 * Write the PDF resources. 2845 * 2846 * @return void 2847 */ 2848 protected function _putResources() 2849 { 2850 $this->_putFonts(); 2851 $this->_putImages(); 2852 // Resource dictionary 2853 $this->_offsets[2] = $this->_buflen + strlen($this->_buffer); 2854 $this->_out('2 0 obj'); 2855 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); 2856 $this->_out('/Font <<'); 2857 foreach ($this->_fonts as $font) { 2858 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); 2859 } 2860 $this->_out('>>'); 2861 if (count($this->_images)) { 2862 $this->_out('/XObject <<'); 2863 foreach ($this->_images as $image) { 2864 $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); 2865 } 2866 $this->_out('>>'); 2867 } 2868 $this->_out('>>'); 2869 $this->_out('endobj'); 2870 } 2871 2872 /** 2873 * Write the PDF information. 2874 * 2875 * @return void 2876 */ 2877 protected function _putInfo() 2878 { 2879 $this->_out('/Producer ' . $this->_textString('Horde PDF')); 2880 if (!empty($this->_info['title'])) { 2881 $this->_out('/Title ' . $this->_textString($this->_info['title'])); 2882 } 2883 if (!empty($this->_info['subject'])) { 2884 $this->_out('/Subject ' . $this->_textString($this->_info['subject'])); 2885 } 2886 if (!empty($this->_info['author'])) { 2887 $this->_out('/Author ' . $this->_textString($this->_info['author'])); 2888 } 2889 if (!empty($this->keywords)) { 2890 $this->_out('/Keywords ' . $this->_textString($this->keywords)); 2891 } 2892 if (!empty($this->creator)) { 2893 $this->_out('/Creator ' . $this->_textString($this->creator)); 2894 } 2895 if (!isset($this->_info['CreationDate'])) { 2896 $this->_info['CreationDate'] = 'D:' . date('YmdHis', time()); 2897 } 2898 $this->_out('/CreationDate ' . $this->_textString($this->_info['CreationDate'])); 2899 } 2900 2901 /** 2902 * Write the PDF catalog. 2903 * 2904 * @return void 2905 */ 2906 protected function _putCatalog() 2907 { 2908 $this->_out('/Type /Catalog'); 2909 $this->_out('/Pages 1 0 R'); 2910 if ($this->_zoom_mode == 'fullpage') { 2911 $this->_out('/OpenAction [3 0 R /Fit]'); 2912 } elseif ($this->_zoom_mode == 'fullwidth') { 2913 $this->_out('/OpenAction [3 0 R /FitH null]'); 2914 } elseif ($this->_zoom_mode == 'real') { 2915 $this->_out('/OpenAction [3 0 R /XYZ null null 1]'); 2916 } elseif (!is_string($this->_zoom_mode)) { 2917 $this->_out('/OpenAction [3 0 R /XYZ null null ' . ($this->_zoom_mode / 100) . ']'); 2918 } 2919 if ($this->_layout_mode == 'single') { 2920 $this->_out('/PageLayout /SinglePage'); 2921 } elseif ($this->_layout_mode == 'continuous') { 2922 $this->_out('/PageLayout /OneColumn'); 2923 } elseif ($this->_layout_mode == 'two') { 2924 $this->_out('/PageLayout /TwoColumnLeft'); 2925 } 2926 } 2927 2928 /** 2929 * Write the PDF trailer. 2930 * 2931 * @return void 2932 */ 2933 protected function _putTrailer() 2934 { 2935 $this->_out('/Size ' . ($this->_n + 1)); 2936 $this->_out('/Root ' . $this->_n . ' 0 R'); 2937 $this->_out('/Info ' . ($this->_n - 1) . ' 0 R'); 2938 } 2939 2940 /** 2941 * End the PDF document 2942 * 2943 * @return void 2944 */ 2945 protected function _endDoc() 2946 { 2947 $this->_putPages(); 2948 $this->_putResources(); 2949 // Info 2950 $this->_newobj(); 2951 $this->_out('<<'); 2952 $this->_putInfo(); 2953 $this->_out('>>'); 2954 $this->_out('endobj'); 2955 // Catalog 2956 $this->_newobj(); 2957 $this->_out('<<'); 2958 $this->_putCatalog(); 2959 $this->_out('>>'); 2960 $this->_out('endobj'); 2961 // Cross-ref 2962 $o = $this->_buflen + strlen($this->_buffer); 2963 $this->_out('xref'); 2964 $this->_out('0 ' . ($this->_n + 1)); 2965 $this->_out('0000000000 65535 f '); 2966 for ($i = 1; $i <= $this->_n; $i++) { 2967 $this->_out(sprintf('%010d 00000 n ', $this->_offsets[$i])); 2968 } 2969 // Trailer 2970 $this->_out('trailer'); 2971 $this->_out('<<'); 2972 $this->_putTrailer(); 2973 $this->_out('>>'); 2974 $this->_out('startxref'); 2975 $this->_out($o); 2976 $this->_out('%%EOF'); 2977 $this->_state = 3; 2978 } 2979 2980 /** 2981 * Begin a new page. 2982 * 2983 * @param string $orientation Orientation code 2984 * @return void 2985 */ 2986 protected function _beginPage($orientation) 2987 { 2988 $this->_page++; 2989 2990 // only assign page contents if it is new 2991 if (!isset($this->_pages[$this->_page])) { 2992 $this->_pages[$this->_page] = ''; 2993 } 2994 2995 $this->_state = 2; 2996 $this->x = $this->_left_margin; 2997 $this->y = $this->_top_margin; 2998 $this->_last_height = 0; 2999 // Page orientation 3000 if (!$orientation) { 3001 $orientation = $this->_default_orientation; 3002 } else { 3003 $orientation = Horde_String::upper($orientation[0]); 3004 if ($orientation != $this->_default_orientation) { 3005 $this->_orientation_changes[$this->_page] = true; 3006 } 3007 } 3008 if ($orientation != $this->_current_orientation) { 3009 // Change orientation 3010 if ($orientation == 'P') { 3011 $this->wPt = $this->fwPt; 3012 $this->hPt = $this->fhPt; 3013 $this->w = $this->fw; 3014 $this->h = $this->fh; 3015 } else { 3016 $this->wPt = $this->fhPt; 3017 $this->hPt = $this->fwPt; 3018 $this->w = $this->fh; 3019 $this->h = $this->fw; 3020 } 3021 $this->_page_break_trigger = $this->h - $this->_break_margin; 3022 $this->_current_orientation = $orientation; 3023 } 3024 } 3025 3026 /** 3027 * Set the end of page contents. 3028 * 3029 * @return void 3030 */ 3031 protected function _endPage() 3032 { 3033 $this->_state = 1; 3034 } 3035 3036 /** 3037 * Begin a new object. 3038 * 3039 * @return void 3040 */ 3041 protected function _newobj() 3042 { 3043 $this->_n++; 3044 $this->_offsets[$this->_n] = $this->_buflen + strlen($this->_buffer); 3045 $this->_out($this->_n . ' 0 obj'); 3046 } 3047 3048 /** 3049 * Underline a block of text. 3050 * 3051 * @param integer $x X-coordinate 3052 * @param integer $y Y-coordinate 3053 * @param string $text Text to underline 3054 * @return string Underlined string 3055 */ 3056 protected function _doUnderline($x, $y, $text) 3057 { 3058 // Set the rectangle width according to text width. 3059 $width = $this->getStringWidth($text, true); 3060 3061 /* Set rectangle position and height, using underline position and 3062 * thickness settings scaled by the font size. */ 3063 $y = $y + ($this->_current_font['up'] * $this->_font_size_pt / 1000); 3064 $height = -$this->_current_font['ut'] * $this->_font_size_pt / 1000; 3065 3066 return sprintf('%.2F %.2F %.2F %.2F re f', $x, $y, $width, $height); 3067 } 3068 3069 /** 3070 * Extract info from a JPEG file. 3071 * 3072 * @param string $file Filename of JPEG image 3073 * @return array Assoc. array of info 3074 */ 3075 protected function _parseJPG($file) 3076 { 3077 // Extract info from a JPEG file. 3078 $img = @getimagesize($file); 3079 if (!$img) { 3080 throw new Horde_Pdf_Exception(sprintf('Missing or incorrect image file: %s', $file)); 3081 } 3082 if ($img[2] != 2) { 3083 throw new Horde_Pdf_Exception(sprintf('Not a JPEG file: %s', $file)); 3084 } 3085 if (!isset($img['channels']) || $img['channels'] == 3) { 3086 $colspace = 'DeviceRGB'; 3087 } elseif ($img['channels'] == 4) { 3088 $colspace = 'DeviceCMYK'; 3089 } else { 3090 $colspace = 'DeviceGray'; 3091 } 3092 $bpc = isset($img['bits']) ? $img['bits'] : 8; 3093 3094 // Read whole file. 3095 $f = fopen($file, 'rb'); 3096 $data = fread($f, filesize($file)); 3097 fclose($f); 3098 3099 return array('w' => $img[0], 'h' => $img[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data); 3100 } 3101 3102 /** 3103 * Extract info from a PNG file. 3104 * 3105 * @param string $file Filename of PNG image 3106 * @return array Assoc. array of info 3107 */ 3108 protected function _parsePNG($file) 3109 { 3110 // Extract info from a PNG file. 3111 $f = fopen($file, 'rb'); 3112 if (!$f) { 3113 throw new Horde_Pdf_Exception(sprintf('Unable to open image file: %s', $file)); 3114 } 3115 3116 // Check signature. 3117 if (fread($f, 8) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { 3118 throw new Horde_Pdf_Exception(sprintf('Not a PNG file: %s', $file)); 3119 } 3120 3121 // Read header chunk. 3122 fread($f, 4); 3123 if (fread($f, 4) != 'IHDR') { 3124 throw new Horde_Pdf_Exception(sprintf('Incorrect PNG file: %s', $file)); 3125 } 3126 $width = $this->_freadInt($f); 3127 $height = $this->_freadInt($f); 3128 $bpc = ord(fread($f, 1)); 3129 if ($bpc > 8) { 3130 throw new Horde_Pdf_Exception(sprintf('16-bit depth not supported: %s', $file)); 3131 } 3132 $ct = ord(fread($f, 1)); 3133 if ($ct == 0) { 3134 $colspace = 'DeviceGray'; 3135 } elseif ($ct == 2) { 3136 $colspace = 'DeviceRGB'; 3137 } elseif ($ct == 3) { 3138 $colspace = 'Indexed'; 3139 } else { 3140 throw new Horde_Pdf_Exception(sprintf('Alpha channel not supported: %s', $file)); 3141 } 3142 if (ord(fread($f, 1)) != 0) { 3143 throw new Horde_Pdf_Exception(sprintf('Unknown compression method: %s', $file)); 3144 } 3145 if (ord(fread($f, 1)) != 0) { 3146 throw new Horde_Pdf_Exception(sprintf('Unknown filter method: %s', $file)); 3147 } 3148 if (ord(fread($f, 1)) != 0) { 3149 throw new Horde_Pdf_Exception(sprintf('Interlacing not supported: %s', $file)); 3150 } 3151 fread($f, 4); 3152 $parms = '/DecodeParms <</Predictor 15 /Colors ' . ($ct == 2 ? 3 : 1) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $width . '>>'; 3153 // Scan chunks looking for palette, transparency and image data. 3154 $pal = ''; 3155 $trns = ''; 3156 $data = ''; 3157 do { 3158 $n = $this->_freadInt($f); 3159 $type = fread($f, 4); 3160 if ($type == 'PLTE') { 3161 // Read palette 3162 $pal = fread($f, $n); 3163 fread($f, 4); 3164 } elseif ($type == 'tRNS') { 3165 // Read transparency info 3166 $t = fread($f, $n); 3167 if ($ct == 0) { 3168 $trns = array(ord(substr($t, 1, 1))); 3169 } elseif ($ct == 2) { 3170 $trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1))); 3171 } else { 3172 $pos = strpos($t, chr(0)); 3173 if (is_int($pos)) { 3174 $trns = array($pos); 3175 } 3176 } 3177 fread($f, 4); 3178 } elseif ($type == 'IDAT') { 3179 // Read image data block 3180 $data .= fread($f, $n); 3181 fread($f, 4); 3182 } elseif ($type == 'IEND') { 3183 break; 3184 } else { 3185 fread($f, $n + 4); 3186 } 3187 } while ($n); 3188 3189 if ($colspace == 'Indexed' && empty($pal)) { 3190 throw new Horde_Pdf_Exception(sprintf('Missing palette in: %s', $file)); 3191 } 3192 fclose($f); 3193 3194 return array('w' => $width, 'h' => $height, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data); 3195 } 3196 3197 /** 3198 * Read a 4-byte integer from stream. 3199 * 3200 * @param resource $f Stream resource 3201 * @return integer Byte 3202 */ 3203 protected function _freadInt($f) 3204 { 3205 $i = ord(fread($f, 1)) << 24; 3206 $i += ord(fread($f, 1)) << 16; 3207 $i += ord(fread($f, 1)) << 8; 3208 $i += ord(fread($f, 1)); 3209 return $i; 3210 } 3211 3212 /** 3213 * Format a text string by escaping and wrapping in parentheses. 3214 * 3215 * @param string $s String to format. 3216 * @param string Formatted string. 3217 * @return string 3218 */ 3219 protected function _textString($s) 3220 { 3221 return '(' . $this->_escape($s) . ')'; 3222 } 3223 3224 /** 3225 * Escape parentheses and forward slash. 3226 * 3227 * @param string $s String to escape. 3228 * @return string Escaped string. 3229 */ 3230 protected function _escape($s) 3231 { 3232 return str_replace(array('\\', ')', '('), 3233 array('\\\\', '\\)', '\\('), 3234 $s); 3235 } 3236 3237 /** 3238 * Add a line to the document wrapped in 'stream' and 'endstream'. 3239 * 3240 * @param string $s Line to add. 3241 * @return void 3242 */ 3243 protected function _putStream($s) 3244 { 3245 $this->_out('stream'); 3246 $this->_out($s); 3247 $this->_out('endstream'); 3248 } 3249 3250 /** 3251 * Add a line to the document. 3252 * 3253 * @param string $s Line to add. 3254 * @return void 3255 */ 3256 protected function _out($s) 3257 { 3258 if ($this->_state == 2) { 3259 $this->_pages[$this->_page] .= $s . "\n"; 3260 } else { 3261 $this->_buffer .= $s . "\n"; 3262 } 3263 } 3264 3265 /** 3266 * Convert hex-based color to RGB 3267 */ 3268 protected function _hexToRgb($hex) 3269 { 3270 if (substr($hex, 0, 1) == '#') { $hex = substr($hex, 1); } 3271 3272 if (strlen($hex) == 6) { 3273 list($r, $g, $b) = array(substr($hex, 0, 2), 3274 substr($hex, 2, 2), 3275 substr($hex, 4, 2)); 3276 } elseif (strlen($hex) == 3) { 3277 list($r, $g, $b) = array(substr($hex, 0, 1).substr($hex, 0, 1), 3278 substr($hex, 1, 1).substr($hex, 1, 1), 3279 substr($hex, 2, 1).substr($hex, 2, 1)); 3280 } 3281 $r = hexdec($r)/255; 3282 $g = hexdec($g)/255; 3283 $b = hexdec($b)/255; 3284 3285 return array($r, $g, $b); 3286 } 3287} 3288