<?php
/*
   php pdf generation library
   Copyright (C) Potential Technologies 2002 - 2003
   http://www.potentialtech.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   $Id: phppdflib.class.php 7 2004-02-11 20:44:53Z gruiick $
*/

class pdffile
{
    /* $objects is an array that stores the objects
     * that will become the pdf when ->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,
        $packer, $import;       // extension class is instantiated here
    var $debug = 0, $dbs = '';  // Default debug level, higher = more data
    
    /* 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');
        $this->_debug_var(10, "Placed rectangle: ", $this->objects[$o]);
        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;
        $this->_debug_var(10, "Placed circle ", $o);
        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;
        $this->_debug_var(10, 'Placed line', $this->objects[$o]);
        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;
        $this->_debug_var(10, 'Placed text', $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 "a4-landscape" :
            $o = $this->_addnewoid();
            $this->objects[$o]["height"] = 595;
            $this->objects[$o]["width"] = 842;
            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];
        }
        $this->_debug_var(10, 'New page', $this->objects[$o]);
        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;
        $this->_debug_var(10, 'Added font', $n);
        return $o;
    }

    function generate($clevel = 9)
    {
        $this->_debug(1, 'Starting ->generate()');
        // 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) {
                        $this->_debug(10, "Adding # $liboid to mstream");
                        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();
                    $this->_debug(10, "Saving mstream to # $o");
                    $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;
        $t['data'] = '[Data stripped]';
        $this->_debug_var(10, 'Embedded image', $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;
        $this->_debug_var(10, 'Placed image', $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];
            }
        }
        $this->_debug(10, "Drawing paragraph\n$text\nReturning\n$rv");
        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' :
        case 'packer' :
        case 'import' :
	        $this->$name = &$this->x[$name];
            break;
        }
        $this->_debug(10, "enabled extension: $name");
    }

    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;
            }
        }
        $this->_debug_var(10, "get_color: $desc ", $r);
        return $r;
    }

/******************************************************
 * These functions are internally used by the library *
 * and shouldn't really be called by a user of        *
 * phppdflib                                          *
 ******************************************************/

    function _debug_var($level, $name, $value)
    {
        if ($level <= $this->debug) {
            $this->dbs .= $name . ' -> ' . $this->_print_r($value) . "\n";
        }
    }

    function _print_r($v)
    {
        if (version_compare('4.3.0', phpversion()) <= 0) {
            return print_r($v, true);
        } else {
            if (is_array($v)) {
                $r = "array {\n";
                foreach ($v as $key => $value) {
                    $r .= "[$key] = " . $this->print_r($value) . "\n";
                }
                $r .= "}\n";
                return $r;
            } else {
                return $v;
            }
        }
    }
    
    function _debug($level, $string)
    {
        if ($level <= $this->debug) {
            $this->dbs .= $string . "\n";
        }
    }
    
    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);
        $this->_debug(1, "ERROR: $num: $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";
        $this->_debug(20, "Made image: $temp");
        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";
        }
        $this->_debug(20, "Made line: $temp");
        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";
        }
        $this->_debug(20, "Made rectangle: $temp");
        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";
        }
        $this->_debug(20, "Made circle: $temp");
        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";
        }
        $this->_debug(20, "Made text: $temp");
        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 ";
        }
        $this->_debug(20, "Resolved color for $libid: $r");
        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";
        $this->_debug(20, "Pagenode: $os");
        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";
        $this->_debug(20, "Page from $liboid: $os");
        return $os;
    }

}
?>
