Location: PHPKode > projects > Moc10 PHP Library > library/Moc10/Pdf.php
<?php
/**
 * Moc10 Library
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.TXT.
 * It is also available through the world-wide-web at this URL:
 * http://www.moc10phplibrary.com/LICENSE.TXT
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to hide@address.com so we can send you a copy immediately.
 *
 * @category   Moc10
 * @package    Moc10_Pdf
 * @author     Nick Sagona, III <hide@address.com>
 * @copyright  Copyright (c) 2009-2011 Moc 10 Media, LLC. (http://www.moc10media.com)
 * @license    http://www.moc10phplibrary.com/LICENSE.TXT     New BSD License
 */

/**
 * Moc10_Pdf
 *
 * @category   Moc10
 * @package    Moc10_Pdf
 * @author     Nick Sagona, III <hide@address.com>
 * @copyright  Copyright (c) 2009-2011 Moc 10 Media, LLC. (http://www.moc10media.com)
 * @license    http://www.moc10phplibrary.com/LICENSE.TXT     New BSD License
 * @version    1.9.7
 */

class Moc10_Pdf extends Moc10_File
{

    /**
     * PDF root index.
     * @var int
     */
    protected $_root = 1;

    /**
     * PDF parent index.
     * @var int
     */
    protected $_parent = 2;

    /**
     * PDF info index.
     * @var int
     */
    protected $_info = 3;

    /**
     * Array of allowed file types.
     * @var array
     */
    protected $_allowed = array('pdf'  => 'application/pdf');

    /**
     * Array of PDF page object indices.
     * @var array
     */
    protected $_pages = array();

    /**
     * Array of PDF objects.
     * @var array
     */
    protected $_objects = array();

    /**
     * PDF trailer.
     * @var string
     */
    protected $_trailer = null;

    /**
     * Current PDF page.
     * @var int
     */
    protected $_curPage = null;

    /**
     * PDF text parameters.
     * @var array
     */
    protected $_textParams = array('c' => 0, 'w' => 0, 'h' => 100, 'v' => 100, 'rot' => 0, 'rend' => 0);

    /**
     * PDF bytelength
     * @var int
     */
    protected $_bytelength = null;

    /**
     * PDF output data
     * @var string
     */
    protected $_output = null;

    /**
     * Standard PDF fonts with their approximate character width and height factors.
     * @var array
     */
    protected $_standard_fonts = array('Arial'                    => array('width_factor' => 0.5, 'height_factor' => 1),
                                       'Arial,Italic'             => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'Arial,Bold'               => array('width_factor' => 0.55, 'height_factor' => 1.12),
                                       'Arial,BoldItalic'         => array('width_factor' => 0.55, 'height_factor' => 1.12),
                                       'Courier'                  => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'CourierNew'               => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'Courier-Oblique'          => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'CourierNew,Italic'        => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'Courier-Bold'             => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'CourierNew,Bold'          => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'Courier-BoldOblique'      => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'CourierNew,BoldItalic'    => array('width_factor' => 0.65, 'height_factor' => 1),
                                       'Helvetica'                => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'Helvetica-Oblique'        => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'Helvetica-Bold'           => array('width_factor' => 0.55, 'height_factor' => 1.12),
                                       'Helvetica-BoldOblique'    => array('width_factor' => 0.55, 'height_factor' => 1.12),
                                       'Symbol'                   => array('width_factor' => 0.85, 'height_factor' => 1.12),
                                       'Times-Roman'              => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'Times-Bold'               => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'Times-Italic'             => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'Times-BoldItalic'         => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'TimesNewRoman'            => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'TimesNewRoman,Italic'     => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'TimesNewRoman,Bold'       => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'TimesNewRoman,BoldItalic' => array('width_factor' => 0.5, 'height_factor' => 1.12),
                                       'ZapfDingbats'             => array('width_factor' => 0.75, 'height_factor' => 1.12));

    /**
     * Language object
     * @var Moc10_Language
     */
    protected $_lang = null;

    /**
     * Constructor
     *
     * Instantiate a PDF file object based on either a pre-existing PDF file on disk, or a new PDF file.
     * Arguments may be passed to add a page upon instantiation. The PDF file exists, it and all of its
     * assets will be imported.
     *
     * @param  string $pdf
     * @param  string $sz
     * @param  int $w
     * @param  int $h
     * @return void
     */
    public function __construct($pdf, $sz = null, $w = null, $h = null)
    {

        $this->_lang = new Moc10_Language();

        parent::__construct($pdf, false);

        // Create a new, base PDF.
        $this->_objects[1] = new Moc10_Pdf_Root();
        $this->_objects[2] = new Moc10_Pdf_Parent();
        $this->_objects[3] = new Moc10_Pdf_Info();

        // If the PDF file already exists, import it.
        if ($this->size != 0) {
            $this->importPdf($this->fullpath);
        }

        // If page parameters were passed, add a new page.
        if (!is_null($sz) || (!is_null($w) && !is_null($h))) {
            $this->addPage($sz, $w, $h);
        }

    }

