generate() * is called. * The layout of ->objects does not directly * mimic the pdf format, although it is similar. * nextoid always holds the next available oid ( oid is short for object id ) */ var $objects, $nextoid; /* xreftable is an array containing data to * create the xref section (PDF calls it a referance table) */ var $xreftable, $nextobj; /* These arrays allow quick translation between * pdflib OIDs and the final PDF OIDs (OID stands for object ids) */ var $libtopdf, $pdftolib; // Errors var $ermsg = array(), $erno = array(); var $builddata; // Various data required during the pdf build var $nextpage; // Tracks the next page number var $widths, $needsset; // Store the font width arrays here var $default; // Default values for objects var $x, $chart, $template; // extension class is instantiated here if requested /* Constructor function: is automatically called when the * object is created. Used to set up the environment */ function pdffile() { /* Per spec, obj 0 should always have a generation * number of 65535 and is always free */ $this->xreftable[0]["gennum"] = 65535; $this->xreftable[0]["offset"] = 0; $this->xreftable[0]["free"] = "f"; // Object #1 will always be the Document Catalog $this->xreftable[1]["gennum"] = 0; $this->xreftable[1]["free"] = "n"; // Object #2 will always be the root pagenode $this->xreftable[2]["gennum"] = 0; $this->xreftable[2]["free"] = "n"; $this->pdftolib[2] = 1; $this->libtopdf[1] = 2; // Object #3 is always the resource library $this->xreftable[3]["gennum"] = 0; $this->xreftable[3]["free"] = "n"; /* nextoid starts at 2 because all * drawing functions return either the * object ID or FALSE on error, so we can't * return an OID of 0, because it equates * to false and error checking would think * the procedure failed */ $this->nextoid = 2; $this->nextobj = 3; // Pages start at 0 $this->nextpage = 0; // Font width tables are not set unless they are needed $this->needsset = true; // Set all the default values $t['pagesize'] = 'letter'; $t['font'] = 'Helvetica'; $t['height'] = 12; $t['align'] = 'left'; $t['width'] = 1; $t['rotation'] = 0; $t['scale'] = 1; $t['strokecolor'] = $this->get_color('black'); $t['fillcolor'] = $this->get_color('black'); $t['margin-left'] = $t['margin-right'] = $t['margin-top'] = $t['margin-bottom'] =72; $t['tmode'] = 0; // Text: fill $t['smode'] = 1; // Shapes: stroke $this->default = $t; } /****************************************************** * These functions are the public ones, they are the * * way that the user will actually enter the data * * that will become the pdf * ******************************************************/ function set_default($setting, $value) { switch ($setting) { case 'margin' : $this->default['margin-left'] = $value; $this->default['margin-right'] = $value; $this->default['margin-top'] = $value; $this->default['margin-bottom'] = $value; break; case 'mode' : $this->default['tmode'] = $this->default['smode'] = $value; break; default : $this->default[$setting] = $value; } return true; } function draw_rectangle($top, $left, $bottom, $right, $parent, $attrib = array()) { if ($this->objects[$parent]["type"] != "page") { $this->_push_std_error(6001); return false; } $o = $this->_addnewoid(); $attrib = $this->_resolve_param($attrib, false); $this->_resolve_colors($n, $attrib); $this->objects[$o] = $n; $this->objects[$o]["width"] = $attrib["width"]; $this->objects[$o]["type"] = "rectangle"; $this->_adjust_margin($left, $top, $parent); $this->_adjust_margin($right, $bottom, $parent); $this->objects[$o]["top"] = $top; $this->objects[$o]["left"] = $left; $this->objects[$o]["bottom"] = $bottom; $this->objects[$o]["right"] = $right; $this->objects[$o]["parent"] = $parent; $this->objects[$o]["mode"] = $this->_resolve_mode($attrib, 'smode'); return $o; } function draw_circle($x, $y, $r, $parent, $attrib = array()) { if ($this->objects[$parent]["type"] != "page") { $this->_push_std_error(6001); return false; } $o = $this->_addnewoid(); $attrib = $this->_resolve_param($attrib, false); $this->_resolve_colors($n, $attrib); $n['width'] = $attrib['width']; $this->_adjust_margin($x, $y, $parent); $n['x'] = $x; $n['y'] = $y; $n['radius'] = $r; $n['type'] = 'circle'; $n['parent'] = $parent; $n['mode'] = $this->_resolve_mode($attrib, 'smode'); $this->objects[$o] = $n; return $o; } function draw_line($x, $y, $parent, $attrib = array()) { if ($this->objects[$parent]["type"] != "page") { $this->_push_std_error(6001); return false; } if (count($x) != count($y)) { $this->_push_error(6002, "X & Y variables must have equal number of elements"); return false; } $o = $this->_addnewoid(); $attrib = $this->_resolve_param($attrib, false); $this->_resolve_colors($n, $attrib); $this->objects[$o] = $n; @$this->objects[$o]["width"] = $attrib["width"]; $this->objects[$o]['mode'] = $this->_resolve_mode($attrib, 'smode'); $this->objects[$o]["type"] = "line"; foreach ($x as $key => $value) { if (isset($x[$key]) && isset($y[$key])) { $this->_adjust_margin($x[$key], $y[$key], $parent); } } $this->objects[$o]["x"] = $x; $this->objects[$o]["y"] = $y; $this->objects[$o]["parent"] = $parent; return $o; } // draw text function draw_text($left, $bottom, $text, $parent, $attrib = array()) { if ($this->objects[$parent]["type"] != "page") { $this->_push_std_error(6001); return false; } $attrib = $this->_resolve_param($attrib); // Validate the font if (!($n["font"] = $this->_use_font($attrib))) { // Couldn't find/add the font $this->_push_error(6003, "Font was not found"); return false; } if (isset($attrib["rotation"])) { $n["rotation"] = $attrib["rotation"]; } $n['mode'] = $this->_resolve_mode($attrib, 'tmode'); if (isset($attrib["height"]) && $attrib["height"] > 0) { $n["height"] = $attrib["height"]; } $this->_resolve_colors($n, $attrib); $n["type"] = "texts"; $this->_adjust_margin($left, $bottom, $parent); $n["left"] = $left; $n["bottom"] = $bottom; $n["text"] = $text; $n["parent"] = $parent; $o = $this->_addnewoid(); $this->objects[$o] = $n; return $o; } function new_page($size = null) { if (is_null($size)) { $size = $this->default['pagesize']; } switch ($size) { case "letter" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 792; $this->objects[$o]["width"] = 612; break; case "legal" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 1008; $this->objects[$o]["width"] = 612; break; case "executive" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 720; $this->objects[$o]["width"] = 540; break; case "tabloid" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 1224; $this->objects[$o]["width"] = 792; break; case "a3" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 1188; $this->objects[$o]["width"] = 842; break; case "a4" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 842; $this->objects[$o]["width"] = 595; break; case "a5" : $o = $this->_addnewoid(); $this->objects[$o]["height"] = 598; $this->objects[$o]["width"] = 418; break; default : if (preg_match("/in/",$size)) { $o = $this->_addnewoid(); $size = substr($size, 0, strlen($size) - 2); $dims = split("x",$size); $this->objects[$o]["height"] = ($dims[1] * 72); $this->objects[$o]["width"] = ($dims[0] * 72); } else { if (preg_match("/cm/",$size)) { $o = $this->_addnewoid(); $size = substr($size, 0, strlen($size) - 2); $dims = split("x",$size); $this->objects[$o]["height"] = ($dims[1] * 28.346); $this->objects[$o]["width"] = ($dims[0] * 28.346); } else { $this->_push_error(6004, "Could not deciper page size description: $size"); return false; } } } $this->objects[$o]['type'] = 'page'; $this->objects[$o]['parent'] = 1; $this->objects[$o]['number'] = $this->nextpage; $this->nextpage ++; foreach (array('margin-left', 'margin-right', 'margin-top', 'margin-bottom') as $margin) { $this->objects[$o][$margin] = $this->default[$margin]; } return $o; } function swap_pages($p1, $p2) { if ($this->objects[$p1]["type"] != "page" || $this->objects[$p2]["type"] != "page") { $this->_push_std_error(6001); return false; } $temp = $this->objects[$p1]["number"]; $this->objects[$p1]["number"] = $this->objects[$p2]["number"]; $this->objects[$p2]["number"] = $temp; return true; } function move_page_before($page, $infrontof) { if ($this->objects[$page]["type"] != "page" || $this->objects[$infrontof]["type"] != "page") { $this->_push_std_error(6001); return false; } if ($page == $infrontof) { $this->_push_error(6005, "You're trying to swap a page with itself"); return false; } $target = $this->objects[$infrontof]["number"]; $leaving = $this->objects[$page]["number"]; foreach ($this->objects as $id => $o) { if ($o["type"] == "page") { if ($target < $leaving) { if ($o["number"] >= $target && $o["number"] < $leaving) { $this->objects[$id]["number"]++; } } else { if ($o["number"] < $target && $o["number"] > $leaving) { $this->objects[$id]["number"]--; } } } } if ($target < $leaving) { $this->objects[$page]["number"] = $target; } else { $this->objects[$page]["number"] = $target - 1; } return true; } function new_font($identifier) { $n["type"] = "font"; switch ($identifier) { /* The "standard" Type 1 fonts * These are "guaranteed" to be available * to the viewer application and don't * need embedded */ case "Courier": case "Courier-Bold": case "Courier-Oblique": case "Courier-BoldOblique": case "Helvetica": case "Helvetica-Bold": case "Helvetica-Oblique": case "Helvetica-BoldOblique": case "Times-Roman": case "Times-Bold": case "Times-Italic": case "Times-BoldItalic": case "Symbol": case "ZapfDingbats": $o = $this->_addnewoid(); $this->builddata["fonts"][$o] = $identifier; $n["subtype"] = "Type1"; $n["basefont"] = $identifier; break; default: if ($this->objects[$identifier]["type"] != "fontembed") { $this->_push_error(6006, "Object must be of type 'fontembed'"); return false; } else { // Not ready yet $this->_push_error(6007, "Feature not implemented yet"); return false; } } $this->objects[$o] = $n; return $o; } function generate($clevel = 9) { // Validate the compression level if (!$clevel) { $this->builddata["compress"] = false; } else { if ($clevel < 10) { $this->builddata["compress"] = $clevel; } else { $this->builddata["compress"] = 9; } } /* Preprocess objects to see if they can * be combined into a single stream * We scan through each page, and create * a multistream object out of all viable * child objects */ $temparray = $this->objects; foreach ($this->objects as $oid => $def) { if ( $def["type"] == "page" ) { unset($temp); $temp['data'] = ""; reset($temparray); while ( list ($liboid, $obj) = each($temparray) ) { if (isset($obj["parent"]) && $obj["parent"] == $oid) { switch ($obj["type"]) { case "texts" : $temp["data"] .= $this->_make_text($liboid); $this->objects[$liboid]["type"] = "null"; $this->objects[$liboid]["parent"] = -1; break; case "rectangle" : $temp["data"] .= $this->_make_rect($liboid); $this->objects[$liboid]["type"] = "null"; $this->objects[$liboid]["parent"] = -1; break; case "iplace" : $temp["data"] .= $this->_place_raw_image($liboid); $this->objects[$liboid]["type"] = "null"; $this->objects[$liboid]["parent"] = -1; break; case "line" : $temp["data"] .= $this->_make_line($liboid); $this->objects[$liboid]["type"] = "null"; $this->objects[$liboid]["parent"] = -1; break; case "circle" : $temp["data"] .= $this->_make_circle($liboid); $this->objects[$liboid]["type"] = "null"; $this->objects[$liboid]["parent"] = -1; break; } } } if (strlen($temp["data"]) > 0) { // this line takes the next available oid $o = $this->_addnewoid(); $temp["type"] = "mstream"; $temp["parent"] = $oid; $this->objects[$o] = $temp; } } } unset($temparray); // Generate a list of PDF object IDs to // use and map them to phppdflib IDs foreach ( $this->objects as $oid => $properties ) { if ( $this->_becomes_object( $properties["type"] ) ) { $o = $this->_addtoxreftable(0,0); $this->libtopdf[$oid] = $o; $this->pdftolib[$o] = $oid; } } /* First characters represent the version * of the PDF spec to conform to. * The PDF spec recommends that the next * four bytes be a comment containing four * non-ASCII characters, to convince * (for example) ftp programs that this is * a binary file */ $os = "%PDF-1.3%\xe2\xe3\xcf\xd3\x0a"; // Create the Document Catalog $carray["Type"] = "/Catalog"; $carray["Pages"] = "2 0 R"; $temp = $this->_makedictionary($carray); $temp = "1 0 obj" . $temp . "endobj\x0a"; $this->xreftable[1]["offset"] = strlen($os); $os .= $temp; // Create the root page node unset($carray); $kids = $this->_order_pages(2); $this->xreftable[2]["offset"] = strlen($os); $os .= "2 0 " . $this->_makepagenode($kids, "" ) . "\x0a"; /* Create a resource dictionary for the entire * PDF file. This may not be the most efficient * way to store it, but it makes the code simple. * At some point, we should analyze performance * and see if it's worth splitting the resource * dictionary up */ unset($temp); unset($carray); if (isset($this->builddata["fonts"]) && count($this->builddata["fonts"]) > 0) { foreach ($this->builddata["fonts"] as $id => $base) { $ta["F$id"] = $this->libtopdf[$id] . " 0 R"; } $temp["Font"] = $this->_makedictionary($ta); } reset($this->objects); while (list($id, $obj) = each($this->objects)) { if ($obj["type"] == "image") { $xol["Img$id"] = $this->libtopdf[$id] . " 0 R"; } } if ( isset($xol) && count($xol) > 0 ) { $temp["XObject"] = $this->_makedictionary($xol); } $this->xreftable[3]["offset"] = strlen($os); $os .= "3 0 obj"; if (isset($temp)) { $os .= $this->_makedictionary($temp); } else { $os .= '<<>>'; } $os .= " endobj\x0a"; // Go through and add the rest of the objects foreach ( $this->pdftolib as $pdfoid => $liboid ) { if ($pdfoid < 4) { continue; } // Set the location of the start $this->xreftable[$pdfoid]["offset"] = strlen($os); switch ( $this->objects[$liboid]["type"] ) { case "page": $kids = $this->_get_kids($pdfoid); $os .= $pdfoid . " 0 "; $os .= $this->_makepage($this->objects[$liboid]["parent"], $kids, $liboid); break; case "rectangle": $os .= $pdfoid . " 0 obj"; $os .= $this->_streamify($this->_make_rect($liboid)); $os .= " endobj"; break; case "line": $os .= $pdfoid . " 0 obj"; $os .= $this->_streamify($this->_make_line($liboid)); $os .= " endobj"; break; case "circle": $os .= $pdfoid . " 0 obj"; $os .= $this->_streamify($this->_make_circle($liboid)); $os .= " endobj"; break; case "texts": $os .= $pdfoid . " 0 obj"; $temp = $this->_make_text($liboid); $os .= $this->_streamify($temp) . " endobj"; break; case "mstream": $os .= $pdfoid . " 0 obj" . $this->_streamify(trim($this->objects[$liboid]["data"])) . " endobj"; break; case "image": $os .= $pdfoid . " 0 obj"; $os .= $this->_make_raw_image($liboid); $os .= " endobj"; break; case "iplace": $os .= $pdfoid . " 0 obj"; $os .= $this->_streamify($this->_place_raw_image($liboid)); $os .= " endobj"; break; case "font" : $os .= $pdfoid . " 0 obj"; unset ( $temp ); $temp["Type"] = "/Font"; $temp["Subtype"] = "/" . $this->objects[$liboid]["subtype"]; $temp["BaseFont"] = "/" . $this->objects[$liboid]["basefont"]; $temp["Encoding"] = "/WinAnsiEncoding"; $temp["Name"] = "/F$liboid"; $os .= $this->_makedictionary($temp); $os .= " endobj"; break; } $os .= "\x0a"; } // Create an Info entry $info = $this->_addtoxreftable(0,0); $this->xreftable[$info]["offset"] = strlen($os); unset($temp); $temp["Producer"] = $this->_stringify("phppdflib http://www.potentialtech.com/ppl.php"); $os .= $info . " 0 obj" . $this->_makedictionary($temp) . " endobj\x0a"; // Create the xref table $this->builddata["startxref"] = strlen($os); $os .= "xref\x0a0 " . (string)($this->nextobj + 1) . "\x0a"; for ( $i = 0; $i <= $this->nextobj; $i ++ ) { $os .= sprintf("%010u %05u %s \x0a", $this->xreftable[$i]["offset"], $this->xreftable[$i]["gennum"], $this->xreftable[$i]["free"]); } // Create document trailer $os .= "trailer\x0a"; unset($temp); $temp["Size"] = $this->nextobj + 1; $temp["Root"] = "1 0 R"; $temp["Info"] = $info . " 0 R"; $os .= $this->_makedictionary($temp); $os .= "\x0astartxref\x0a"; $os .= $this->builddata["startxref"] . "\x0a"; // Required end of file marker $os .= "%%EOF\x0a"; return $os; } function png_embed($data) { // Sanity, make sure this is a png if (substr($data, 0, 8) != "\x89PNG\x0d\x0a\x1a\x0a") { $this->_push_error(6011, 'brand not valid'); return false; } $data = substr($data, 12); if (substr($data, 0, 4) != 'IHDR') { $this->_push_error(6011, 'IHDR chunk missing'); return false; } $data = substr($data, 4); $width = $this->_int_val(substr($data, 0, 4)); $height = $this->_int_val(substr($data, 4, 4)); $data = substr($data, 8); $bpc = ord(substr($data, 0, 1)); $ct = ord(substr($data, 1, 1)); if ($bpc > 8) { $this->_push_error(6014, '16 bit PNG unsupported'); return false; } switch ($ct) { case 0 : $cspace = '/DeviceGray'; break; case 2 : $cspace = '/DeviceRGB'; break; case 3 : $cspace = '/Indexed'; break; default: $this->_push_error(6015, 'PNG with alpha not supported'); return false; } if (ord(substr($data, 2, 1)) != 0) { $this->_push_error(6016, 'Unknown compression type'); return false; } if (ord(substr($data, 3, 1)) != 0) { $this->_push_error(6017, 'Unknown PNG filter method'); return false; } if (ord(substr($data, 4, 1)) != 0) { $this->_push_error(6018, 'PNG interlacing not supported'); return false; } $params['Predictor'] = '15'; $params['Colors'] = $ct == 2 ? 3 : 1; $params['BitsPerComponent'] = $bpc; $params['Columns'] = $width; $additional['DecodeParms'] = $this->_makedictionary($params); $data = substr($data, 9); $pal = ''; $trns = ''; $rawdata = ''; do { $n = $this->_int_val(substr($data, 0, 4)); $type = substr($data, 4, 4); $data = substr($data, 8); switch ($type) { case 'PLTE' : $pal = substr($data, 0, $n); $data = substr($data, $n + 4); break; case 'tRNS' : $t = substr($data, 0, $n); if ($ct == 0) $trns = array(ord(substr($t, 1, 1))); elseif ($ct == 2) $trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1))); else { $pos = strpos($t, chr(0)); if (is_int($pos)) $trns = array($pos); } $data = substr($data, $n + 4); break; case 'IDAT' : $rawdata .= substr($data, 0, $n); $data = substr($data, $n + 4); break; case 'IEND' : break 2; default : $data = substr($data, $n + 4); } } while ($n); if ($cspace == '/Indexed') { $this->_push_error(6011, 'Indexed without palette'); return false; } return $this->image_raw_embed($rawdata, $cspace, $bpc, $height, $width, '/FlateDecode', $additional); } function jfif_embed($data) { /* Sanity check: Check magic numbers to see if * this is really a JFIF stream */ if ( substr($data, 0, 4) != "\xff\xd8\xff\xe0" || substr($data, 6, 4) != "JFIF" ) { // This is not in JFIF format $this->_push_std_error(6008); return false; } /* Now we'll scan through all the markers in the * JFIF and extract whatever data we need from them * We're not being terribly anal about validating * the structure of the JFIF, so a corrupt stream * could have very unpredictable results */ // Default values $pos = 0; $size = strlen($data); while ( $pos < $size ) { $marker = substr($data, $pos + 1, 1); // Just skip these markers if ($marker == "\xd8" || $marker == "\xd9" || $marker == "\x01") { $pos += 2; continue; } if ($marker == "\xff") { $pos ++; continue; } switch ($marker) { // Start of frame // Baseline case "\xc0": // Extended sequential case "\xc1": // Differential sequential case "\xc5": // Progressive case "\xc2": // differential progressive case "\xc6": // Lossless case "\xc3": // differential lossless case "\xc7": // Arithmetic encoded case "\xc9": case "\xca": case "\xcb": case "\xcd": case "\xce": case "\xcf": $precision = $this->_int_val(substr($data, $pos + 4, 1)); $height = $this->_int_val(substr($data, $pos + 5, 2)); $width = $this->_int_val(substr($data, $pos + 7, 2)); $numcomp = $this->_int_val(substr($data, $pos + 9, 1)); if ( $numcomp != 3 && $numcomp != 1 ) { // Abort if we aren't encoded as B&W or YCbCr $this->_push_std_error(6008); return false; } $pos += 2 + $this->_int_val(substr($data, $pos + 2, 2)); break 2; } /* All marker identifications continue the * loop, thus if we got here, we need to skip * this marker as we don't understand it. */ $pos += 2 + $this->_int_val(substr($data, $pos + 2, 2)); } $cspace = $numcomp == 1 ? "/DeviceGray" : "/DeviceRGB"; return $this->image_raw_embed($data, $cspace, $precision, $height, $width, "/DCTDecode"); } function image_raw_embed($data, $cspace, $bpc, $height, $width, $filter = "", $addtl = array()) { $o = $this->_addnewoid(); $t['additional'] = $addtl; $t['data'] = $data; $t['colorspace'] = $cspace; $t['bpc'] = $bpc; $t['type'] = "image"; $t['height'] = $height; $t['width'] = $width; $t['filter'] = $filter; $this->objects[$o] = $t; return $o; } function get_image_size($id) { if ($this->objects[$id]['type'] != 'image') { $this->_push_std_error(6009); return false; } $r['width'] = $this->objects[$id]['width']; $r['height'] = $this->objects[$id]['height']; return $r; } function image_place($oid, $bottom, $left, $parent, $param = array()) { if ($this->objects[$oid]["type"] != "image") { $this->_push_std_error(6009); return false; } if ($this->objects[$parent]["type"] != "page") { $this->_push_std_error(6001); return false; } $o = $this->_addnewoid(); $param = $this->_resolve_param($param, false); $t["type"] = "iplace"; $this->_adjust_margin($left, $bottom, $parent); $t["bottom"] = $bottom; $t["left"] = $left; $t["parent"] = $parent; // find out what the image size should be $width = $this->objects[$oid]["width"]; $height = $this->objects[$oid]["height"]; $scale = $param['scale']; if (is_array($scale)) { $t["xscale"] = $scale["x"] * $width; $t["yscale"] = $scale["y"] * $height; } else { $t["xscale"] = $scale * $width; $t["yscale"] = $scale * $height; } $t["rotation"] = $param['rotation']; $t["image"] = $oid; $this->objects[$o] = $t; return $o; } function strlen($string , $params = false, $tabwidth = 4) { if ($this->needsset) { require_once(dirname(__FILE__) . '/strlen.inc.php'); } if (empty($params["font"])) { $font = $this->default['font']; } else { $font = $params["font"]; switch ($font) { case "Times-Roman" : $font = "Times"; break; case "Helvetica-Oblique" : $font = "Helvetica"; break; case "Helvetica-BoldOblique" : $font = "Helvetica-Bold"; break; case "ZapfDingbats" : $font = "Dingbats"; break; } } if ($params["height"] == 0) { $size = $this->default['height']; } else { $size = $params["height"]; } $tab = ''; for ($i = 0; $i < $tabwidth; $i++) { $tab .= ' '; } $string = str_replace(chr(9), $tab, $string); if (substr($font, 0, 7) == "Courier") { // Courier is a fixed-width font $width = strlen($string) * 600; } else { $width = 0; $len = strlen($string); for ($i = 0; $i < $len; $i++) { $width += $this->widths[$font][ord($string{$i})]; } } // We now have the string width in font units return $width * $size / 1000; } function wrap_line(&$text, $width, $param = array()) { $maxchars = (int)(1.1 * $width / $this->strlen("i", $param)); $words = explode(" ", substr($text, 0, $maxchars)); if ($this->strlen($words[0]) >= $width) { $this->_push_error(3001, "Single token too long for allowed space"); $final = $words[0]; } else { $space = $this->strlen(" ", $param); $len = 0; $word = 0; $final = ""; while ($len < $width) { if ($word >= count($words)) { break; } $temp = $this->strlen($words[$word], $param); if ( ($len + $temp) <= $width) { $final .= $words[$word] . " "; $word ++; } $len += $space + $temp; } } $text = substr($text, strlen($final)); return $final; } function word_wrap($words, $width, $param = array()) { // break $words into an array separated by manual paragraph break character $paragraph = explode("\n", $words); // find the width of 1 space in this font $swidth = $this->strlen( " " , $param ); // uses each element of $paragraph array and splits it at spaces for ($lc = 0; $lc < count($paragraph); $lc++){ while (strlen($paragraph[$lc]) > 0) { $returnarray[] = $this->wrap_line($paragraph[$lc], $width, $param); } } return $returnarray; } function draw_one_paragraph($top, $left, $bottom, $right, $text, $page, $param = array()) { $param = $this->_resolve_param($param); $height = 1.1 * $param['height']; $width = $right - $left; while ($top > $bottom) { if (strlen($text) < 1) { break; } $top -= $height; if ($top >= $bottom) { $line = $this->wrap_line($text, $width, $param); switch ($param['align']) { case 'right' : $line = trim($line); $l = $right - $this->strlen($line, $param); break; case 'center' : $line = trim($line); $l = $left + (($width - $this->strlen($line, $param)) / 2); break; default : $l = $left; } $this->draw_text($l, $top, $line, $page, $param); } else { $top += $height; break; } } if (strlen($text) > 0) { return $text; } else { return $top; } } function draw_paragraph($top, $left, $bottom, $right, $text, $page, $param = array()) { $paras = split("\n", $text); for ($i = 0; $i < count($paras); $i++) { $over = $this->draw_one_paragraph($top, $left, $bottom, $right, $paras[$i], $page, $param); if (is_string($over)) { break; } $top = $over; } $rv = $over; if ($i < count($paras)) { for ($x = $i + 1; $x < count($paras); $x++) { $rv .= "\n" . $paras[$x]; } } return $rv; } function error_array() { $rv = array(); while (count($this->ermsg) > 0) { $this->pop_error($num, $msg); $rv[] = "Error $num: $msg"; } return $rv; } function pop_error(&$num, &$msg) { $num = array_pop($this->erno); $msg = array_pop($this->ermsg); if (is_null($num)) { return false; } else { return $num; } } function enable($name) { $name = strtolower($name); @include_once(dirname(__FILE__) . "/${name}.class.php"); $this->x[$name] = new $name; $this->x[$name]->pdf = &$this; switch ($name) { case 'chart' : case 'template' : $this->$name = &$this->x[$name]; break; } } function get_color($desc) { $r = array(); switch (strtolower($desc)) { case 'black' : $r['red'] = $r['blue'] = $r['green'] = 0; break; case 'white' : $r['red'] = $r['blue'] = $r['green'] = 1; break; case 'red' : $r['red'] = 1; $r['blue'] = $r['green'] = 0; break; case 'blue' : $r['blue'] = 1; $r['red'] = $r['green'] = 0; break; case 'green' : $r['green'] = 1; $r['blue'] = $r['red'] = 0; break; default : if (substr($desc, 0, 1) == '#') { // Parse out a hex triplet $v = substr($desc, 1, 2); $r['red'] = eval("return ord(\"\\x$v\");") / 255; $v = substr($desc, 3, 2); $r['green'] = eval("return ord(\"\\x$v\");") / 255; $v = substr($desc, 5, 2); $r['blue'] = eval("return ord(\"\\x$v\");") / 255; } else { // Error condition? $this->_push_error(6012, "Unparsable color identifier: $desc"); $r = false; } } return $r; } /****************************************************** * These functions are internally used by the library * * and shouldn't really be called by a user of * * phppdflib * ******************************************************/ function _resolve_mode($attrib, $mode) { $rmode = $attrib[$mode]; if ($rmode != 0) { $r = $rmode; } else { switch ($rmode) { case "fill": $r = 0; break; case "stroke": $r = 1; break; case "fill+stroke": $r = 2; break; } } return $r; } function _adjust_margin(&$x, &$y, $page) { $x += $this->objects[$page]['margin-left']; $y += $this->objects[$page]['margin-bottom']; } function _resolve_param($param, $text = true) { $rv = $this->default; if (is_array($param)) { if (isset($param['mode'])) { $param['tmode'] = $param['smode'] = $param['mode']; } foreach ($param as $key => $value) { $rv[$key] = $value; } } return $rv; } function _push_error($num, $msg) { array_push($this->erno, $num); array_push($this->ermsg, $msg); } function _push_std_error($num) { switch ($num) { case 6001 : $m = "Object must be of type 'page'"; break; case 6008 : $m = "Data stream not recognized as JFIF"; break; case 6009 : $m = "Object must be of type 'image'"; break; case 6011 : $m = "Data stream not recognized as PNG"; break; default : $m = "_push_std_error() called with invalid error number: $num"; break; } $this->_push_error($num, $m); } function _resolve_colors(&$n, $attrib) { $temp = array('red','green','blue'); foreach ($temp as $colcomp) { if (isset($attrib['fillcolor'][$colcomp])) { $n['fillcolor'][$colcomp] = $attrib['fillcolor'][$colcomp]; } if (isset($attrib['strokecolor'][$colcomp])) { $n['strokecolor'][$colcomp] = $attrib['strokecolor'][$colcomp]; } } } /* Check to see if a requested font is already in the * list, if not add it. Either way, return the libid * of the font */ function _use_font($id) { if (!isset($id['font'])) { $id['font'] = $this->default['font']; } if ( isset($this->builddata["fonts"]) && count($this->builddata["fonts"]) > 0 ) { foreach ($this->builddata["fonts"] as $libid => $name) { if ($name == $id['font']) { return $libid; } } } /* The font isn't in the table, so we add it * and return it's ID */ return $this->new_font($id['font']); } /* Convert a big-endian byte stream into an integer */ function _int_val($string) { $r = 0; for ($i = 0; $i < strlen($string); $i ++ ) { $r += ord($string{$i}) * pow(256, strlen($string) - $i -1); } return $r; } function _make_raw_image($liboid) { if (is_array($this->objects[$liboid]['additional'])) $s = $this->objects[$liboid]['additional']; $s["Type"] = "/XObject"; $s["Subtype"] = "/Image"; $s["Width"] = $this->objects[$liboid]["width"]; $s["Height"] = $this->objects[$liboid]["height"]; $s["ColorSpace"] = $this->objects[$liboid]["colorspace"]; $s["BitsPerComponent"] = $this->objects[$liboid]["bpc"]; if (strlen($this->objects[$liboid]["filter"]) > 0) { $s["Filter"] = $this->objects[$liboid]["filter"]; } return $this->_streamify($this->objects[$liboid]["data"], $s); } function _place_raw_image($liboid) { $xscale = $this->objects[$liboid]["xscale"]; $yscale = $this->objects[$liboid]["yscale"]; $angle = $this->objects[$liboid]["rotation"]; $temp = "q 1 0 0 1 " . $this->objects[$liboid]["left"] . " " . $this->objects[$liboid]["bottom"] . " cm "; if ($angle != 0) { $temp .= $this->_rotate($angle) . " cm "; } if ($xscale != 1 || $yscale != 1) { $temp .= "$xscale 0 0 $yscale 0 0 cm "; } $temp .= "/Img" . $this->objects[$liboid]["image"] . " Do Q\x0a"; return $temp; } function _rotate($angle) { $a = deg2rad($angle); $cos = cos($a); $sin = sin($a); $r = sprintf("%1\$1.6f %2\$1.6f %3\$1.6f %1\$1.6f 0 0", $cos, $sin, -$sin); return $r; } function _get_operator($liboid) { switch ($this->objects[$liboid]['mode']) { case 0 : return "f"; break; case 1 : return "S"; break; case 2 : return "b"; break; } } function _make_line($liboid) { $gstate = ""; if ( $colortest = $this->_colorset($liboid) ) { $gstate .= $colortest . " "; } if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) { $gstate .= $this->objects[$liboid]["width"] . " w "; } $firstpoint = true; $temp = ""; foreach ($this->objects[$liboid]["x"] as $pointid => $x) { $y = $this->objects[$liboid]["y"][$pointid]; $temp .= $x . " " . $y . " "; if ($firstpoint) { $temp .= "m "; $firstpoint = false; } else { $temp .= "l "; } } $temp .= $this->_get_operator($liboid); if ( strlen($gstate) > 0 ) { $temp = "q " . $gstate . $temp . " Q"; } return $temp . "\x0a"; } function _make_rect($liboid) { $gstate = ""; if ( $colortest = $this->_colorset($liboid) ) { $gstate .= $colortest . " "; } if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) { $gstate .= $this->objects[$liboid]["width"] . " w "; } $temp = $this->objects[$liboid]["left"] . " "; $temp .= $this->objects[$liboid]["bottom"]; $temp .= " " . ( $this->objects[$liboid]["right"] - $this->objects[$liboid]["left"] ); $temp .= " " . ( $this->objects[$liboid]["top"] - $this->objects[$liboid]["bottom"] ); $temp .= ' re '; $temp .= $this->_get_operator($liboid); if ( strlen($gstate) > 0 ) { $temp = "q " . $gstate . $temp . " Q"; } return $temp . "\x0a"; } function _make_circle($liboid) { $gstate = ""; if ( $colortest = $this->_colorset($liboid) ) { $gstate .= $colortest . " "; } if ( isset($this->objects[$liboid]["width"]) && $this->objects[$liboid]["width"] != 1 ) { $gstate .= $this->objects[$liboid]["width"] . " w "; } $r = $this->objects[$liboid]['radius']; $x = $this->objects[$liboid]['x']; $y = $this->objects[$liboid]['y']; $ql = $x - $r; $pt = $y + $r * 1.33333; $qr = $x + $r; $pb = $y - $r * 1.33333; $temp = "$ql $y m "; $temp .= "$ql $pt $qr $pt $qr $y c "; $temp .= "$qr $pb $ql $pb $ql $y c "; $temp .= $this->_get_operator($liboid); if ( strlen($gstate) > 0 ) { $temp = "q " . $gstate . $temp . " Q"; } return $temp . "\x0a"; } function _make_text($liboid) { $statechange = ""; $locateinbt = true; $statechange = $this->_colorset($liboid); if (isset($this->objects[$liboid]["rotation"]) && $this->objects[$liboid]["rotation"] != 0) { $statechange .= "1 0 0 1 " . $this->objects[$liboid]["left"] . " " . $this->objects[$liboid]["bottom"] . " cm " . $this->_rotate($this->objects[$liboid]["rotation"]) . " cm "; $locateinbt = false; } $temp = "BT "; if ($this->objects[$liboid]["mode"] != 0) { $temp .= $this->objects[$liboid]["mode"] . " Tr "; // Adjust stroke width $statechange .= $this->objects[$liboid]["height"] / 35 . " w "; } $temp .= "/F" . $this->objects[$liboid]["font"] . " "; $temp .= $this->objects[$liboid]["height"]; $temp .= " Tf "; if ($locateinbt) { $temp .= $this->objects[$liboid]["left"] . " " . $this->objects[$liboid]["bottom"]; } else { $temp .= "0 0"; } $temp .= " Td "; $temp .= $this->_stringify($this->objects[$liboid]["text"]); $temp .= " Tj "; $temp .= "ET"; if (strlen($statechange) > 0) { $temp = "q " . $statechange . $temp . " Q"; } return $temp . "\x0a"; } function _colorset($libid) { $red = isset($this->objects[$libid]['fillcolor']["red"]) ? (float)$this->objects[$libid]['fillcolor']["red"] : 0; $green = isset($this->objects[$libid]['fillcolor']["green"]) ? (float)$this->objects[$libid]['fillcolor']["green"] : 0; $blue = isset($this->objects[$libid]['fillcolor']["blue"]) ? (float)$this->objects[$libid]['fillcolor']["blue"] : 0; if (($red > 0) || ($green > 0) || ($blue > 0)) { $r = $red . " " . $green . " " . $blue; $r .= " rg "; } else { $r = ""; } $red = isset($this->objects[$libid]['strokecolor']["red"]) ? (float)$this->objects[$libid]['strokecolor']["red"] : 0; $green = isset($this->objects[$libid]['strokecolor']["green"]) ? (float)$this->objects[$libid]['strokecolor']["green"] : 0; $blue = isset($this->objects[$libid]['strokecolor']["blue"]) ? (float)$this->objects[$libid]['strokecolor']["blue"] : 0; if (($red > 0) || ($green > 0) || ($blue > 0) ) { $r .= $red . " " . $green . " " . $blue; $r .= " RG "; } return $r; } /* Used to determine what pdflib objects need converted * to actual PDF objects. */ function _becomes_object($object) { if ($object == "null") { return false; } return true; } /* builds an array of child objects */ function _get_kids($pdfid) { $libid = $this->pdftolib[$pdfid]; foreach( $this->objects as $obid => $object ) { if (isset($object["parent"]) && $object["parent"] == $libid) { $kids[] = $this->libtopdf[$obid] . " 0 R"; } } return $kids; } /* builds an array of pages, in order */ function _order_pages($pdfid) { $libid = $this->pdftolib[$pdfid]; foreach( $this->objects as $obid => $object ) { if (isset($object["parent"]) && $object["parent"] == $libid) { $kids[$object["number"]] = $this->libtopdf[$obid] . " 0 R"; } } ksort($kids); return $kids; } /* simple helper function to return the current oid * and increment it by one */ function _addnewoid() { $o = $this->nextoid; $this->nextoid++; return $o; } /* The xreftable will contain a list of all the * objects in the pdf file and the number of bytes * from the beginning of the file that the object * occurs. Each time we add an object, we call this * to record it's location, then call ->_genxreftable() * to generate the table from array */ function _addtoxreftable($offset, $gennum) { $this->nextobj ++; $this->xreftable[$this->nextobj]["offset"] = $offset; $this->xreftable[$this->nextobj]["gennum"] = $gennum; $this->xreftable[$this->nextobj]["free"] = "n"; return $this->nextobj; } /* Returns a properly formatted pdf dictionary * containing entries specified by * the array $entries */ function _makedictionary($entries) { $rs = "<<\x0a"; if (isset($entries) && count($entries) > 0) { foreach ($entries as $key => $value) { $rs .= "/" . $key . " " . $value . "\x0a"; } } $rs .= ">>"; return $rs; } /* returns a properly formatted pdf array */ function _makearray($entries) { $rs = "["; if ( is_array($entries) ) { foreach ($entries as $entry) { $rs .= $entry . " "; } } else { $rs .= $entries; } $rs = rtrim($rs) . "]"; return $rs; } /* Returns a properly formatted string, with any * special characters escaped */ function _stringify($string) { // Escape potentially problematic characters $string = preg_replace("-\\\\-","\\\\\\\\",$string); $bad = array ("-\(-", "-\)-" ); $good = array ("\\(", "\\)" ); $string = preg_replace($bad,$good,$string); return "(" . rtrim($string) . ")"; } function _streamify($data, $sarray = array()) { /* zlib compression is a compile time option * for php, thus we need to make sure it's * available before using it. */ $go = true; if (function_exists('gzcompress') && $this->builddata['compress']) { if ( !isset($sarray['Filter']) || strlen($sarray['Filter']) == 0 ) { $sarray['Filter'] = '/FlateDecode'; } else { if ($sarray['Filter'] != '/FlateDecode') $sarray['Filter'] = '[/FlateDecode ' . $sarray['Filter'] . ']'; else $go = false; } if ($go) $data = gzcompress($data, $this->builddata['compress']); } $sarray['Length'] = strlen($data); $os = $this->_makedictionary($sarray); $os .= "stream\x0a" . $data . "\x0aendstream"; return $os; } /* Returns a properly formatted page node * page nodes with 0 kids are not created */ function _makepagenode($kids, $addtlopts = false) { $parray["Type"] = "/Pages"; if ( isset($kids) AND count($kids) > 0 ) { // Array of child objects $parray["Kids"] = $this->_makearray($kids); // Number of pages $parray["Count"] = count($kids); } else { // No kids is an error condition $this->_push_error(600, "Pagenode has no children"); return false; } if ( is_array($addtlopts) ) { foreach ( $addtlopts as $key => $value ) { $parray[$key] = $value; } } /* The resource dictionary is always object 3 */ $parray["Resources"] = "3 0 R"; $os = $this->_makedictionary($parray); $os = "obj" . $os . "endobj"; return $os; } function _makepage($parent, $contents, $liboid) { $parray["Type"] = "/Page"; $parray["Parent"] = $this->libtopdf[$parent] . " 0 R"; $parray["Contents"] = $this->_makearray($contents); $parray["MediaBox"] = "[0 0 " . $this->objects[$liboid]["width"] . " " . $this->objects[$liboid]["height"] . "]"; $os = $this->_makedictionary($parray); $os = "obj" . $os . "endobj"; return $os; } } ?>