3 php pdf generation library
4 Copyright (C) Potential Technologies 2002 - 2003
5 http://www.potentialtech.com
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 $Id: phppdflib.class.php,v 2.6 2003/08/29 21:15:21 wmoran Exp $
26 /* $objects is an array that stores the objects
27 * that will become the pdf when ->generate()
29 * The layout of ->objects does not directly
30 * mimic the pdf format, although it is similar.
31 * nextoid always holds the next available oid ( oid is short for object id )
33 var $objects, $nextoid;
35 /* xreftable is an array containing data to
36 * create the xref section (PDF calls it a referance table)
38 var $xreftable, $nextobj;
40 /* These arrays allow quick translation between
41 * pdflib OIDs and the final PDF OIDs (OID stands for object ids)
43 var $libtopdf, $pdftolib;
46 var $ermsg = array(), $erno = array();
48 var $builddata; // Various data required during the pdf build
49 var $nextpage; // Tracks the next page number
50 var $widths, $needsset; // Store the font width arrays here
51 var $default; // Default values for objects
52 var $x, $chart, $template; // extension class is instantiated here if requested
53 /* Constructor function: is automatically called when the
54 * object is created. Used to set up the environment
58 /* Per spec, obj 0 should always have a generation
59 * number of 65535 and is always free
61 $this->xreftable[0]["gennum"] = 65535;
62 $this->xreftable[0]["offset"] = 0;
63 $this->xreftable[0]["free"] = "f";
65 // Object #1 will always be the Document Catalog
66 $this->xreftable[1]["gennum"] = 0;
67 $this->xreftable[1]["free"] = "n";
69 // Object #2 will always be the root pagenode
70 $this->xreftable[2]["gennum"] = 0;
71 $this->xreftable[2]["free"] = "n";
72 $this->pdftolib[2] = 1;
73 $this->libtopdf[1] = 2;
75 // Object #3 is always the resource library
76 $this->xreftable[3]["gennum"] = 0;
77 $this->xreftable[3]["free"] = "n";
79 /* nextoid starts at 2 because all
80 * drawing functions return either the
81 * object ID or FALSE on error, so we can't
82 * return an OID of 0, because it equates
83 * to false and error checking would think
84 * the procedure failed
92 // Font width tables are not set unless they are needed
93 $this->needsset = true;
95 // Set all the default values
96 $t['pagesize'] = 'letter';
97 $t['font'] = 'Helvetica';
103 $t['strokecolor'] = $this->get_color('black');
104 $t['fillcolor'] = $this->get_color('black');
105 $t['margin-left'] = $t['margin-right'] = $t['margin-top'] = $t['margin-bottom'] =72;
106 $t['tmode'] = 0; // Text: fill
107 $t['smode'] = 1; // Shapes: stroke
111 /******************************************************
112 * These functions are the public ones, they are the *
113 * way that the user will actually enter the data *
114 * that will become the pdf *
115 ******************************************************/
117 function set_default($setting, $value)
121 $this->default['margin-left'] = $value;
122 $this->default['margin-right'] = $value;
123 $this->default['margin-top'] = $value;
124 $this->default['margin-bottom'] = $value;
128 $this->default['tmode'] = $this->default['smode'] = $value;
132 $this->default[$setting] = $value;
137 function draw_rectangle($top, $left, $bottom, $right, $parent, $attrib = array())
139 if ($this->objects[$parent]["type"] != "page") {
140 $this->_push_std_error(6001);
143 $o = $this->_addnewoid();
144 $attrib = $this->_resolve_param($attrib, false);
145 $this->_resolve_colors($n, $attrib);
146 $this->objects[$o] = $n;
147 $this->objects[$o]["width"] = $attrib["width"];
148 $this->objects[$o]["type"] = "rectangle";
149 $this->_adjust_margin($left, $top, $parent);
150 $this->_adjust_margin($right, $bottom, $parent);
151 $this->objects[$o]["top"] = $top;
152 $this->objects[$o]["left"] = $left;
153 $this->objects[$o]["bottom"] = $bottom;
154 $this->objects[$o]["right"] = $right;
155 $this->objects[$o]["parent"] = $parent;
156 $this->objects[$o]["mode"] = $this->_resolve_mode($attrib, 'smode');
160 function draw_circle($x, $y, $r, $parent, $attrib = array())
162 if ($this->objects[$parent]["type"] != "page") {
163 $this->_push_std_error(6001);
166 $o = $this->_addnewoid();
167 $attrib = $this->_resolve_param($attrib, false);
168 $this->_resolve_colors($n, $attrib);
169 $n['width'] = $attrib['width'];
170 $this->_adjust_margin($x, $y, $parent);
174 $n['type'] = 'circle';
175 $n['parent'] = $parent;
176 $n['mode'] = $this->_resolve_mode($attrib, 'smode');
177 $this->objects[$o] = $n;
181 function draw_line($x, $y, $parent, $attrib = array())
183 if ($this->objects[$parent]["type"] != "page") {
184 $this->_push_std_error(6001);
187 if (count($x) != count($y)) {
188 $this->_push_error(6002, "X & Y variables must have equal number of elements");
191 $o = $this->_addnewoid();
192 $attrib = $this->_resolve_param($attrib, false);
193 $this->_resolve_colors($n, $attrib);
194 $this->objects[$o] = $n;
195 @$this->objects[$o]["width"] = $attrib["width"];
196 $this->objects[$o]['mode'] = $this->_resolve_mode($attrib, 'smode');
197 $this->objects[$o]["type"] = "line";
198 foreach ($x as $key => $value) {
199 if (isset($x[$key]) && isset($y[$key])) {
200 $this->_adjust_margin($x[$key], $y[$key], $parent);
203 $this->objects[$o]["x"] = $x;
204 $this->objects[$o]["y"] = $y;
205 $this->objects[$o]["parent"] = $parent;
210 function draw_text($left, $bottom, $text, $parent, $attrib = array())
212 if ($this->objects[$parent]["type"] != "page") {
213 $this->_push_std_error(6001);
216 $attrib = $this->_resolve_param($attrib);
218 if (!($n["font"] = $this->_use_font($attrib))) {
219 // Couldn't find/add the font
220 $this->_push_error(6003, "Font was not found");
223 if (isset($attrib["rotation"])) {
224 $n["rotation"] = $attrib["rotation"];
226 $n['mode'] = $this->_resolve_mode($attrib, 'tmode');
227 if (isset($attrib["height"]) && $attrib["height"] > 0) {
228 $n["height"] = $attrib["height"];
230 $this->_resolve_colors($n, $attrib);
231 $n["type"] = "texts";
232 $this->_adjust_margin($left, $bottom, $parent);
234 $n["bottom"] = $bottom;
236 $n["parent"] = $parent;
238 $o = $this->_addnewoid();
239 $this->objects[$o] = $n;
243 function new_page($size = null)
245 if (is_null($size)) {
246 $size = $this->default['pagesize'];
250 $o = $this->_addnewoid();
251 $this->objects[$o]["height"] = 792;
252 $this->objects[$o]["width"] = 612;
256 $o = $this->_addnewoid();
257 $this->objects[$o]["height"] = 1008;
258 $this->objects[$o]["width"] = 612;
262 $o = $this->_addnewoid();
263 $this->objects[$o]["height"] = 720;
264 $this->objects[$o]["width"] = 540;
268 $o = $this->_addnewoid();
269 $this->objects[$o]["height"] = 1224;
270 $this->objects[$o]["width"] = 792;
274 $o = $this->_addnewoid();
275 $this->objects[$o]["height"] = 1188;
276 $this->objects[$o]["width"] = 842;
280 $o = $this->_addnewoid();
281 $this->objects[$o]["height"] = 842;
282 $this->objects[$o]["width"] = 595;
286 $o = $this->_addnewoid();
287 $this->objects[$o]["height"] = 598;
288 $this->objects[$o]["width"] = 418;
292 if (preg_match("/in/",$size)) {
293 $o = $this->_addnewoid();
294 $size = substr($size, 0, strlen($size) - 2);
295 $dims = split("x",$size);
296 $this->objects[$o]["height"] = ($dims[1] * 72);
297 $this->objects[$o]["width"] = ($dims[0] * 72);
299 if (preg_match("/cm/",$size)) {
300 $o = $this->_addnewoid();
301 $size = substr($size, 0, strlen($size) - 2);
302 $dims = split("x",$size);
303 $this->objects[$o]["height"] = ($dims[1] * 28.346);
304 $this->objects[$o]["width"] = ($dims[0] * 28.346);
306 $this->_push_error(6004, "Could not deciper page size description: $size");
312 $this->objects[$o]['type'] = 'page';
313 $this->objects[$o]['parent'] = 1;
314 $this->objects[$o]['number'] = $this->nextpage;
316 foreach (array('margin-left', 'margin-right', 'margin-top', 'margin-bottom') as $margin) {
317 $this->objects[$o][$margin] = $this->default[$margin];
322 function swap_pages($p1, $p2)
324 if ($this->objects[$p1]["type"] != "page" ||
325 $this->objects[$p2]["type"] != "page") {
326 $this->_push_std_error(6001);
329 $temp = $this->objects[$p1]["number"];
330 $this->objects[$p1]["number"] = $this->objects[$p2]["number"];
331 $this->objects[$p2]["number"] = $temp;
335 function move_page_before($page, $infrontof)
337 if ($this->objects[$page]["type"] != "page" ||
338 $this->objects[$infrontof]["type"] != "page") {
339 $this->_push_std_error(6001);
342 if ($page == $infrontof) {
343 $this->_push_error(6005, "You're trying to swap a page with itself");
346 $target = $this->objects[$infrontof]["number"];
347 $leaving = $this->objects[$page]["number"];
348 foreach ($this->objects as $id => $o) {
349 if ($o["type"] == "page") {
350 if ($target < $leaving) {
351 if ($o["number"] >= $target && $o["number"] < $leaving) {
352 $this->objects[$id]["number"]++;
355 if ($o["number"] < $target && $o["number"] > $leaving) {
356 $this->objects[$id]["number"]--;
361 if ($target < $leaving) {
362 $this->objects[$page]["number"] = $target;
364 $this->objects[$page]["number"] = $target - 1;
369 function new_font($identifier)
373 switch ($identifier) {
374 /* The "standard" Type 1 fonts
375 * These are "guaranteed" to be available
376 * to the viewer application and don't
381 case "Courier-Oblique":
382 case "Courier-BoldOblique":
384 case "Helvetica-Bold":
385 case "Helvetica-Oblique":
386 case "Helvetica-BoldOblique":
390 case "Times-BoldItalic":
393 $o = $this->_addnewoid();
394 $this->builddata["fonts"][$o] = $identifier;
395 $n["subtype"] = "Type1";
396 $n["basefont"] = $identifier;
400 if ($this->objects[$identifier]["type"] != "fontembed") {
401 $this->_push_error(6006, "Object must be of type 'fontembed'");
405 $this->_push_error(6007, "Feature not implemented yet");
409 $this->objects[$o] = $n;
413 function generate($clevel = 9)
415 // Validate the compression level
417 $this->builddata["compress"] = false;
420 $this->builddata["compress"] = $clevel;
422 $this->builddata["compress"] = 9;
425 /* Preprocess objects to see if they can
426 * be combined into a single stream
427 * We scan through each page, and create
428 * a multistream object out of all viable
431 $temparray = $this->objects;
432 foreach ($this->objects as $oid => $def) {
433 if ( $def["type"] == "page" ) {
437 while ( list ($liboid, $obj) = each($temparray) ) {
438 if (isset($obj["parent"]) && $obj["parent"] == $oid) {
439 switch ($obj["type"]) {
441 $temp["data"] .= $this->_make_text($liboid);
442 $this->objects[$liboid]["type"] = "null";
443 $this->objects[$liboid]["parent"] = -1;
447 $temp["data"] .= $this->_make_rect($liboid);
448 $this->objects[$liboid]["type"] = "null";
449 $this->objects[$liboid]["parent"] = -1;
453 $temp["data"] .= $this->_place_raw_image($liboid);
454 $this->objects[$liboid]["type"] = "null";
455 $this->objects[$liboid]["parent"] = -1;
459 $temp["data"] .= $this->_make_line($liboid);
460 $this->objects[$liboid]["type"] = "null";
461 $this->objects[$liboid]["parent"] = -1;
465 $temp["data"] .= $this->_make_circle($liboid);
466 $this->objects[$liboid]["type"] = "null";
467 $this->objects[$liboid]["parent"] = -1;
472 if (strlen($temp["data"]) > 0) {
473 // this line takes the next available oid
474 $o = $this->_addnewoid();
475 $temp["type"] = "mstream";
476 $temp["parent"] = $oid;
477 $this->objects[$o] = $temp;
483 // Generate a list of PDF object IDs to
484 // use and map them to phppdflib IDs
485 foreach ( $this->objects as $oid => $properties ) {
486 if ( $this->_becomes_object( $properties["type"] ) ) {
487 $o = $this->_addtoxreftable(0,0);
488 $this->libtopdf[$oid] = $o;
489 $this->pdftolib[$o] = $oid;
493 /* First characters represent the version
494 * of the PDF spec to conform to.
495 * The PDF spec recommends that the next
496 * four bytes be a comment containing four
497 * non-ASCII characters, to convince
498 * (for example) ftp programs that this is
501 $os = "%PDF-1.3%\xe2\xe3\xcf\xd3\x0a";
503 // Create the Document Catalog
504 $carray["Type"] = "/Catalog";
505 $carray["Pages"] = "2 0 R";
506 $temp = $this->_makedictionary($carray);
507 $temp = "1 0 obj" . $temp . "endobj\x0a";
508 $this->xreftable[1]["offset"] = strlen($os);
511 // Create the root page node
513 $kids = $this->_order_pages(2);
514 $this->xreftable[2]["offset"] = strlen($os);
515 $os .= "2 0 " . $this->_makepagenode($kids, "" ) . "\x0a";
517 /* Create a resource dictionary for the entire
518 * PDF file. This may not be the most efficient
519 * way to store it, but it makes the code simple.
520 * At some point, we should analyze performance
521 * and see if it's worth splitting the resource
526 if (isset($this->builddata["fonts"]) && count($this->builddata["fonts"]) > 0) {
527 foreach ($this->builddata["fonts"] as $id => $base) {
528 $ta["F$id"] = $this->libtopdf[$id] . " 0 R";
530 $temp["Font"] = $this->_makedictionary($ta);
532 reset($this->objects);
533 while (list($id, $obj) = each($this->objects)) {
534 if ($obj["type"] == "image") {
535 $xol["Img$id"] = $this->libtopdf[$id] . " 0 R";
538 if ( isset($xol) && count($xol) > 0 ) {
539 $temp["XObject"] = $this->_makedictionary($xol);
541 $this->xreftable[3]["offset"] = strlen($os);
544 $os .= $this->_makedictionary($temp);
548 $os .= " endobj\x0a";
550 // Go through and add the rest of the objects
551 foreach ( $this->pdftolib as $pdfoid => $liboid ) {
555 // Set the location of the start
556 $this->xreftable[$pdfoid]["offset"] = strlen($os);
557 switch ( $this->objects[$liboid]["type"] ) {
559 $kids = $this->_get_kids($pdfoid);
560 $os .= $pdfoid . " 0 ";
561 $os .= $this->_makepage($this->objects[$liboid]["parent"],
566 $os .= $pdfoid . " 0 obj";
567 $os .= $this->_streamify($this->_make_rect($liboid));
572 $os .= $pdfoid . " 0 obj";
573 $os .= $this->_streamify($this->_make_line($liboid));
578 $os .= $pdfoid . " 0 obj";
579 $os .= $this->_streamify($this->_make_circle($liboid));
584 $os .= $pdfoid . " 0 obj";
585 $temp = $this->_make_text($liboid);
586 $os .= $this->_streamify($temp) . " endobj";
590 $os .= $pdfoid . " 0 obj" .
591 $this->_streamify(trim($this->objects[$liboid]["data"])) .
596 $os .= $pdfoid . " 0 obj";
597 $os .= $this->_make_raw_image($liboid);
602 $os .= $pdfoid . " 0 obj";
603 $os .= $this->_streamify($this->_place_raw_image($liboid));
608 $os .= $pdfoid . " 0 obj";
610 $temp["Type"] = "/Font";
611 $temp["Subtype"] = "/" . $this->objects[$liboid]["subtype"];
612 $temp["BaseFont"] = "/" . $this->objects[$liboid]["basefont"];
613 $temp["Encoding"] = "/WinAnsiEncoding";
614 $temp["Name"] = "/F$liboid";
615 $os .= $this->_makedictionary($temp);
622 // Create an Info entry
623 $info = $this->_addtoxreftable(0,0);
624 $this->xreftable[$info]["offset"] = strlen($os);
627 $this->_stringify("phppdflib http://www.potentialtech.com/ppl.php");
628 $os .= $info . " 0 obj" . $this->_makedictionary($temp) . " endobj\x0a";
630 // Create the xref table
631 $this->builddata["startxref"] = strlen($os);
632 $os .= "xref\x0a0 " . (string)($this->nextobj + 1) . "\x0a";
633 for ( $i = 0; $i <= $this->nextobj; $i ++ ) {
634 $os .= sprintf("%010u %05u %s \x0a", $this->xreftable[$i]["offset"],
635 $this->xreftable[$i]["gennum"],
636 $this->xreftable[$i]["free"]);
639 // Create document trailer
640 $os .= "trailer\x0a";
642 $temp["Size"] = $this->nextobj + 1;
643 $temp["Root"] = "1 0 R";
644 $temp["Info"] = $info . " 0 R";
645 $os .= $this->_makedictionary($temp);
646 $os .= "\x0astartxref\x0a";
647 $os .= $this->builddata["startxref"] . "\x0a";
649 // Required end of file marker
655 function png_embed($data)
657 // Sanity, make sure this is a png
658 if (substr($data, 0, 8) != "\x89PNG\x0d\x0a\x1a\x0a") {
659 $this->_push_error(6011, 'brand not valid');
662 $data = substr($data, 12);
663 if (substr($data, 0, 4) != 'IHDR') {
664 $this->_push_error(6011, 'IHDR chunk missing');
667 $data = substr($data, 4);
668 $width = $this->_int_val(substr($data, 0, 4));
669 $height = $this->_int_val(substr($data, 4, 4));
670 $data = substr($data, 8);
671 $bpc = ord(substr($data, 0, 1));
672 $ct = ord(substr($data, 1, 1));
674 $this->_push_error(6014, '16 bit PNG unsupported');
678 case 0 : $cspace = '/DeviceGray'; break;
679 case 2 : $cspace = '/DeviceRGB'; break;
680 case 3 : $cspace = '/Indexed'; break;
682 $this->_push_error(6015, 'PNG with alpha not supported');
685 if (ord(substr($data, 2, 1)) != 0) {
686 $this->_push_error(6016, 'Unknown compression type');
689 if (ord(substr($data, 3, 1)) != 0) {
690 $this->_push_error(6017, 'Unknown PNG filter method');
693 if (ord(substr($data, 4, 1)) != 0) {
694 $this->_push_error(6018, 'PNG interlacing not supported');
697 $params['Predictor'] = '15';
698 $params['Colors'] = $ct == 2 ? 3 : 1;
699 $params['BitsPerComponent'] = $bpc;
700 $params['Columns'] = $width;
701 $additional['DecodeParms'] = $this->_makedictionary($params);
702 $data = substr($data, 9);
707 $n = $this->_int_val(substr($data, 0, 4));
708 $type = substr($data, 4, 4);
709 $data = substr($data, 8);
712 $pal = substr($data, 0, $n);
713 $data = substr($data, $n + 4);
717 $t = substr($data, 0, $n);
719 $trns = array(ord(substr($t, 1, 1)));
721 $trns = array(ord(substr($t, 1, 1)),
722 ord(substr($t, 3, 1)),
723 ord(substr($t, 5, 1)));
725 $pos = strpos($t, chr(0));
729 $data = substr($data, $n + 4);
733 $rawdata .= substr($data, 0, $n);
734 $data = substr($data, $n + 4);
741 $data = substr($data, $n + 4);
744 if ($cspace == '/Indexed') {
745 $this->_push_error(6011, 'Indexed without palette');
748 return $this->image_raw_embed($rawdata,
757 function jfif_embed($data)
759 /* Sanity check: Check magic numbers to see if
760 * this is really a JFIF stream
762 if ( substr($data, 0, 4) != "\xff\xd8\xff\xe0" ||
763 substr($data, 6, 4) != "JFIF" ) {
764 // This is not in JFIF format
765 $this->_push_std_error(6008);
769 /* Now we'll scan through all the markers in the
770 * JFIF and extract whatever data we need from them
771 * We're not being terribly anal about validating
772 * the structure of the JFIF, so a corrupt stream
773 * could have very unpredictable results
777 $size = strlen($data);
779 while ( $pos < $size ) {
780 $marker = substr($data, $pos + 1, 1);
781 // Just skip these markers
782 if ($marker == "\xd8" || $marker == "\xd9" || $marker == "\x01") {
786 if ($marker == "\xff") {
795 // Extended sequential
797 // Differential sequential
801 // differential progressive
805 // differential lossless
807 // Arithmetic encoded
814 $precision = $this->_int_val(substr($data, $pos + 4, 1));
815 $height = $this->_int_val(substr($data, $pos + 5, 2));
816 $width = $this->_int_val(substr($data, $pos + 7, 2));
817 $numcomp = $this->_int_val(substr($data, $pos + 9, 1));
818 if ( $numcomp != 3 && $numcomp != 1 ) {
819 // Abort if we aren't encoded as B&W or YCbCr
820 $this->_push_std_error(6008);
823 $pos += 2 + $this->_int_val(substr($data, $pos + 2, 2));
827 /* All marker identifications continue the
828 * loop, thus if we got here, we need to skip
829 * this marker as we don't understand it.
831 $pos += 2 + $this->_int_val(substr($data, $pos + 2, 2));
833 $cspace = $numcomp == 1 ? "/DeviceGray" : "/DeviceRGB";
834 return $this->image_raw_embed($data,
842 function image_raw_embed($data,
850 $o = $this->_addnewoid();
851 $t['additional'] = $addtl;
853 $t['colorspace'] = $cspace;
855 $t['type'] = "image";
856 $t['height'] = $height;
857 $t['width'] = $width;
858 $t['filter'] = $filter;
859 $this->objects[$o] = $t;
863 function get_image_size($id)
865 if ($this->objects[$id]['type'] != 'image') {
866 $this->_push_std_error(6009);
869 $r['width'] = $this->objects[$id]['width'];
870 $r['height'] = $this->objects[$id]['height'];
874 function image_place($oid, $bottom, $left, $parent, $param = array())
876 if ($this->objects[$oid]["type"] != "image") {
877 $this->_push_std_error(6009);
880 if ($this->objects[$parent]["type"] != "page") {
881 $this->_push_std_error(6001);
885 $o = $this->_addnewoid();
886 $param = $this->_resolve_param($param, false);
887 $t["type"] = "iplace";
888 $this->_adjust_margin($left, $bottom, $parent);
889 $t["bottom"] = $bottom;
891 $t["parent"] = $parent;
892 // find out what the image size should be
893 $width = $this->objects[$oid]["width"];
894 $height = $this->objects[$oid]["height"];
895 $scale = $param['scale'];
896 if (is_array($scale)) {
897 $t["xscale"] = $scale["x"] * $width;
898 $t["yscale"] = $scale["y"] * $height;
900 $t["xscale"] = $scale * $width;
901 $t["yscale"] = $scale * $height;
903 $t["rotation"] = $param['rotation'];
905 $this->objects[$o] = $t;
909 function strlen($string , $params = false, $tabwidth = 4)
911 if ($this->needsset) {
912 require_once(dirname(__FILE__) . '/strlen.inc.php');
914 if (empty($params["font"])) {
915 $font = $this->default['font'];
917 $font = $params["font"];
922 case "Helvetica-Oblique" :
925 case "Helvetica-BoldOblique" :
926 $font = "Helvetica-Bold";
928 case "ZapfDingbats" :
933 if ($params["height"] == 0) {
934 $size = $this->default['height'];
936 $size = $params["height"];
939 for ($i = 0; $i < $tabwidth; $i++) {
942 $string = str_replace(chr(9), $tab, $string);
943 if (substr($font, 0, 7) == "Courier") {
944 // Courier is a fixed-width font
945 $width = strlen($string) * 600;
948 $len = strlen($string);
949 for ($i = 0; $i < $len; $i++) {
950 $width += $this->widths[$font][ord($string{$i})];
953 // We now have the string width in font units
954 return $width * $size / 1000;
957 function wrap_line(&$text, $width, $param = array())
959 $maxchars = (int)(1.1 * $width / $this->strlen("i", $param));
960 $words = explode(" ", substr($text, 0, $maxchars));
961 if ($this->strlen($words[0]) >= $width) {
962 $this->_push_error(3001, "Single token too long for allowed space");
965 $space = $this->strlen(" ", $param);
969 while ($len < $width) {
970 if ($word >= count($words)) {
973 $temp = $this->strlen($words[$word], $param);
974 if ( ($len + $temp) <= $width) {
975 $final .= $words[$word] . " ";
978 $len += $space + $temp;
981 $text = substr($text, strlen($final));
985 function word_wrap($words, $width, $param = array())
987 // break $words into an array separated by manual paragraph break character
988 $paragraph = explode("\n", $words);
989 // find the width of 1 space in this font
990 $swidth = $this->strlen( " " , $param );
991 // uses each element of $paragraph array and splits it at spaces
992 for ($lc = 0; $lc < count($paragraph); $lc++){
993 while (strlen($paragraph[$lc]) > 0) {
994 $returnarray[] = $this->wrap_line($paragraph[$lc], $width, $param);
1000 function draw_one_paragraph($top, $left, $bottom, $right, $text, $page, $param = array())
1002 $param = $this->_resolve_param($param);
1003 $height = 1.1 * $param['height'];
1004 $width = $right - $left;
1005 while ($top > $bottom) {
1006 if (strlen($text) < 1) {
1010 if ($top >= $bottom) {
1011 $line = $this->wrap_line($text, $width, $param);
1012 switch ($param['align']) {
1014 $line = trim($line);
1015 $l = $right - $this->strlen($line, $param);
1019 $line = trim($line);
1020 $l = $left + (($width - $this->strlen($line, $param)) / 2);
1026 $this->draw_text($l, $top, $line, $page, $param);
1032 if (strlen($text) > 0) {
1039 function draw_paragraph($top, $left, $bottom, $right, $text, $page, $param = array())
1041 $paras = split("\n", $text);
1042 for ($i = 0; $i < count($paras); $i++) {
1043 $over = $this->draw_one_paragraph($top,
1050 if (is_string($over)) {
1056 if ($i < count($paras)) {
1057 for ($x = $i + 1; $x < count($paras); $x++) {
1058 $rv .= "\n" . $paras[$x];
1064 function error_array()
1067 while (count($this->ermsg) > 0) {
1068 $this->pop_error($num, $msg);
1069 $rv[] = "Error $num: $msg";
1074 function pop_error(&$num, &$msg)
1076 $num = array_pop($this->erno);
1077 $msg = array_pop($this->ermsg);
1078 if (is_null($num)) {
1085 function enable($name)
1087 $name = strtolower($name);
1088 @include_once(dirname(__FILE__) . "/${name}.class.php");
1089 $this->x[$name] = new $name;
1090 $this->x[$name]->pdf = &$this;
1094 $this->$name = &$this->x[$name];
1099 function get_color($desc)
1103 switch (strtolower($desc)) {
1105 $r['red'] = $r['blue'] = $r['green'] = 0;
1109 $r['red'] = $r['blue'] = $r['green'] = 1;
1114 $r['blue'] = $r['green'] = 0;
1119 $r['red'] = $r['green'] = 0;
1124 $r['blue'] = $r['red'] = 0;
1128 if (substr($desc, 0, 1) == '#') {
1129 // Parse out a hex triplet
1130 $v = substr($desc, 1, 2);
1131 $r['red'] = eval("return ord(\"\\x$v\");") / 255;
1132 $v = substr($desc, 3, 2);
1133 $r['green'] = eval("return ord(\"\\x$v\");") / 255;
1134 $v = substr($desc, 5, 2);
1135 $r['blue'] = eval("return ord(\"\\x$v\");") / 255;
1138 $this->_push_error(6012, "Unparsable color identifier: $desc");
1145 /******************************************************
1146 * These functions are internally used by the library *
1147 * and shouldn't really be called by a user of *
1149 ******************************************************/
1151 function _resolve_mode($attrib, $mode)
1153 $rmode = $attrib[$mode];
1174 function _adjust_margin(&$x, &$y, $page)
1176 $x += $this->objects[$page]['margin-left'];
1177 $y += $this->objects[$page]['margin-bottom'];
1180 function _resolve_param($param, $text = true)
1182 $rv = $this->default;
1183 if (is_array($param)) {
1184 if (isset($param['mode'])) {
1185 $param['tmode'] = $param['smode'] = $param['mode'];
1187 foreach ($param as $key => $value) {
1194 function _push_error($num, $msg)
1196 array_push($this->erno, $num);
1197 array_push($this->ermsg, $msg);
1200 function _push_std_error($num)
1203 case 6001 : $m = "Object must be of type 'page'"; break;
1204 case 6008 : $m = "Data stream not recognized as JFIF"; break;
1205 case 6009 : $m = "Object must be of type 'image'"; break;
1206 case 6011 : $m = "Data stream not recognized as PNG"; break;
1207 default : $m = "_push_std_error() called with invalid error number: $num"; break;
1209 $this->_push_error($num, $m);
1212 function _resolve_colors(&$n, $attrib)
1214 $temp = array('red','green','blue');
1215 foreach ($temp as $colcomp) {
1216 if (isset($attrib['fillcolor'][$colcomp])) {
1217 $n['fillcolor'][$colcomp] = $attrib['fillcolor'][$colcomp];
1219 if (isset($attrib['strokecolor'][$colcomp])) {
1220 $n['strokecolor'][$colcomp] = $attrib['strokecolor'][$colcomp];
1225 /* Check to see if a requested font is already in the
1226 * list, if not add it. Either way, return the libid
1229 function _use_font($id)
1231 if (!isset($id['font'])) {
1232 $id['font'] = $this->default['font'];
1234 if ( isset($this->builddata["fonts"]) && count($this->builddata["fonts"]) > 0 ) {
1235 foreach ($this->builddata["fonts"] as $libid => $name) {
1236 if ($name == $id['font']) {
1241 /* The font isn't in the table, so we add it
1242 * and return it's ID
1244 return $this->new_font($id['font']);
1247 /* Convert a big-endian byte stream into an integer */
1248 function _int_val($string)
1251 for ($i = 0; $i < strlen($string); $i ++ ) {
1252 $r += ord($string{$i}) * pow(256, strlen($string) - $i -1);
1257 function _make_raw_image($liboid)
1259 if (is_array($this->objects[$liboid]['additional']))
1260 $s = $this->objects[$liboid]['additional'];
1261 $s["Type"] = "/XObject";
1262 $s["Subtype"] = "/Image";
1263 $s["Width"] = $this->objects[$liboid]["width"];
1264 $s["Height"] = $this->objects[$liboid]["height"];
1265 $s["ColorSpace"] = $this->objects[$liboid]["colorspace"];
1266 $s["BitsPerComponent"] = $this->objects[$liboid]["bpc"];
1267 if (strlen($this->objects[$liboid]["filter"]) > 0) {
1268 $s["Filter"] = $this->objects[$liboid]["filter"];
1270 return $this->_streamify($this->objects[$liboid]["data"], $s);
1273 function _place_raw_image($liboid)
1275 $xscale = $this->objects[$liboid]["xscale"];
1276 $yscale = $this->objects[$liboid]["yscale"];
1277 $angle = $this->objects[$liboid]["rotation"];
1278 $temp = "q 1 0 0 1 " .
1279 $this->objects[$liboid]["left"] . " " .
1280 $this->objects[$liboid]["bottom"] . " cm ";
1282 $temp .= $this->_rotate($angle) . " cm ";
1284 if ($xscale != 1 || $yscale != 1) {
1285 $temp .= "$xscale 0 0 $yscale 0 0 cm ";
1287 $temp .= "/Img" . $this->objects[$liboid]["image"] .
1292 function _rotate($angle)
1294 $a = deg2rad($angle);
1297 $r = sprintf("%1\$1.6f %2\$1.6f %3\$1.6f %1\$1.6f 0 0", $cos, $sin, -$sin);
1301 function _get_operator($liboid)
1303 switch ($this->objects[$liboid]['mode']) {
1304 case 0 : return "f"; break;
1305 case 1 : return "S"; break;
1306 case 2 : return "b"; break;
1310 function _make_line($liboid)
1313 if ( $colortest = $this->_colorset($liboid) ) {
1314 $gstate .= $colortest . " ";
1316 if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) {
1317 $gstate .= $this->objects[$liboid]["width"] . " w ";
1321 foreach ($this->objects[$liboid]["x"] as $pointid => $x) {
1322 $y = $this->objects[$liboid]["y"][$pointid];
1323 $temp .= $x . " " . $y . " ";
1326 $firstpoint = false;
1331 $temp .= $this->_get_operator($liboid);
1332 if ( strlen($gstate) > 0 ) {
1333 $temp = "q " . $gstate . $temp . " Q";
1335 return $temp . "\x0a";
1338 function _make_rect($liboid)
1341 if ( $colortest = $this->_colorset($liboid) ) {
1342 $gstate .= $colortest . " ";
1344 if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) {
1345 $gstate .= $this->objects[$liboid]["width"] . " w ";
1347 $temp = $this->objects[$liboid]["left"] . " ";
1348 $temp .= $this->objects[$liboid]["bottom"];
1349 $temp .= " " . ( $this->objects[$liboid]["right"]
1350 - $this->objects[$liboid]["left"] );
1351 $temp .= " " . ( $this->objects[$liboid]["top"]
1352 - $this->objects[$liboid]["bottom"] );
1354 $temp .= $this->_get_operator($liboid);
1355 if ( strlen($gstate) > 0 ) {
1356 $temp = "q " . $gstate . $temp . " Q";
1358 return $temp . "\x0a";
1361 function _make_circle($liboid)
1364 if ( $colortest = $this->_colorset($liboid) ) {
1365 $gstate .= $colortest . " ";
1367 if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) {
1368 $gstate .= $this->objects[$liboid]["width"] . " w ";
1370 $r = $this->objects[$liboid]['radius'];
1371 $x = $this->objects[$liboid]['x'];
1372 $y = $this->objects[$liboid]['y'];
1374 $pt = $y + $r * 1.33333;
1376 $pb = $y - $r * 1.33333;
1377 $temp = "$ql $y m ";
1378 $temp .= "$ql $pt $qr $pt $qr $y c ";
1379 $temp .= "$qr $pb $ql $pb $ql $y c ";
1380 $temp .= $this->_get_operator($liboid);
1381 if ( strlen($gstate) > 0 ) {
1382 $temp = "q " . $gstate . $temp . " Q";
1384 return $temp . "\x0a";
1387 function _make_text($liboid)
1389 $statechange = ""; $locateinbt = true;
1390 $statechange = $this->_colorset($liboid);
1391 if (isset($this->objects[$liboid]["rotation"]) && $this->objects[$liboid]["rotation"] != 0) {
1392 $statechange .= "1 0 0 1 " .
1393 $this->objects[$liboid]["left"] . " " .
1394 $this->objects[$liboid]["bottom"] . " cm " .
1395 $this->_rotate($this->objects[$liboid]["rotation"]) .
1397 $locateinbt = false;
1400 if ($this->objects[$liboid]["mode"] != 0) {
1401 $temp .= $this->objects[$liboid]["mode"] .
1403 // Adjust stroke width
1404 $statechange .= $this->objects[$liboid]["height"] / 35 . " w ";
1406 $temp .= "/F" . $this->objects[$liboid]["font"] . " ";
1407 $temp .= $this->objects[$liboid]["height"];
1410 $temp .= $this->objects[$liboid]["left"] . " " .
1411 $this->objects[$liboid]["bottom"];
1416 $temp .= $this->_stringify($this->objects[$liboid]["text"]);
1419 if (strlen($statechange) > 0) {
1420 $temp = "q " . $statechange . $temp . " Q";
1422 return $temp . "\x0a";
1425 function _colorset($libid)
1427 $red = isset($this->objects[$libid]['fillcolor']["red"]) ? (float)$this->objects[$libid]['fillcolor']["red"] : 0;
1428 $green = isset($this->objects[$libid]['fillcolor']["green"]) ? (float)$this->objects[$libid]['fillcolor']["green"] : 0;
1429 $blue = isset($this->objects[$libid]['fillcolor']["blue"]) ? (float)$this->objects[$libid]['fillcolor']["blue"] : 0;
1430 if (($red > 0) || ($green > 0) || ($blue > 0)) {
1431 $r = $red . " " . $green . " " . $blue;
1436 $red = isset($this->objects[$libid]['strokecolor']["red"]) ? (float)$this->objects[$libid]['strokecolor']["red"] : 0;
1437 $green = isset($this->objects[$libid]['strokecolor']["green"]) ? (float)$this->objects[$libid]['strokecolor']["green"] : 0;
1438 $blue = isset($this->objects[$libid]['strokecolor']["blue"]) ? (float)$this->objects[$libid]['strokecolor']["blue"] : 0;
1439 if (($red > 0) || ($green > 0) || ($blue > 0) ) {
1440 $r .= $red . " " . $green . " " . $blue;
1446 /* Used to determine what pdflib objects need converted
1447 * to actual PDF objects.
1449 function _becomes_object($object)
1451 if ($object == "null") {
1457 /* builds an array of child objects */
1458 function _get_kids($pdfid)
1460 $libid = $this->pdftolib[$pdfid];
1461 foreach( $this->objects as $obid => $object ) {
1462 if (isset($object["parent"]) && $object["parent"] == $libid) {
1463 $kids[] = $this->libtopdf[$obid] . " 0 R";
1469 /* builds an array of pages, in order */
1470 function _order_pages($pdfid)
1472 $libid = $this->pdftolib[$pdfid];
1473 foreach( $this->objects as $obid => $object ) {
1474 if (isset($object["parent"]) && $object["parent"] == $libid) {
1475 $kids[$object["number"]] = $this->libtopdf[$obid] . " 0 R";
1482 /* simple helper function to return the current oid
1483 * and increment it by one
1485 function _addnewoid()
1487 $o = $this->nextoid;
1492 /* The xreftable will contain a list of all the
1493 * objects in the pdf file and the number of bytes
1494 * from the beginning of the file that the object
1495 * occurs. Each time we add an object, we call this
1496 * to record it's location, then call ->_genxreftable()
1497 * to generate the table from array
1499 function _addtoxreftable($offset, $gennum)
1502 $this->xreftable[$this->nextobj]["offset"] = $offset;
1503 $this->xreftable[$this->nextobj]["gennum"] = $gennum;
1504 $this->xreftable[$this->nextobj]["free"] = "n";
1505 return $this->nextobj;
1508 /* Returns a properly formatted pdf dictionary
1509 * containing entries specified by
1510 * the array $entries
1512 function _makedictionary($entries)
1515 if (isset($entries) && count($entries) > 0) {
1516 foreach ($entries as $key => $value) {
1517 $rs .= "/" . $key . " " . $value . "\x0a";
1524 /* returns a properly formatted pdf array */
1525 function _makearray($entries)
1528 if ( is_array($entries) ) {
1529 foreach ($entries as $entry) {
1530 $rs .= $entry . " ";
1535 $rs = rtrim($rs) . "]";
1539 /* Returns a properly formatted string, with any
1540 * special characters escaped
1542 function _stringify($string)
1544 // Escape potentially problematic characters
1545 $string = preg_replace("-\\\\-","\\\\\\\\",$string);
1546 $bad = array ("-\(-", "-\)-" );
1547 $good = array ("\\(", "\\)" );
1548 $string = preg_replace($bad,$good,$string);
1549 return "(" . rtrim($string) . ")";
1552 function _streamify($data, $sarray = array())
1554 /* zlib compression is a compile time option
1555 * for php, thus we need to make sure it's
1556 * available before using it.
1559 if (function_exists('gzcompress') && $this->builddata['compress']) {
1560 if ( !isset($sarray['Filter']) || strlen($sarray['Filter']) == 0 ) {
1561 $sarray['Filter'] = '/FlateDecode';
1563 if ($sarray['Filter'] != '/FlateDecode')
1564 $sarray['Filter'] = '[/FlateDecode ' . $sarray['Filter'] . ']';
1568 if ($go) $data = gzcompress($data, $this->builddata['compress']);
1570 $sarray['Length'] = strlen($data);
1571 $os = $this->_makedictionary($sarray);
1572 $os .= "stream\x0a" . $data . "\x0aendstream";
1576 /* Returns a properly formatted page node
1577 * page nodes with 0 kids are not created
1579 function _makepagenode($kids, $addtlopts = false)
1581 $parray["Type"] = "/Pages";
1582 if ( isset($kids) AND count($kids) > 0 ) {
1583 // Array of child objects
1584 $parray["Kids"] = $this->_makearray($kids);
1586 $parray["Count"] = count($kids);
1588 // No kids is an error condition
1589 $this->_push_error(600, "Pagenode has no children");
1592 if ( is_array($addtlopts) ) {
1593 foreach ( $addtlopts as $key => $value ) {
1594 $parray[$key] = $value;
1598 /* The resource dictionary is always object 3
1600 $parray["Resources"] = "3 0 R";
1602 $os = $this->_makedictionary($parray);
1603 $os = "obj" . $os . "endobj";
1607 function _makepage($parent, $contents, $liboid)
1609 $parray["Type"] = "/Page";
1610 $parray["Parent"] = $this->libtopdf[$parent] . " 0 R";
1611 $parray["Contents"] = $this->_makearray($contents);
1612 $parray["MediaBox"] = "[0 0 "
1613 . $this->objects[$liboid]["width"] . " "
1614 . $this->objects[$liboid]["height"] . "]";
1615 $os = $this->_makedictionary($parray);
1616 $os = "obj" . $os . "endobj";