    /**
     * Method to import either an entire PDF, or a page of a PDF, and the related data.
     *
     * @param  string $pdf
     * @param  int|string|array $pg
     * @return void
     */
    public function importPdf($pdf, $pg = null)
    {

        // Create a new PDF Import object.
        $pdfi = new Moc10_Pdf_Import($pdf, $pg);

        // Shift the imported objects indices based on existing indices in this PDF.
        $pdfi->shiftObjects(array_keys($this->_objects), ($this->_lastIndex($this->_objects) + 1));

        // Fetch the imported objects.
        $imported_objs = $pdfi->returnObjects($this->_parent);

        // Loop through the imported objects, adding the pages or objects as applicable.
        foreach($imported_objs as $key => $value) {

            if ($value['type'] == 'page') {

                // Add the page object.
                $this->_objects[$key] = new Moc10_Pdf_Page($value['data']);

                // Finalize related page variables and objects.
                $this->_curPage = (is_null($this->_curPage)) ? 0 : ($this->_lastIndex($this->_pages) + 1);
                $this->_pages[$this->_curPage] = $key;
                $this->_objects[$this->_parent]->count += 1;
                $this->_objects[$this->_parent]->kids[] = $key;

            } else {

                // Else, add the content object.
                $this->_objects[$key] = new Moc10_Pdf_Object($value['data']);

            }

        }

    }

    /**
     * Method to add a page to the PDF of a determined size.
     *
     * @param  string $sz
     * @param  int $w
     * @param  int $h
     * @return void
     */
    public function addPage($sz = null, $w = null, $h = null)
    {

        // Define the next page and content object indices.
        $pi = $this->_lastIndex($this->_objects) + 1;
        $ci = $this->_lastIndex($this->_objects) + 2;

        // Create the page object.
        $this->_objects[$pi] = new Moc10_Pdf_Page(null, $sz, $w, $h, $pi);
        $this->_objects[$pi]->content[] = $ci;
        $this->_objects[$pi]->curContent = $ci;
        $this->_objects[$pi]->parent = $this->_parent;

        // Create the content object.
        $this->_objects[$ci] = new Moc10_Pdf_Object($ci);

        // Finalize related page variables and objects.
        $this->_curPage = (is_null($this->_curPage)) ? 0 : ($this->_lastIndex($this->_pages) + 1);
        $this->_pages[$this->_curPage] = $pi;
        $this->_objects[$this->_parent]->count += 1;
        $this->_objects[$this->_parent]->kids[] = $pi;

    }

    /**
     * Method to copy a page of the PDF.
     *
     * @param  int $pg
     * @throws Exception
     * @return void
     */
    public function copyPage($pg)
    {

        $key = $pg - 1;

        // Check if the page exists.
        if (!array_key_exists($key, $this->_pages)) {
            throw new Exception($this->_lang->__('Error: That page does not exist.'));
        } else {
            $pi = $this->_lastIndex($this->_objects) + 1;
            $ci = $this->_lastIndex($this->_objects) + 2;
            $this->_objects[$pi] = new Moc10_Pdf_Page($this->_objects[$this->_pages[$key]]);
            $this->_objects[$pi]->index = $pi;

            // Duplicate the page's content objects.
            $oldContent = $this->_objects[$pi]->content;
            unset($this->_objects[$pi]->content);
            foreach ($oldContent as $key => $value) {
                $this->_objects[$ci] = new Moc10_Pdf_Object((string)$this->_objects[$value]);
                $this->_objects[$ci]->index = $ci;
                $this->_objects[$pi]->content[] = $ci;
                $ci += 1;
            }

            // Finalize related page variables and objects.
            $this->_curPage = (is_null($this->_curPage)) ? 0 : ($this->_lastIndex($this->_pages) + 1);
            $this->_pages[$this->_curPage] = $pi;
            $this->_objects[$this->_parent]->count += 1;
            $this->_objects[$this->_parent]->kids[] = $pi;

        }

    }

