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: