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.5 2003/07/05 21:33:07 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) != "\137PNG\x0d\x0a\x1a\x0d") {
659 $this->_push_std_error(6011);
665 function jfif_embed($data)
667 /* Sanity check: Check magic numbers to see if
668 * this is really a JFIF stream
670 if ( substr($data, 0, 4) != "\xff\xd8\xff\xe0" ||
671 substr($data, 6, 4) != "JFIF" ) {
672 // This is not in JFIF format
673 $this->_push_std_error(6008);
677 /* Now we'll scan through all the markers in the
678 * JFIF and extract whatever data we need from them
679 * We're not being terribly anal about validating
680 * the structure of the JFIF, so a corrupt stream
681 * could have very unpredictable results
685 $size = strlen($data);
687 while ( $pos < $size ) {
688 $marker = substr($data, $pos + 1, 1);
689 // Just skip these markers
690 if ($marker == "\xd8" || $marker == "\xd9" || $marker == "\x01") {
694 if ($marker == "\xff") {
703 // Extended sequential
705 // Differential sequential
709 // differential progressive
713 // differential lossless
715 // Arithmetic encoded
722 $precision = $this->_int_val(substr($data, $pos + 4, 1));
723 $height = $this->_int_val(substr($data, $pos + 5, 2));
724 $width = $this->_int_val(substr($data, $pos + 7, 2));
725 $numcomp = $this->_int_val(substr($data, $pos + 9, 1));
726 if ( $numcomp != 3 && $numcomp != 1 ) {
727 // Abort if we aren't encoded as B&W or YCbCr
728 $this->_push_std_error(6008);
731 $pos += 2 + $this->_int_val(substr($data, $pos + 2, 2));
735 /* All marker identifications continue the
736 * loop, thus if we got here, we need to skip
737 * this marker as we don't understand it.
739 $pos += 2 + $this->_int_val(substr($data, $pos + 2, 2));
741 $cspace = $numcomp == 1 ? "/DeviceGray" : "/DeviceRGB";
742 return $this->image_raw_embed($data,
750 function image_raw_embed($data, $cspace, $bpc, $height, $width, $filter = "")
752 $o = $this->_addnewoid();
754 $t["colorspace"] = $cspace;
756 $t["type"] = "image";
757 $t["height"] = $height;
758 $t["width"] = $width;
759 $t["filter"] = $filter;
760 $this->objects[$o] = $t;
764 function get_image_size($id)
766 if ($this->objects[$id]['type'] != 'image') {
767 $this->_push_std_error(6009);
770 $r['width'] = $this->objects[$id]['width'];
771 $r['height'] = $this->objects[$id]['height'];
775 function image_place($oid, $bottom, $left, $parent, $param = array())
777 if ($this->objects[$oid]["type"] != "image") {
778 $this->_push_std_error(6009);
781 if ($this->objects[$parent]["type"] != "page") {
782 $this->_push_std_error(6001);
786 $o = $this->_addnewoid();
787 $param = $this->_resolve_param($param, false);
788 $t["type"] = "iplace";
789 $this->_adjust_margin($left, $bottom, $parent);
790 $t["bottom"] = $bottom;
792 $t["parent"] = $parent;
793 // find out what the image size should be
794 $width = $this->objects[$oid]["width"];
795 $height = $this->objects[$oid]["height"];
796 $scale = $param['scale'];
797 if (is_array($scale)) {
798 $t["xscale"] = $scale["x"] * $width;
799 $t["yscale"] = $scale["y"] * $height;
801 $t["xscale"] = $scale * $width;
802 $t["yscale"] = $scale * $height;
804 $t["rotation"] = $param['rotation'];
806 $this->objects[$o] = $t;
810 function strlen($string , $params = false, $tabwidth = 4)
812 if ($this->needsset) {
813 require_once(dirname(__FILE__) . '/strlen.inc.php');
815 if (empty($params["font"])) {
816 $font = $this->default['font'];
818 $font = $params["font"];
823 case "Helvetica-Oblique" :
826 case "Helvetica-BoldOblique" :
827 $font = "Helvetica-Bold";
829 case "ZapfDingbats" :
834 if ($params["height"] == 0) {
835 $size = $this->default['height'];
837 $size = $params["height"];
840 for ($i = 0; $i < $tabwidth; $i++) {
843 $string = str_replace(chr(9), $tab, $string);
844 if (substr($font, 0, 7) == "Courier") {
845 // Courier is a fixed-width font
846 $width = strlen($string) * 600;
849 $len = strlen($string);
850 for ($i = 0; $i < $len; $i++) {
851 $width += $this->widths[$font][ord($string{$i})];
854 // We now have the string width in font units
855 return $width * $size / 1000;
858 function wrap_line(&$text, $width, $param = array())
860 $maxchars = (int)(1.1 * $width / $this->strlen("i", $param));
861 $words = explode(" ", substr($text, 0, $maxchars));
862 if ($this->strlen($words[0]) >= $width) {
863 $this->_push_error(3001, "Single token too long for allowed space");
866 $space = $this->strlen(" ", $param);
870 while ($len < $width) {
871 if ($word >= count($words)) {
874 $temp = $this->strlen($words[$word], $param);
875 if ( ($len + $temp) <= $width) {
876 $final .= $words[$word] . " ";
879 $len += $space + $temp;
882 $text = substr($text, strlen($final));
886 function word_wrap($words, $width, $param = array())
888 // break $words into an array separated by manual paragraph break character
889 $paragraph = explode("\n", $words);
890 // find the width of 1 space in this font
891 $swidth = $this->strlen( " " , $param );
892 // uses each element of $paragraph array and splits it at spaces
893 for ($lc = 0; $lc < count($paragraph); $lc++){
894 while (strlen($paragraph[$lc]) > 0) {
895 $returnarray[] = $this->wrap_line($paragraph[$lc], $width, $param);
901 function draw_one_paragraph($top, $left, $bottom, $right, $text, $page, $param = array())
903 $param = $this->_resolve_param($param);
904 $height = 1.1 * $param['height'];
905 $width = $right - $left;
906 while ($top > $bottom) {
907 if (strlen($text) < 1) {
911 if ($top >= $bottom) {
912 $line = $this->wrap_line($text, $width, $param);
913 switch ($param['align']) {
916 $l = $right - $this->strlen($line, $param);
921 $l = $left + (($width - $this->strlen($line, $param)) / 2);
927 $this->draw_text($l, $top, $line, $page, $param);
933 if (strlen($text) > 0) {
940 function draw_paragraph($top, $left, $bottom, $right, $text, $page, $param = array())
942 $paras = split("\n", $text);
943 for ($i = 0; $i < count($paras); $i++) {
944 $over = $this->draw_one_paragraph($top,
951 if (is_string($over)) {
957 if ($i < count($paras)) {
958 for ($x = $i + 1; $x < count($paras); $x++) {
959 $rv .= "\n" . $paras[$x];
965 function error_array()
968 while (count($this->ermsg) > 0) {
969 $this->pop_error($num, $msg);
970 $rv[] = "Error $num: $msg";
975 function pop_error(&$num, &$msg)
977 $num = array_pop($this->erno);
978 $msg = array_pop($this->ermsg);
986 function enable($name)
988 $name = strtolower($name);
989 @include_once(dirname(__FILE__) . "/${name}.class.php");
990 $this->x[$name] = new $name;
991 $this->x[$name]->pdf = &$this;
995 $this->$name = &$this->x[$name];
1000 function get_color($desc)
1004 switch (strtolower($desc)) {
1006 $r['red'] = $r['blue'] = $r['green'] = 0;
1010 $r['red'] = $r['blue'] = $r['green'] = 1;
1015 $r['blue'] = $r['green'] = 0;
1020 $r['red'] = $r['green'] = 0;
1025 $r['blue'] = $r['red'] = 0;
1029 if (substr($desc, 0, 1) == '#') {
1030 // Parse out a hex triplet
1031 $v = substr($desc, 1, 2);
1032 $r['red'] = eval("return ord(\"\\x$v\");") / 255;
1033 $v = substr($desc, 3, 2);
1034 $r['green'] = eval("return ord(\"\\x$v\");") / 255;
1035 $v = substr($desc, 5, 2);
1036 $r['blue'] = eval("return ord(\"\\x$v\");") / 255;
1039 $this->_push_error(6012, "Unparsable color identifier: $desc");
1046 /******************************************************
1047 * These functions are internally used by the library *
1048 * and shouldn't really be called by a user of *
1050 ******************************************************/
1052 function _resolve_mode($attrib, $mode)
1054 $rmode = $attrib[$mode];
1075 function _adjust_margin(&$x, &$y, $page)
1077 $x += $this->objects[$page]['margin-left'];
1078 $y += $this->objects[$page]['margin-bottom'];
1081 function _resolve_param($param, $text = true)
1083 $rv = $this->default;
1084 if (is_array($param)) {
1085 if (isset($param['mode'])) {
1086 $param['tmode'] = $param['smode'] = $param['mode'];
1088 foreach ($param as $key => $value) {
1095 function _push_error($num, $msg)
1097 array_push($this->erno, $num);
1098 array_push($this->ermsg, $msg);
1101 function _push_std_error($num)
1104 case 6001 : $m = "Object must be of type 'page'"; break;
1105 case 6008 : $m = "Data stream not recognized as JFIF"; break;
1106 case 6009 : $m = "Object must be of type 'image'"; break;
1107 case 6011 : $m = "Data stream not recognized as PNG"; break;
1108 default : $m = "_push_std_error() called with invalid error number: $num"; break;
1110 $this->_push_error($num, $m);
1113 function _resolve_colors(&$n, $attrib)
1115 $temp = array('red','green','blue');
1116 foreach ($temp as $colcomp) {
1117 if (isset($attrib['fillcolor'][$colcomp])) {
1118 $n['fillcolor'][$colcomp] = $attrib['fillcolor'][$colcomp];
1120 if (isset($attrib['strokecolor'][$colcomp])) {
1121 $n['strokecolor'][$colcomp] = $attrib['strokecolor'][$colcomp];
1126 /* Check to see if a requested font is already in the
1127 * list, if not add it. Either way, return the libid
1130 function _use_font($id)
1132 if (!isset($id['font'])) {
1133 $id['font'] = $this->default['font'];
1135 if ( isset($this->builddata["fonts"]) && count($this->builddata["fonts"]) > 0 ) {
1136 foreach ($this->builddata["fonts"] as $libid => $name) {
1137 if ($name == $id['font']) {
1142 /* The font isn't in the table, so we add it
1143 * and return it's ID
1145 return $this->new_font($id['font']);
1148 /* Convert a big-endian byte stream into an integer */
1149 function _int_val($string)
1152 for ($i = 0; $i < strlen($string); $i ++ ) {
1153 $r += ord($string{$i}) * pow(256, strlen($string) - $i -1);
1158 function _make_raw_image($liboid)
1160 $s["Type"] = "/XObject";
1161 $s["Subtype"] = "/Image";
1162 $s["Width"] = $this->objects[$liboid]["width"];
1163 $s["Height"] = $this->objects[$liboid]["height"];
1164 $s["ColorSpace"] = $this->objects[$liboid]["colorspace"];
1165 $s["BitsPerComponent"] = $this->objects[$liboid]["bpc"];
1166 if (strlen($this->objects[$liboid]["filter"]) > 0) {
1167 $s["Filter"] = $this->objects[$liboid]["filter"];
1169 return $this->_streamify($this->objects[$liboid]["data"], $s);
1172 function _place_raw_image($liboid)
1174 $xscale = $this->objects[$liboid]["xscale"];
1175 $yscale = $this->objects[$liboid]["yscale"];
1176 $angle = $this->objects[$liboid]["rotation"];
1177 $temp = "q 1 0 0 1 " .
1178 $this->objects[$liboid]["left"] . " " .
1179 $this->objects[$liboid]["bottom"] . " cm ";
1181 $temp .= $this->_rotate($angle) . " cm ";
1183 if ($xscale != 1 || $yscale != 1) {
1184 $temp .= "$xscale 0 0 $yscale 0 0 cm ";
1186 $temp .= "/Img" . $this->objects[$liboid]["image"] .
1191 function _rotate($angle)
1193 $a = deg2rad($angle);
1196 $r = sprintf("%1\$01.6f %2\$01.6f %3\$01.6f %1\$01.6f 0 0", $cos, $sin, -$sin);
1200 function _get_operator($liboid)
1202 switch ($this->objects[$liboid]['mode']) {
1203 case 0 : return "f"; break;
1204 case 1 : return "S"; break;
1205 case 2 : return "b"; break;
1209 function _make_line($liboid)
1212 if ( $colortest = $this->_colorset($liboid) ) {
1213 $gstate .= $colortest . " ";
1215 if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) {
1216 $gstate .= $this->objects[$liboid]["width"] . " w ";
1220 foreach ($this->objects[$liboid]["x"] as $pointid => $x) {
1221 $y = $this->objects[$liboid]["y"][$pointid];
1222 $temp .= $x . " " . $y . " ";
1225 $firstpoint = false;
1230 $temp .= $this->_get_operator($liboid);
1231 if ( strlen($gstate) > 0 ) {
1232 $temp = "q " . $gstate . $temp . " Q";
1234 return $temp . "\x0a";
1237 function _make_rect($liboid)
1240 if ( $colortest = $this->_colorset($liboid) ) {
1241 $gstate .= $colortest . " ";
1243 if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) {
1244 $gstate .= $this->objects[$liboid]["width"] . " w ";
1246 $temp = $this->objects[$liboid]["left"] . " ";
1247 $temp .= $this->objects[$liboid]["bottom"];
1248 $temp .= " " . ( $this->objects[$liboid]["right"]
1249 - $this->objects[$liboid]["left"] );
1250 $temp .= " " . ( $this->objects[$liboid]["top"]
1251 - $this->objects[$liboid]["bottom"] );
1253 $temp .= $this->_get_operator($liboid);
1254 if ( strlen($gstate) > 0 ) {
1255 $temp = "q " . $gstate . $temp . " Q";
1257 return $temp . "\x0a";
1260 function _make_circle($liboid)
1263 if ( $colortest = $this->_colorset($liboid) ) {
1264 $gstate .= $colortest . " ";
1266 if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) {
1267 $gstate .= $this->objects[$liboid]["width"] . " w ";
1269 $r = $this->objects[$liboid]['radius'];
1270 $x = $this->objects[$liboid]['x'];
1271 $y = $this->objects[$liboid]['y'];
1273 $pt = $y + $r * 1.33333;
1275 $pb = $y - $r * 1.33333;
1276 $temp = "$ql $y m ";
1277 $temp .= "$ql $pt $qr $pt $qr $y c ";
1278 $temp .= "$qr $pb $ql $pb $ql $y c ";
1279 $temp .= $this->_get_operator($liboid);
1280 if ( strlen($gstate) > 0 ) {
1281 $temp = "q " . $gstate . $temp . " Q";
1283 return $temp . "\x0a";
1286 function _make_text($liboid)
1288 $statechange = ""; $locateinbt = true;
1289 $statechange = $this->_colorset($liboid);
1290 if (isset($this->objects[$liboid]["rotation"]) && $this->objects[$liboid]["rotation"] != 0) {
1291 $statechange .= "1 0 0 1 " .
1292 $this->objects[$liboid]["left"] . " " .
1293 $this->objects[$liboid]["bottom"] . " cm " .
1294 $this->_rotate($this->objects[$liboid]["rotation"]) .
1296 $locateinbt = false;
1299 if ($this->objects[$liboid]["mode"] != 0) {
1300 $temp .= $this->objects[$liboid]["mode"] .
1302 // Adjust stroke width
1303 $statechange .= $this->objects[$liboid]["height"] / 35 . " w ";
1305 $temp .= "/F" . $this->objects[$liboid]["font"] . " ";
1306 $temp .= $this->objects[$liboid]["height"];
1309 $temp .= $this->objects[$liboid]["left"] . " " .
1310 $this->objects[$liboid]["bottom"];
1315 $temp .= $this->_stringify($this->objects[$liboid]["text"]);
1318 if (strlen($statechange) > 0) {
1319 $temp = "q " . $statechange . $temp . " Q";
1321 return $temp . "\x0a";
1324 function _colorset($libid)
1326 $red = isset($this->objects[$libid]['fillcolor']["red"]) ? (float)$this->objects[$libid]['fillcolor']["red"] : 0;
1327 $green = isset($this->objects[$libid]['fillcolor']["green"]) ? (float)$this->objects[$libid]['fillcolor']["green"] : 0;
1328 $blue = isset($this->objects[$libid]['fillcolor']["blue"]) ? (float)$this->objects[$libid]['fillcolor']["blue"] : 0;
1329 if (($red > 0) || ($green > 0) || ($blue > 0)) {
1330 $r = $red . " " . $green . " " . $blue;
1335 $red = isset($this->objects[$libid]['strokecolor']["red"]) ? (float)$this->objects[$libid]['strokecolor']["red"] : 0;
1336 $green = isset($this->objects[$libid]['strokecolor']["green"]) ? (float)$this->objects[$libid]['strokecolor']["green"] : 0;
1337 $blue = isset($this->objects[$libid]['strokecolor']["blue"]) ? (float)$this->objects[$libid]['strokecolor']["blue"] : 0;
1338 if (($red > 0) || ($green > 0) || ($blue > 0) ) {
1339 $r .= $red . " " . $green . " " . $blue;
1345 /* Used to determine what pdflib objects need converted
1346 * to actual PDF objects.
1348 function _becomes_object($object)
1350 if ($object == "null") {
1356 /* builds an array of child objects */
1357 function _get_kids($pdfid)
1359 $libid = $this->pdftolib[$pdfid];
1360 foreach( $this->objects as $obid => $object ) {
1361 if (isset($object["parent"]) && $object["parent"] == $libid) {
1362 $kids[] = $this->libtopdf[$obid] . " 0 R";
1368 /* builds an array of pages, in order */
1369 function _order_pages($pdfid)
1371 $libid = $this->pdftolib[$pdfid];
1372 foreach( $this->objects as $obid => $object ) {
1373 if (isset($object["parent"]) && $object["parent"] == $libid) {
1374 $kids[$object["number"]] = $this->libtopdf[$obid] . " 0 R";
1381 /* simple helper function to return the current oid
1382 * and increment it by one
1384 function _addnewoid()
1386 $o = $this->nextoid;
1391 /* The xreftable will contain a list of all the
1392 * objects in the pdf file and the number of bytes
1393 * from the beginning of the file that the object
1394 * occurs. Each time we add an object, we call this
1395 * to record it's location, then call ->_genxreftable()
1396 * to generate the table from array
1398 function _addtoxreftable($offset, $gennum)
1401 $this->xreftable[$this->nextobj]["offset"] = $offset;
1402 $this->xreftable[$this->nextobj]["gennum"] = $gennum;
1403 $this->xreftable[$this->nextobj]["free"] = "n";
1404 return $this->nextobj;
1407 /* Returns a properly formatted pdf dictionary
1408 * containing entries specified by
1409 * the array $entries
1411 function _makedictionary($entries)
1414 if (isset($entries) && count($entries) > 0) {
1415 foreach ($entries as $key => $value) {
1416 $rs .= "/" . $key . " " . $value . "\x0a";
1423 /* returns a properly formatted pdf array */
1424 function _makearray($entries)
1427 if ( is_array($entries) ) {
1428 foreach ($entries as $entry) {
1429 $rs .= $entry . " ";
1434 $rs = rtrim($rs) . "]";
1438 /* Returns a properly formatted string, with any
1439 * special characters escaped
1441 function _stringify($string)
1443 // Escape potentially problematic characters
1444 $string = preg_replace("-\\\\-","\\\\\\\\",$string);
1445 $bad = array ("-\(-", "-\)-" );
1446 $good = array ("\\(", "\\)" );
1447 $string = preg_replace($bad,$good,$string);
1448 return "(" . rtrim($string) . ")";
1451 function _streamify($data, $sarray = array())
1453 /* zlib compression is a compile time option
1454 * for php, thus we need to make sure it's
1455 * available before using it.
1457 if ( function_exists('gzcompress') && $this->builddata["compress"] ) {
1458 // For now, we don't compress if already using a filter
1459 if ( !isset($sarray["Filter"]) || strlen($sarray["Filter"]) == 0 ) {
1460 $sarray["Filter"] = "/FlateDecode";
1462 $sarray['Filter'] = "[/FlateDecode " . $sarray['Filter'] . "]";
1464 $data = gzcompress($data, $this->builddata["compress"]);
1466 $sarray["Length"] = strlen($data);
1467 $os = $this->_makedictionary($sarray);
1468 $os .= "stream\x0a" . $data . "\x0aendstream";
1472 /* Returns a properly formatted page node
1473 * page nodes with 0 kids are not created
1475 function _makepagenode($kids, $addtlopts = false)
1477 $parray["Type"] = "/Pages";
1478 if ( isset($kids) AND count($kids) > 0 ) {
1479 // Array of child objects
1480 $parray["Kids"] = $this->_makearray($kids);
1482 $parray["Count"] = count($kids);
1484 // No kids is an error condition
1485 $this->_push_error(600, "Pagenode has no children");
1488 if ( is_array($addtlopts) ) {
1489 foreach ( $addtlopts as $key => $value ) {
1490 $parray[$key] = $value;
1494 /* The resource dictionary is always object 3
1496 $parray["Resources"] = "3 0 R";
1498 $os = $this->_makedictionary($parray);
1499 $os = "obj" . $os . "endobj";
1503 function _makepage($parent, $contents, $liboid)
1505 $parray["Type"] = "/Page";
1506 $parray["Parent"] = $this->libtopdf[$parent] . " 0 R";
1507 $parray["Contents"] = $this->_makearray($contents);
1508 $parray["MediaBox"] = "[0 0 "
1509 . $this->objects[$liboid]["width"] . " "
1510 . $this->objects[$liboid]["height"] . "]";
1511 $os = $this->_makedictionary($parray);
1512 $os = "obj" . $os . "endobj";