    /**
     * Method to delete the page of the PDF and its content objects.
     *
     * @param  int $pg
     * @throws Exception
     * @return void
     */
    public function deletePage($pg)
    {

        $key = $pg - 1;

        // Check if the page exists.
        if (!array_key_exists($key, $this->_pages)) {
            throw new Exception($this->_lang->__('Error: That page does not exist.'));
        } else {

            // Determine the page index and related data.
            $pi = $this->_pages[$key];
            $ki =  array_search($pi, $this->_objects[$this->_parent]->kids);
            $content_objs = $this->_objects[$pi]->content;

            // Remove the page's content objects.
            if (count($content_objs) != 0) {
                foreach ($content_objs as $value) {
                    unset($this->_objects[$value]);
                }
            }

            // Subtract the page from the parent's count property.
            $this->_objects[$this->_parent]->count -= 1;

            // Remove the page from the kids and pages arrays, and remove the page object.
            unset($this->_objects[$this->_parent]->kids[$ki]);
            unset($this->_pages[$key]);
            unset($this->_objects[$pi]);

            // Reset the kids array.
            $tmpAry = $this->_objects[$this->_parent]->kids;
            $this->_objects[$this->_parent]->kids = array();
            foreach ($tmpAry as $value) {
                $this->_objects[$this->_parent]->kids[] = $value;
            }

            // Reset the pages array.
            $tmpAry = $this->_pages;
            $this->_pages = array();
            foreach ($tmpAry as $value) {
                $this->_pages[] = $value;
            }

        }

    }

    /**
     * Method to order the pages of the PDF.
     *
     * @param  array $pgs
     * @throws Exception
     * @return void
     */
    public function orderPages($pgs)
    {

        $newOrder = array();

        // Check if the PDF has more than one page.
        if (count($this->_pages) <= 1) {

            throw new Exception($this->_lang->__('Error: The PDF does not have enough pages in which to order.'));

        } else if (count($pgs) != count($this->_pages)) {

            // Else, check if the numbers of pages passed equals the number of pages in the PDF.
            throw new Exception($this->_lang->__('Error: The pages array passed does not contain the same number of pages as the PDF.'));

        } else {

            // Make sure each page passed is within the PDF and not out of range.
            foreach ($pgs as $value) {
                if (!array_key_exists(($value - 1), $this->_pages)) {
                    throw new Exception($this->_lang->__('Error: The pages array passed contains a page that does not exist.'));
                }
            }

            // Set the new order of the page objects.
            foreach ($pgs as $value) {
                $newOrder[] = $this->_pages[$value - 1];
            }

        }

        // Set the kids and pages arrays to the new order.
        $this->_objects[$this->_parent]->kids = $newOrder;
        $this->_pages = $newOrder;

    }

    /**
     * Method to return the current page number of the current page of the PDF.
     *
     * @return int
     */
    public function curPage()
    {

        return ($this->_curPage + 1);

    }

    /**
     * Method to return the current number of pages in the PDF.
     *
     * @return int
     */
    public function numPages()
    {

        return count($this->_pages);

    }

    /**
     * Method to set the current page of the PDF in which to edit.
     *
     * @param  int $pg
     * @throws Exception
     * @return Moc10_Pdf
     */
    public function setPage($pg)
    {

        $key = $pg - 1;

        // Check if the page exists.
        if (!array_key_exists($key, $this->_pages)) {
            throw new Exception($this->_lang->__('Error: That page does not exist.'));
        } else {
            $this->_curPage = $pg - 1;
        }

        return $this;

    }

    /**
     * Method to set the PDF version.
     *
     * @param  string $ver
     * @return Moc10_Pdf
     */
    public function setVersion($ver)
    {

        $this->_objects[$this->_root]->version = $ver;
        return $this;

    }

    /**
     * Method to set the PDF info title.
     *
     * @param  string $tle
     * @return Moc10_Pdf
     */
    public function setTitle($tle)
    {

        $this->_objects[$this->_info]->title = $tle;
        return $this;

    }

    /**
     * Method to set the PDF info author.
     *
     * @param  string $auth
     * @return Moc10_Pdf
     */
    public function setAuthor($auth)
    {

        $this->_objects[$this->_info]->author = $auth;
        return $this;

    }

    /**
     * Method to set the PDF info subject.
     *
     * @param  string $subj
     * @return Moc10_Pdf
     */
    public function setSubject($subj)
    {

        $this->_objects[$this->_info]->subject = $subj;
        return $this;

    }

    /**
     * Method to set the PDF info creation date.
     *
     * @param  string $dt
     * @return Moc10_Pdf
     */
    public function setCreateDate($dt)
    {

        $this->_objects[$this->_info]->create_date = $dt;
        return $this;

    }

    /**
     * Method to set the PDF info modification date.
     *
     * @param  string $dt
     * @return Moc10_Pdf
     */
    public function setModDate($dt)
    {

        $this->_objects[$this->_info]->mod_date = $dt;
        return $this;

    }

    /**
     * Method to set the fill color of objects and text in the PDF.
     *
     * @param  int $r
     * @param  int $g
     * @param  int $b
     * @return Moc10_Pdf
     */
    public function setFillColor($r, $g, $b)
    {

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("\n" . $this->_convertColor($r) . " " . $this->_convertColor($g) . " " . $this->_convertColor($b) . " rg\n");

        return $this;

    }

