Overview

Packages

  • Pdf

Classes

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