    /**
     * Method to set the stroke color of paths in the PDF.
     *
     * @param  int $r
     * @param  int $g
     * @param  int $b
     * @return Moc10_Pdf
     */
    public function setStrokeColor($r, $g, $b)
    {

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("\n" . $this->_convertColor($r) . " " . $this->_convertColor($g) . " " . $this->_convertColor($b) . " RG\n");

        return $this;

    }

    /**
     * Method to set the width and dash properties of paths in the PDF.
     *
     * @param  int $w
     * @param  int $dash_len
     * @param  int $dash_gap
     * @return Moc10_Pdf
     */
    public function setStroke($w = null, $dash_len = null, $dash_gap = null)
    {

        // Set stroke to the $w argument, or else default it to 1pt.
        $new_str = (!is_null($w)) ? "\n{$w} w\n" : "1 w\n";

        // Set the dash properties of the stroke, or else default it to a solid line.
        $new_str .= (!is_null($dash_len) && !is_null($dash_gap)) ? "[{$dash_len} {$dash_gap}] 0 d\n" : "[] 0 d\n";

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream($new_str);

        return $this;

    }

    /**
     * Method to set the text parameters for rendering text content.
     *
     * @param  int $c
     * @param  int $w
     * @param  int $h
     * @param  int $v
     * @param  int $rot
     * @param  int $rend
     * @throws Exception
     * @return Moc10_Pdf
     */
    public function setTextParams($c = 0, $w = 0, $h = 100, $v = 100, $rot = 0, $rend = 0)
    {

        // Check the rotation parameter.
        if (abs($rot) > 90) {
            throw new Exception($this->_lang->__('Error: The rotation parameter must be between -90 and 90 degrees.'));
        }

        // Check the render parameter.
        if ((!is_int($rend)) || (($rend > 7) || ($rend < 0))) {
            throw new Exception($this->_lang->__('Error: The render parameter must be an integer between 0 and 7.'));
        }

        // Set the text parameters.
        $this->_textParams['c'] = $c;
        $this->_textParams['w'] = $w;
        $this->_textParams['h'] = $h;
        $this->_textParams['v'] = $v;
        $this->_textParams['rot'] = $rot;
        $this->_textParams['rend'] = $rend;

        return $this;

    }

    /**
     * Method to add a font object to the PDF.
     *
     * @param  string $font
     * @throws Exception
     * @return void
     */
    public function addFont($font)
    {

        // Check to make sure the font is a standard PDF font.
        if (!array_key_exists($font, $this->_standard_fonts)) {
            throw new Exception($this->_lang->__('Error: That font is not contained within the standard PDF fonts.'));
        } else {

            // Set the font index.
            $ft_index = (count($this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts) == 0) ? 1 : ($this->_lastIndex($this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts) + 1);

            // Set the font name and the next object index.
            $f = 'MF' . $ft_index;
            $i = $this->_lastIndex($this->_objects) + 1;

            // Add the font to the current page's fonts and add the font to _objects array.
            $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts[$font] = "/{$f} {$i} 0 R";
            $this->_objects[$i] = new Moc10_Pdf_Object("{$i} 0 obj\n<<\n    /Type /Font\n    /Subtype /Type1\n    /Name /{$f}\n    /BaseFont /{$font}\n    /Encoding /StandardEncoding\n>>\nendobj\n\n");

        }

    }

    /**
     * Method to add text to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  string $str
     * @param  string $font
     * @param  int $size
     * @throws Exception
     * @return void
     */
    public function addText($x, $y, $str, $font, $size)
    {

        // Check to see if the font already exists on another page.
        $fontExists = false;

        foreach ($this->_pages as $value) {
            if (array_key_exists($font, $this->_objects[$value]->fonts)) {
                $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts[$font] = $this->_objects[$value]->fonts[$font];
                $fontObj = substr($this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts[$font], 1, (strpos(' ', $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts[$font]) + 3));
                $fontExists = true;
            }
        }

        // If the font does not already exist, add it.
        if (!$fontExists) {
            if (array_key_exists($font, $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts)) {
                $fontObj = substr($this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts[$font], 1, (strpos(' ', $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->fonts[$font]) + 3));
            } else {
                throw new Exception($this->_lang->__('Error: That font has not been added to the PDF.'));
            }
        }

        // Add the text to the current page's content stream.
        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("\nBT\n    /{$fontObj} {$size} Tf\n    " . $this->_calcTextMatrix() . " {$x} {$y} Tm\n    " . $this->_textParams['c'] . " Tc " . $this->_textParams['w'] . " Tw " . $this->_textParams['rend'] . " Tr\n    ({$str})Tj\nET\n");

    }

    /**
     * Method to get the width and height of a string in a certain font. It returns
     * an array with the approximate width, height and offset baseline values.
     *
     * @param  string $str
     * @param  string $font
     * @param  int $sz
     * @throws Exception
     * @return array
     */
    public function getStringSize($str, $font, $sz)
    {

        if (!array_key_exists($font, $this->_standard_fonts)) {
            throw new Exception($this->_lang->__('Error: That font is not contained within the standard PDF fonts.'));
        } else {

            // Calculate the approximate width, height and offset baseline values of the string at the certain font.
            $size = array();

            $size['width'] = round(($sz * $this->_standard_fonts[$font]['width_factor']) * strlen($str));
            $size['height'] = round($sz * $this->_standard_fonts[$font]['height_factor']);
            $size['baseline'] = round($sz / 3);

            return $size;

        }

    }

    /**
     * Method to add a line to the PDF.
     *
     * @param  int $x1
     * @param  int $y1
     * @param  int $x2
     * @param  int $y2
     * @return void
     */
    public function addLine($x1, $y1, $x2, $y2)
    {

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("\n{$x1} {$y1} m\n{$x2} {$y2} l\nS\n");

    }

    /**
     * Method to add a rectangle to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  int $l
     * @param  int $w
     * @param  boolean $fill
     * @return void
     */
    public function addRect($x, $y, $l, $w, $fill = true)
    {

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("\n{$x} {$y} {$l} {$w} re\n" . (($fill) ? 'B' : 'S') . "\n");

    }

    /**
     * Method to add a square to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  int $w
     * @param  boolean $fill
     * @return void
     */
    public function addSquare($x, $y, $w, $fill = true)
    {

        $this->addRect($x, $y, $w, $w, $fill);

    }

    /**
     * Method to add an ellipse to the PDF.
     *
     * @param  int $x1
     * @param  int $y1
     * @param  int $x2
     * @param  int $y2
     * @param  int $x3
     * @param  int $y3
     * @param  int $x4
     * @param  int $y4
     * @param  boolean $fill
     * @return void
     */
    public function addEllipse($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4, $fill = true)
    {

        // Calculate coordinate number one's 2 bezier points.
        $coor1_bez1_x = $x1;
        $coor1_bez1_y = (round(0.55 * ($y2 - $y1))) + $y1;
        $coor1_bez2_x = $x1;
        $coor1_bez2_y = (round(0.45 * ($y1 - $y4))) + $y4;

        // Calculate coordinate number two's 2 bezier points.
        $coor2_bez1_x = (round(0.45 * ($x2 - $x1))) + $x1;
        $coor2_bez1_y = $y2;
        $coor2_bez2_x = (round(0.55 * ($x3 - $x2))) + $x2;
        $coor2_bez2_y = $y2;

        // Calculate coordinate number three's 2 bezier points.
        $coor3_bez1_x = $x3;
        $coor3_bez1_y = (round(0.55 * ($y2 - $y3))) + $y3;
        $coor3_bez2_x = $x3;
        $coor3_bez2_y = (round(0.45 * ($y3 - $y4))) + $y4;

        // Calculate coordinate number four's 2 bezier points.
        $coor4_bez1_x = (round(0.55 * ($x3 - $x4))) + $x4;
        $coor4_bez1_y = $y4;
        $coor4_bez2_x = (round(0.45 * ($x4 - $x1))) + $x1;
        $coor4_bez2_y = $y4;

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("\n{$x1} {$y1} m\n{$coor1_bez1_x} {$coor1_bez1_y} {$coor2_bez1_x} {$coor2_bez1_y} {$x2} {$y2} c\n{$coor2_bez2_x} {$coor2_bez2_y} {$coor3_bez1_x} {$coor3_bez1_y} {$x3} {$y3} c\n{$coor3_bez2_x} {$coor3_bez2_y} {$coor4_bez1_x} {$coor4_bez1_y} {$x4} {$y4} c\n{$coor4_bez2_x} {$coor4_bez2_y} {$coor1_bez2_x} {$coor1_bez2_y} {$x1} {$y1} c\n" . (($fill) ? 'b' : 'S') . "\n");

    }

    /**
     * Method to add a circle to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  int $r
     * @param  boolean $fill
     * @return void
     */
    public function addCircle($x, $y, $r, $fill = true)
    {

        $this->addEllipse(($x - $r), $y, $x, ($y + $r), ($x + $r), $y, $x, ($y - $r), $fill);

    }

    /**
     * Method to add a URL link to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  int $l
     * @param  int $w
     * @param  string $url
     * @return void
     */
    public function addURL($x, $y, $l, $w, $url)
    {

        $x2 = $x + $l;
        $y2 = $y + $w;

        $i = $this->_lastIndex($this->_objects) + 1;

        // Add the annotation index to the current page's annotations and add the annotation to _objects array.
        $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->annots[] = $i;
        $this->_objects[$i] = new Moc10_Pdf_Object("{$i} 0 obj\n<<\n    /Type /Annot\n    /Subtype /Link\n    /Rect [{$x} {$y} {$x2} {$y2}]\n    /Border [0 0 0]\n    /A <</S /URI /URI ({$url})>>\n>>\nendobj\n\n");

    }

    /**
     * Method to add an internal link to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  int $l
     * @param  int $w
     * @param  int $X
     * @param  int $Y
     * @param  int $Z
     * @param  int $dest
     * @throws Exception
     * @return void
     */
    public function addLink($x, $y, $l, $w, $X, $Y, $Z, $dest = null)
    {

        $x2 = $x + $l;
        $y2 = $y + $w;

        $i = $this->_lastIndex($this->_objects) + 1;

        // Set the destination of the internal link, or default to the current page.
        if (!is_null($dest)) {

            if (!isset($this->_pages[$dest - 1])) {
                throw new Exception($this->_lang->__('Error: That page has not been defined.'));
            } else {
                $d = $this->_objects[$this->_pages[$dest - 1]]->index;
            }

        } else {
            // Else, set the destination to the current page.
            $d = $this->_objects[$this->_pages[$this->_curPage]]->index;

        }

        // Add the annotation index to the current page's annotations and add the annotation to _objects array.
        $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->annots[] = $i;
        $this->_objects[$i] = new Moc10_Pdf_Object("{$i} 0 obj\n<<\n    /Type /Annot\n    /Subtype /Link\n    /Rect [{$x} {$y} {$x2} {$y2}]\n    /Border [0 0 0]\n    /Dest [{$d} 0 R /XYZ {$X} {$Y} {$Z}]\n>>\nendobj\n\n");

    }

    /**
     * Method to add an image to the PDF.
     *
     * @param  int $x
     * @param  int $y
     * @param  string $image
     * @param  float $scl
     * @throws Exception
     * @return void
     */
    public function addImage($x, $y, $image, $scl = null)
    {

        // Get the initial image data.
        $img = new Moc10_Image($image);

        // Calculate the scaled image, if applicable.
        $scaled_img = '';

        if (!is_null($scl)) {
            $scaled_img = $img->dir . $img->filename . '_scaled.' . $img->ext;
            $img->copy($scaled_img);
            $img = new Moc10_Image($scaled_img);
            $img->scale($scl);
        }

        // Set the image width and height.
        $width = $img->width;
        $height = $img->height;

        // Set the initial image data and data length.
        $img_data = $img->read();
        $img_length = strlen($img_data);

        $i = $this->_lastIndex($this->_objects) + 1;

        // If the image is a JPG.
        if ($img->mime == 'image/jpeg') {

            // Determine the JPG colorspace.
            if ($img->channels == 1) {
                $colorspace = "/DeviceGray";
            } else if ($img->channels == 4) {
                $colorspace = "/DeviceCMYK\n    /Decode [1 0 1 0 1 0 1 0]";
            } else {
                $colorspace = "/DeviceRGB";
            }

            // Add the image to the _objects array.
            $this->_objects[$i] = new Moc10_Pdf_Object("{$i} 0 obj\n<<\n    /Type /XObject\n    /Subtype /Image\n    /Width {$width}\n    /Height {$height}\n    /ColorSpace {$colorspace}\n    /BitsPerComponent 8\n    /Filter /DCTDecode\n    /Length {$img_length}\n>>\nstream\n{$img_data}\nendstream\nendobj\n");

            // If the image has been scaled, delete the scaled image resource.
            if ($scaled_img != '') {
                $img->delete();
            }

        // Else, if the image is a PNG or a GIF.
        } else if (($img->mime == 'image/png') || ($img->mime == 'image/gif')) {

            // Define some variables.
            $PLTE = null;
            $TRNS = null;
            $mask_index = null;
            $mask = null;
            $org_mime = null;

            // If the image is a GIF, convert to a PNG and re-read image data.
            if ($img->mime == 'image/gif') {

                $org_mime = $img->mime;

                $img->convert('png');
                $img_data = $img->read();

                // Image conversion clean-up.
                if (($scaled_img != '') && file_exists($scaled_img)) {
                    unlink($scaled_img);
                }

            }

            // Read the bits and colors of the PNG image.
            $bits = ord($img_data[24]);
            $colors = ord($img_data[25]);

            // Make sure the PNG does not contain a true alpha channel.
            if (($colors == 4) || ($colors == 6)) {
                throw new Exception($this->_lang->__('Error: PNG alpha channels are not supported. Only 8-bit transparent PNG images are supported.'));
            }

            // Determine the PNG colorspace.
            if ($colors == 0) {

                $color_space = '/DeviceGray';
                $num_of_colors = 1;

            } else if ($colors == 2) {

		        $color_space = '/DeviceRGB';
                $num_of_colors = 3;

            } else if ($colors == 3) {

		        $color_space = '/Indexed';
                $num_of_colors = 1;

                // If the PNG is indexed, parse and read the palette and any transparencies that might exist.
                if (strpos($img_data, 'PLTE') !== false) {

                    // If a transparency exists, parse it and set the mask accordindly, along with the palette.
                    if (strpos($img_data, 'tRNS') !== false) {

                        $PLTE = substr($img_data, (strpos($img_data, "PLTE") + 4), (strpos($img_data, "tRNS") - strpos($img_data, "PLTE") - 4));
                        $TRNS = substr($img_data, (strpos($img_data, "tRNS") + 4), (strpos($img_data, "IDAT") - strpos($img_data, "tRNS") - 4));

                        $mask_index = strpos($TRNS, chr(0));
                        $mask = "    /Mask [" . $mask_index . " " . $mask_index . "]\n";

                    } else {
                        // Else, just set the palette.
                        $PLTE = substr($img_data, (strpos($img_data, "PLTE") + 4), (strpos($img_data, "IDAT") - strpos($img_data, "PLTE") - 4));
                        $mask = '';

                    }

                }

                $color_space = "[/Indexed /DeviceRGB " . ($img->colorTotal() - 1) . " " . ($i + 1) . " 0 R]";

            }

            // Parse and set the PNG image data and data length.
            $IDAT = substr($img_data, (strpos($img_data, "IDAT") + 4), (strpos($img_data, "IEND") - strpos($img_data, "IDAT") - 4));
            $img_length = strlen($IDAT);

            // Add the image to the _objects array.
            $this->_objects[$i] = new Moc10_Pdf_Object("{$i} 0 obj\n<<\n    /Type /XObject\n    /Subtype /Image\n    /Width {$width}\n    /Height {$height}\n    /ColorSpace {$color_space}\n    /BitsPerComponent {$bits}\n    /Filter /FlateDecode\n    /DecodeParms <</Predictor 15 /Colors {$num_of_colors} /BitsPerComponent {$bits} /Columns {$width}>>\n{$mask}    /Length {$img_length}\n>>\nstream\n{$IDAT}\nendstream\nendobj\n");

            // If it exists, add the image palette to the _objects array.
            if ($PLTE != '') {
                $j = $i + 1;
                $this->_objects[$j] = new Moc10_Pdf_Object("{$j} 0 obj\n<<\n    /Length " . strlen($PLTE) . "\n>>\nstream\n{$PLTE}\nendstream\nendobj\n");
            }

            // If the image is a GIF, delete the converted image resource.
            if ($org_mime == 'image/gif') {
                $img->delete();
            }

        } else {

            throw new Exception($this->_lang->__('Error: That image type is not supported. Only GIF, JPG and PNG image types are supported.'));

        }

        // Add the image to the current page's xobject array and content stream.
        $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->xobjs[] = "/I{$i} {$i} 0 R";

        $co_index = $this->_getContentObject();
        $this->_objects[$co_index]->setStream("q\n{$width} 0 0 {$height} {$x} {$y} cm\n/I{$i} Do\nQ\n");

    }

    /**
     * Save the PDF directly to the server.
     *
     * @param  string $filename
     * @throws Exception
     * @return void
     */
    public function save($filename = null)
    {

        // Format and finalize the PDF.
        $this->finalize();

        if (is_null($filename)) {

            // Write the PDF to itself.
            if (file_exists($this->fullpath)) {
                throw new Exception($this->_lang->__('Error: The file already exists and it cannot be overwritten. Please specify a new filename.'));
            } else {
                $this->write($this->_output);
            }

        } else {

            // Write the PDF to the newly specified filename.
            if (file_exists($filename)) {
                throw new Exception($this->_lang->__('Error: The file already exists and it cannot be overwritten. Please specify another filename.'));
            } else {
                $new_pdf = new Moc10_File($filename);
                $new_pdf->write($this->_output);
            }

        }

    }

    /**
     * Output the PDF directly to the browser.
     *
     * @param  boolean $download
     * @return void
     */
    public function output($download = false)
    {

        // Format and finalize the PDF.
        $this->finalize();

        $attach = ($download !== false) ? 'attachment; ' : '';

        // Send the file's mime type.
        header('Content-type: ' . $this->mime);

        // Send the file information.
        header('Content-disposition: ' . $attach . 'filename=' . $this->basename);

        // Send cache control headers for IE SSL issue.
        if ($_SERVER['SERVER_PORT'] == 443) {
            header('Expires: 0');
            header('Cache-Control: private, must-revalidate');
            header('Pragma: cache');
        }

        // Output the file contents.
        print($this->_output);

    }

    /**
     * Method to finalize the PDF.
     *
     * @return void
     */
    public function finalize()
    {

        // Define some variables and initialize the trailer.
        $numObjs = count($this->_objects);
        $this->_trailer = "xref\n0 {$numObjs}\n0000000000 65535 f \n";

        // Calculate the root object lead off.
        $byteLength = $this->_calcByteLength($this->_objects[$this->_root]);
        $this->_bytelength += $byteLength;
        $this->_trailer .= $this->_formatByteLength($this->_bytelength) . " 00000 n \n";
        $this->_output .= $this->_objects[$this->_root];

        // Loop through the rest of the objects, calculate their size and length for the xref table and add their data to the output.
        foreach ($this->_objects as $value) {

            if ($value->index != $this->_root) {
                $byteLength = $this->_calcByteLength($value);
                $this->_bytelength += $byteLength;
                $this->_trailer .= $this->_formatByteLength($this->_bytelength) . " 00000 n \n";
                $this->_output .= $value;
            }

        }

        // Finalize the trailer.
        $this->_trailer .= "trailer\n<</Size {$numObjs}/Root {$this->_root} 0 R/Info {$this->_info} 0 R>>\nstartxref\n" . ($this->_bytelength + 68) . "\n%%EOF";

        // Append the trailer to the final output.
        $this->_output .= $this->_trailer;

    }

    /**
     * Method to return the current page's content object, or create one if necessary.
     *
     * @return int
     */
    protected function _getContentObject()
    {

        // If the page's current content object index is not set, create one.
        if (is_null($this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->curContent)) {

            $coi = $this->_lastIndex($this->_objects) + 1;
            $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->content[] = $coi;
            $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->curContent = $coi;
            $this->_objects[$coi] = new Moc10_Pdf_Object($coi);

        } else {

            // Else, set and return the page's current content object index.
            $coi = $this->_objects[$this->_objects[$this->_pages[$this->_curPage]]->index]->curContent;

        }

        return $coi;

    }

    /**
     * Method to calculate text matrix.
     *
     * @return string
     */
    protected function _calcTextMatrix()
    {

        // Define some variables.
        $tm = '';
        $a = '';
        $b = '';
        $c = '';
        $d = '';
        $neg = null;

        // Determine is the rotate parameter is negative or not.
        $neg = ($this->_textParams['rot'] < 0) ? true : false;

        // Calculate the text matrix parameters.
        $rot = abs($this->_textParams['rot']);

        if (($rot >= 0) && ($rot <= 45)) {
            $factor = round(($rot / 45), 2);
            if ($neg) {
                $a = 1;
                $b = '-' . $factor;
                $c = $factor;
                $d = 1;
            } else {
                $a = 1;
                $b = $factor;
                $c = '-' . $factor;
                $d = 1;
            }
        } else if (($rot > 45) && ($rot <= 90)) {
            $factor = round(((90 - $rot) / 45), 2);
            if ($neg) {
                $a = $factor;
                $b = -1;
                $c = 1;
                $d = $factor;
            } else {
                $a = $factor;
                $b = 1;
                $c = -1;
                $d = $factor;
            }
        }

        // Adjust the text matrix parameters according to the horizontal and vertical scale parameters.
        if ($this->_textParams['h'] != 100) {
            $a = round(($a * ($this->_textParams['h'] / 100)), 2);
            $b = round(($b * ($this->_textParams['h'] / 100)), 2);
        }

        if ($this->_textParams['v'] != 100) {
            $c = round(($c * ($this->_textParams['v'] / 100)), 2);
            $d = round(($d * ($this->_textParams['v'] / 100)), 2);
        }

        // Set the text matrix and return it.
        $tm = "{$a} {$b} {$c} {$d}";

        return $tm;

    }

    /**
     * Method to calculate byte length.
     *
     * @param  string $str
     * @return int
     */
    protected function _calcByteLength($str)
    {

        $bytes = str_replace("\n", "", $str);
        return strlen($bytes);

    }

    /**
     * Method to format byte length.
     *
     * @param  int|string $num
     * @return string
     */
    protected function _formatByteLength($num)
    {

        return sprintf('%010d', $num);

    }

    /**
     * Method to convert color.
     *
     * @param  int|string $color
     * @return float
     */
    protected function _convertColor($color)
    {

        $c = round(($color / 256), 2);
        return $c;

    }

    /**
     * Method to return the last object index.
     *
     * @param  array $arr
     * @throws Exception
     * @return int
     */
    protected function _lastIndex($arr)
    {

        if (!is_array($arr)) {
            throw new Exception($this->_lang->__('Error: The argument passed must be an array.'));
        } else {
            $objs = array_keys($arr);
            sort($objs);

            foreach ($objs as $value) {
                $last = $value;
            }

            return $last;
        }

    }

}
Return current item: Moc10 PHP Library