Guest User

Pdf.php

a guest
Dec 15th, 2014
237
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 52.69 KB | None | 0 0
  1. <?php
  2. /**
  3.  * Zend Framework
  4.  *
  5.  * LICENSE
  6.  *
  7.  * This source file is subject to the new BSD license that is bundled
  8.  * with this package in the file LICENSE.txt.
  9.  * It is also available through the world-wide-web at this URL:
  10.  * http://framework.zend.com/license/new-bsd
  11.  * If you did not receive a copy of the license and are unable to
  12.  * obtain it through the world-wide-web, please send an email
  13.  * to [email protected] so we can send you a copy immediately.
  14.  *
  15.  * @category   Zend
  16.  * @package    Zend_Pdf
  17.  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  18.  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  19.  * @version    $Id: Pdf.php 24593 2012-01-05 20:35:02Z matthew $
  20.  */
  21.  
  22.  
  23. /** User land classes and interfaces turned on by Zend/Pdf.php file inclusion. */
  24. /** @todo Section should be removed with ZF 2.0 release as obsolete            */
  25.  
  26. /** Zend_Pdf_Page */
  27. require_once 'Zend/Pdf/Page.php';
  28.  
  29. /** Zend_Pdf_Style */
  30. require_once 'Zend/Pdf/Style.php';
  31.  
  32. /** Zend_Pdf_Color_GrayScale */
  33. require_once 'Zend/Pdf/Color/GrayScale.php';
  34.  
  35. /** Zend_Pdf_Color_Rgb */
  36. require_once 'Zend/Pdf/Color/Rgb.php';
  37.  
  38. /** Zend_Pdf_Color_Cmyk */
  39. require_once 'Zend/Pdf/Color/Cmyk.php';
  40.  
  41. /** Zend_Pdf_Color_Html */
  42. require_once 'Zend/Pdf/Color/Html.php';
  43.  
  44. /** Zend_Pdf_Image */
  45. require_once 'Zend/Pdf/Image.php';
  46.  
  47. /** Zend_Pdf_Font */
  48. require_once 'Zend/Pdf/Font.php';
  49.  
  50. /** Zend_Pdf_Resource_Extractor */
  51. require_once 'Zend/Pdf/Resource/Extractor.php';
  52.  
  53. /** Zend_Pdf_Canvas */
  54. require_once 'Zend/Pdf/Canvas.php';
  55.  
  56.  
  57. /** Internally used classes */
  58. require_once 'Zend/Pdf/Element.php';
  59. require_once 'Zend/Pdf/Element/Array.php';
  60. require_once 'Zend/Pdf/Element/String/Binary.php';
  61. require_once 'Zend/Pdf/Element/Boolean.php';
  62. require_once 'Zend/Pdf/Element/Dictionary.php';
  63. require_once 'Zend/Pdf/Element/Name.php';
  64. require_once 'Zend/Pdf/Element/Null.php';
  65. require_once 'Zend/Pdf/Element/Numeric.php';
  66. require_once 'Zend/Pdf/Element/String.php';
  67.  
  68.  
  69. /**
  70.  * General entity which describes PDF document.
  71.  * It implements document abstraction with a document level operations.
  72.  *
  73.  * Class is used to create new PDF document or load existing document.
  74.  * See details in a class constructor description
  75.  *
  76.  * Class agregates document level properties and entities (pages, bookmarks,
  77.  * document level actions, attachments, form object, etc)
  78.  *
  79.  * @category   Zend
  80.  * @package    Zend_Pdf
  81.  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  82.  * @license    http://framework.zend.com/license/new-bsd     New BSD License
  83.  */
  84. class Zend_Pdf
  85. {
  86.   /**** Class Constants ****/
  87.  
  88.     /**
  89.      * Version number of generated PDF documents.
  90.      */
  91.     const PDF_VERSION = '1.4';
  92.  
  93.     /**
  94.      * PDF file header.
  95.      */
  96.     const PDF_HEADER  = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
  97.  
  98.  
  99.  
  100.     /**
  101.      * Pages collection
  102.      *
  103.      * @todo implement it as a class, which supports ArrayAccess and Iterator interfaces,
  104.      *       to provide incremental parsing and pages tree updating.
  105.      *       That will give good performance and memory (PDF size) benefits.
  106.      *
  107.      * @var array   - array of Zend_Pdf_Page object
  108.      */
  109.     public $pages = array();
  110.  
  111.     /**
  112.      * Document properties
  113.      *
  114.      * It's an associative array with PDF meta information, values may
  115.      * be string, boolean or float.
  116.      * Returned array could be used directly to access, add, modify or remove
  117.      * document properties.
  118.      *
  119.      * Standard document properties: Title (must be set for PDF/X documents), Author,
  120.      * Subject, Keywords (comma separated list), Creator (the name of the application,
  121.      * that created document, if it was converted from other format), Trapped (must be
  122.      * true, false or null, can not be null for PDF/X documents)
  123.      *
  124.      * @var array
  125.      */
  126.     public $properties = array();
  127.  
  128.     /**
  129.      * Original properties set.
  130.      *
  131.      * Used for tracking properties changes
  132.      *
  133.      * @var array
  134.      */
  135.     protected $_originalProperties = array();
  136.  
  137.     /**
  138.      * Document level javascript
  139.      *
  140.      * @var string
  141.      */
  142.     protected $_javaScript = null;
  143.  
  144.     /**
  145.      * Document named destinations or "GoTo..." actions, used to refer
  146.      * document parts from outside PDF
  147.      *
  148.      * @var array   - array of Zend_Pdf_Target objects
  149.      */
  150.     protected $_namedTargets = array();
  151.  
  152.     /**
  153.      * Document outlines
  154.      *
  155.      * @var array - array of Zend_Pdf_Outline objects
  156.      */
  157.     public $outlines = array();
  158.  
  159.     /**
  160.      * Original document outlines list
  161.      * Used to track outlines update
  162.      *
  163.      * @var array - array of Zend_Pdf_Outline objects
  164.      */
  165.     protected $_originalOutlines = array();
  166.  
  167.     /**
  168.      * Original document outlines open elements count
  169.      * Used to track outlines update
  170.      *
  171.      * @var integer
  172.      */
  173.     protected $_originalOpenOutlinesCount = 0;
  174.  
  175.     /**
  176.      * Pdf trailer (last or just created)
  177.      *
  178.      * @var Zend_Pdf_Trailer
  179.      */
  180.     protected $_trailer = null;
  181.  
  182.     /**
  183.      * PDF objects factory.
  184.      *
  185.      * @var Zend_Pdf_ElementFactory_Interface
  186.      */
  187.     protected $_objFactory = null;
  188.  
  189.     /**
  190.      * Memory manager for stream objects
  191.      *
  192.      * @var Zend_Memory_Manager|null
  193.      */
  194.     protected static $_memoryManager = null;
  195.  
  196.     /**
  197.      * Pdf file parser.
  198.      * It's not used, but has to be destroyed only with Zend_Pdf object
  199.      *
  200.      * @var Zend_Pdf_Parser
  201.      */
  202.     protected $_parser;
  203.  
  204.  
  205.     /**
  206.      * List of inheritable attributesfor pages tree
  207.      *
  208.      * @var array
  209.      */
  210.     protected static $_inheritableAttributes = array('Resources', 'MediaBox', 'CropBox', 'Rotate');
  211.    
  212.     /**
  213.      * List of form fields
  214.      *
  215.      * @var array - Associative array, key: name of form field, value: Zend_Pdf_Element
  216.      */
  217.     protected $_formFields = array();
  218.  
  219.     /**
  220.      * True if the object is a newly created PDF document (affects save() method behavior)
  221.      * False otherwise
  222.      *
  223.      * @var boolean
  224.      */
  225.     protected $_isNewDocument = true;
  226.  
  227.     /**
  228.      * Request used memory manager
  229.      *
  230.      * @return Zend_Memory_Manager
  231.      */
  232.     static public function getMemoryManager()
  233.     {
  234.         if (self::$_memoryManager === null) {
  235.             require_once 'Zend/Memory.php';
  236.             self::$_memoryManager = Zend_Memory::factory('none');
  237.         }
  238.  
  239.         return self::$_memoryManager;
  240.     }
  241.  
  242.     /**
  243.      * Set user defined memory manager
  244.      *
  245.      * @param Zend_Memory_Manager $memoryManager
  246.      */
  247.     static public function setMemoryManager(Zend_Memory_Manager $memoryManager)
  248.     {
  249.         self::$_memoryManager = $memoryManager;
  250.     }
  251.  
  252.  
  253.     /**
  254.      * Create new PDF document from a $source string
  255.      *
  256.      * @param string $source
  257.      * @param integer $revision
  258.      * @return Zend_Pdf
  259.      */
  260.     public static function parse(&$source = null, $revision = null)
  261.     {
  262.         return new Zend_Pdf($source, $revision);
  263.     }
  264.  
  265.     /**
  266.      * Load PDF document from a file
  267.      *
  268.      * @param string $source
  269.      * @param integer $revision
  270.      * @return Zend_Pdf
  271.      */
  272.     public static function load($source = null, $revision = null)
  273.     {
  274.         return new Zend_Pdf($source, $revision, true);
  275.     }
  276.  
  277.     /**
  278.      * Render PDF document and save it.
  279.      *
  280.      * If $updateOnly is true and it's not a new document, then it only
  281.      * appends new section to the end of file.
  282.      *
  283.      * @param string $filename
  284.      * @param boolean $updateOnly
  285.      * @throws Zend_Pdf_Exception
  286.      */
  287.     public function save($filename, $updateOnly = false)
  288.     {
  289.         if (($file = @fopen($filename, $updateOnly ? 'ab':'wb')) === false ) {
  290.             require_once 'Zend/Pdf/Exception.php';
  291.             throw new Zend_Pdf_Exception( "Can not open '$filename' file for writing." );
  292.         }
  293.  
  294.         $this->render($updateOnly, $file);
  295.  
  296.         fclose($file);
  297.     }
  298.  
  299.     /**
  300.      * Creates or loads PDF document.
  301.      *
  302.      * If $source is null, then it creates a new document.
  303.      *
  304.      * If $source is a string and $load is false, then it loads document
  305.      * from a binary string.
  306.      *
  307.      * If $source is a string and $load is true, then it loads document
  308.      * from a file.
  309.  
  310.      * $revision used to roll back document to specified version
  311.      * (0 - current version, 1 - previous version, 2 - ...)
  312.      *
  313.      * @param string  $source - PDF file to load
  314.      * @param integer $revision
  315.      * @throws Zend_Pdf_Exception
  316.      * @return Zend_Pdf
  317.      */
  318.     public function __construct($source = null, $revision = null, $load = false)
  319.     {
  320.         require_once 'Zend/Pdf/ElementFactory.php';
  321.         $this->_objFactory = Zend_Pdf_ElementFactory::createFactory(1);
  322.  
  323.         if ($source !== null) {
  324.             require_once 'Zend/Pdf/Parser.php';
  325.             $this->_parser           = new Zend_Pdf_Parser($source, $this->_objFactory, $load);
  326.             $this->_pdfHeaderVersion = $this->_parser->getPDFVersion();
  327.             $this->_trailer          = $this->_parser->getTrailer();
  328.             if ($this->_trailer->Encrypt !== null) {
  329.                 require_once 'Zend/Pdf/Exception.php';
  330.                 throw new Zend_Pdf_Exception('Encrypted document modification is not supported');
  331.             }
  332.             if ($revision !== null) {
  333.                 $this->rollback($revision);
  334.             } else {
  335.                 $this->_loadPages($this->_trailer->Root->Pages);
  336.             }
  337.  
  338.             $this->_loadNamedDestinations($this->_trailer->Root, $this->_parser->getPDFVersion());
  339.             $this->_loadOutlines($this->_trailer->Root);
  340.             $this->_loadFormfields($this->_trailer->Root);
  341.  
  342.             if ($this->_trailer->Info !== null) {
  343.                 $this->properties = $this->_trailer->Info->toPhp();
  344.  
  345.                 if (isset($this->properties['Trapped'])) {
  346.                     switch ($this->properties['Trapped']) {
  347.                         case 'True':
  348.                             $this->properties['Trapped'] = true;
  349.                             break;
  350.  
  351.                         case 'False':
  352.                             $this->properties['Trapped'] = false;
  353.                             break;
  354.  
  355.                         case 'Unknown':
  356.                             $this->properties['Trapped'] = null;
  357.                             break;
  358.  
  359.                         default:
  360.                             // Wrong property value
  361.                             // Do nothing
  362.                             break;
  363.                     }
  364.                 }
  365.  
  366.                 $this->_originalProperties = $this->properties;
  367.             }
  368.  
  369.             $this->_isNewDocument = false;
  370.         } else {
  371.             $this->_pdfHeaderVersion = Zend_Pdf::PDF_VERSION;
  372.  
  373.             $trailerDictionary = new Zend_Pdf_Element_Dictionary();
  374.  
  375.             /**
  376.              * Document id
  377.              */
  378.             $docId = md5(uniqid(rand(), true));   // 32 byte (128 bit) identifier
  379.             $docIdLow  = substr($docId,  0, 16);  // first 16 bytes
  380.             $docIdHigh = substr($docId, 16, 16);  // second 16 bytes
  381.  
  382.             $trailerDictionary->ID = new Zend_Pdf_Element_Array();
  383.             $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdLow);
  384.             $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdHigh);
  385.  
  386.             $trailerDictionary->Size = new Zend_Pdf_Element_Numeric(0);
  387.  
  388.             require_once 'Zend/Pdf/Trailer/Generator.php';
  389.             $this->_trailer = new Zend_Pdf_Trailer_Generator($trailerDictionary);
  390.  
  391.             /**
  392.              * Document catalog indirect object.
  393.              */
  394.             $docCatalog = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
  395.             $docCatalog->Type    = new Zend_Pdf_Element_Name('Catalog');
  396.             $docCatalog->Version = new Zend_Pdf_Element_Name(Zend_Pdf::PDF_VERSION);
  397.             $this->_trailer->Root = $docCatalog;
  398.  
  399.             /**
  400.              * Pages container
  401.              */
  402.             $docPages = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
  403.             $docPages->Type  = new Zend_Pdf_Element_Name('Pages');
  404.             $docPages->Kids  = new Zend_Pdf_Element_Array();
  405.             $docPages->Count = new Zend_Pdf_Element_Numeric(0);
  406.             $docCatalog->Pages = $docPages;
  407.         }
  408.     }
  409.  
  410.     /**
  411.      * Retrive number of revisions.
  412.      *
  413.      * @return integer
  414.      */
  415.     public function revisions()
  416.     {
  417.         $revisions = 1;
  418.         $currentTrailer = $this->_trailer;
  419.  
  420.         while ($currentTrailer->getPrev() !== null && $currentTrailer->getPrev()->Root !== null ) {
  421.             $revisions++;
  422.             $currentTrailer = $currentTrailer->getPrev();
  423.         }
  424.  
  425.         return $revisions++;
  426.     }
  427.  
  428.     /**
  429.      * Rollback document $steps number of revisions.
  430.      * This method must be invoked before any changes, applied to the document.
  431.      * Otherwise behavior is undefined.
  432.      *
  433.      * @param integer $steps
  434.      */
  435.     public function rollback($steps)
  436.     {
  437.         for ($count = 0; $count < $steps; $count++) {
  438.             if ($this->_trailer->getPrev() !== null && $this->_trailer->getPrev()->Root !== null) {
  439.                 $this->_trailer = $this->_trailer->getPrev();
  440.             } else {
  441.                 break;
  442.             }
  443.         }
  444.         $this->_objFactory->setObjectCount($this->_trailer->Size->value);
  445.  
  446.         // Mark content as modified to force new trailer generation at render time
  447.         $this->_trailer->Root->touch();
  448.  
  449.         $this->pages = array();
  450.         $this->_loadPages($this->_trailer->Root->Pages);
  451.     }
  452.  
  453.  
  454.     /**
  455.      * Load pages recursively
  456.      *
  457.      * @param Zend_Pdf_Element_Reference $pages
  458.      * @param array|null $attributes
  459.      */
  460.     protected function _loadPages(Zend_Pdf_Element_Reference $pages, $attributes = array())
  461.     {
  462.         if ($pages->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
  463.             require_once 'Zend/Pdf/Exception.php';
  464.             throw new Zend_Pdf_Exception('Wrong argument');
  465.         }
  466.  
  467.         foreach ($pages->getKeys() as $property) {
  468.             if (in_array($property, self::$_inheritableAttributes)) {
  469.                 $attributes[$property] = $pages->$property;
  470.                 $pages->$property = null;
  471.             }
  472.         }
  473.  
  474.  
  475.         foreach ($pages->Kids->items as $child) {
  476.             if ($child->Type->value == 'Pages') {
  477.                 $this->_loadPages($child, $attributes);
  478.             } else if ($child->Type->value == 'Page') {
  479.                 foreach (self::$_inheritableAttributes as $property) {
  480.                     if ($child->$property === null && array_key_exists($property, $attributes)) {
  481.                         /**
  482.                          * Important note.
  483.                          * If any attribute or dependant object is an indirect object, then it's still
  484.                          * shared between pages.
  485.                          */
  486.                         if ($attributes[$property] instanceof Zend_Pdf_Element_Object  ||
  487.                             $attributes[$property] instanceof Zend_Pdf_Element_Reference) {
  488.                             $child->$property = $attributes[$property];
  489.                         } else {
  490.                             $child->$property = $this->_objFactory->newObject($attributes[$property]);
  491.                         }
  492.                     }
  493.                 }
  494.  
  495.                 require_once 'Zend/Pdf/Page.php';
  496.                 $this->pages[] = new Zend_Pdf_Page($child, $this->_objFactory);
  497.             }
  498.         }
  499.     }
  500.  
  501.     /**
  502.      * Load named destinations recursively
  503.      *
  504.      * @param Zend_Pdf_Element_Reference $root Document catalog entry
  505.      * @param string $pdfHeaderVersion
  506.      * @throws Zend_Pdf_Exception
  507.      */
  508.     protected function _loadNamedDestinations(Zend_Pdf_Element_Reference $root, $pdfHeaderVersion)
  509.     {
  510.         if ($root->Version !== null  &&  version_compare($root->Version->value, $pdfHeaderVersion, '>')) {
  511.             $versionIs_1_2_plus = version_compare($root->Version->value,    '1.1', '>');
  512.         } else {
  513.             $versionIs_1_2_plus = version_compare($pdfHeaderVersion, '1.1', '>');
  514.         }
  515.  
  516.         if ($versionIs_1_2_plus) {
  517.             // PDF version is 1.2+
  518.             // Look for Destinations structure at Name dictionary
  519.             if ($root->Names !== null  &&  $root->Names->Dests !== null) {
  520.                 require_once 'Zend/Pdf/NameTree.php';
  521.                 require_once 'Zend/Pdf/Target.php';
  522.                 foreach (new Zend_Pdf_NameTree($root->Names->Dests) as $name => $destination) {
  523.                     $this->_namedTargets[$name] = Zend_Pdf_Target::load($destination);
  524.                 }
  525.             }
  526.         } else {
  527.             // PDF version is 1.1 (or earlier)
  528.             // Look for Destinations sructure at Dest entry of document catalog
  529.             if ($root->Dests !== null) {
  530.                 if ($root->Dests->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
  531.                     require_once 'Zend/Pdf/Exception.php';
  532.                     throw new Zend_Pdf_Exception('Document catalog Dests entry must be a dictionary.');
  533.                 }
  534.  
  535.                 require_once 'Zend/Pdf/Target.php';
  536.                 foreach ($root->Dests->getKeys() as $destKey) {
  537.                     $this->_namedTargets[$destKey] = Zend_Pdf_Target::load($root->Dests->$destKey);
  538.                 }
  539.             }
  540.         }
  541.     }
  542.  
  543.     /**
  544.      * Load outlines recursively
  545.      *
  546.      * @param Zend_Pdf_Element_Reference $root Document catalog entry
  547.      */
  548.     protected function _loadOutlines(Zend_Pdf_Element_Reference $root)
  549.     {
  550.         if ($root->Outlines === null) {
  551.             return;
  552.         }
  553.  
  554.         if ($root->Outlines->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
  555.             require_once 'Zend/Pdf/Exception.php';
  556.             throw new Zend_Pdf_Exception('Document catalog Outlines entry must be a dictionary.');
  557.         }
  558.  
  559.         if ($root->Outlines->Type !== null  &&  $root->Outlines->Type->value != 'Outlines') {
  560.             require_once 'Zend/Pdf/Exception.php';
  561.             throw new Zend_Pdf_Exception('Outlines Type entry must be an \'Outlines\' string.');
  562.         }
  563.  
  564.         if ($root->Outlines->First === null) {
  565.             return;
  566.         }
  567.  
  568.         $outlineDictionary = $root->Outlines->First;
  569.         $processedDictionaries = new SplObjectStorage();
  570.         while ($outlineDictionary !== null  &&  !$processedDictionaries->contains($outlineDictionary)) {
  571.             $processedDictionaries->attach($outlineDictionary);
  572.  
  573.             require_once 'Zend/Pdf/Outline/Loaded.php';
  574.             $this->outlines[] = new Zend_Pdf_Outline_Loaded($outlineDictionary);
  575.  
  576.             $outlineDictionary = $outlineDictionary->Next;
  577.         }
  578.  
  579.         $this->_originalOutlines = $this->outlines;
  580.  
  581.         if ($root->Outlines->Count !== null) {
  582.             $this->_originalOpenOutlinesCount = $root->Outlines->Count->value;
  583.         }
  584.     }
  585.    
  586.     /**
  587.      * Load form fields
  588.      * Populates the _formFields array, for later lookup of fields by name
  589.      *
  590.      * @param Zend_Pdf_Element_Reference $root Document catalog entry
  591.      */
  592.     protected function _loadFormFields(Zend_Pdf_Element_Reference $root)
  593.     {
  594.       if ($root->AcroForm === null || $root->AcroForm->Fields === null) {
  595.         return;
  596.       }
  597.      
  598.       foreach ($root->AcroForm->Fields->items as $field)
  599.       {
  600.           if ( $field->FT->value == 'Tx' && $field->T !== null ) /* We only support fields that are textfields and have a name */
  601.           {
  602.               $this->_formFields[$field->T->value] = $field;
  603.           }
  604.       }
  605.      
  606.       if ( !$root->AcroForm->NeedAppearances || !$root->AcroForm->NeedAppearances->value )
  607.       {
  608.         /* Ask the .pdf viewer to generate its own appearance data, so we do not have to */
  609.         $root->AcroForm->add(new Zend_Pdf_Element_Name('NeedAppearances'), new Zend_Pdf_Element_Boolean(true) );
  610.         $root->AcroForm->touch();
  611.       }
  612.     }
  613.    
  614.     /**
  615.      * Retrieves a list with the names of the AcroForm textfields in the PDF
  616.      *
  617.      * @return array of strings
  618.      */
  619.     public function getTextFieldNames()
  620.     {
  621.       return array_keys($this->_formFields);
  622.     }
  623.    
  624.     /**
  625.      * Sets the value of an AcroForm text field
  626.      *
  627.      * @param string $name Name of textfield
  628.      * @param string $value Value
  629.      * @throws Zend_Pdf_Exception if the textfield does not exist in the pdf
  630.      */
  631.     public function setTextField($name, $value)
  632.     {
  633.       if ( !isset($this->_formFields[$name]))
  634.         throw new Zend_Pdf_Exception("Field '$name' does not exist or is not a textfield");
  635.      
  636.       $field = $this->_formFields[$name];
  637.       $detected = mb_detect_encoding($value);
  638.       $value = mb_convert_encoding($value, 'UTF-8', $detected);
  639.       $field->add(new Zend_Pdf_Element_Name('V'), new Zend_Pdf_Element_String($value) );
  640.       $field->touch();      
  641.     }
  642.  
  643.     /**
  644.      * Orginize pages to tha pages tree structure.
  645.      *
  646.      * @todo atomatically attach page to the document, if it's not done yet.
  647.      * @todo check, that page is attached to the current document
  648.      *
  649.      * @todo Dump pages as a balanced tree instead of a plain set.
  650.      */
  651.     protected function _dumpPages()
  652.     {
  653.         $root = $this->_trailer->Root;
  654.         $pagesContainer = $root->Pages;
  655.  
  656.         $pagesContainer->touch();
  657.         $pagesContainer->Kids->items = array();
  658.  
  659.         foreach ($this->pages as $page ) {
  660.             $page->render($this->_objFactory);
  661.  
  662.             $pageDictionary = $page->getPageDictionary();
  663.             $pageDictionary->touch();
  664.             $pageDictionary->Parent = $pagesContainer;
  665.  
  666.             $pagesContainer->Kids->items[] = $pageDictionary;
  667.         }
  668.  
  669.         $this->_refreshPagesHash();
  670.  
  671.         $pagesContainer->Count->touch();
  672.         $pagesContainer->Count->value = count($this->pages);
  673.  
  674.  
  675.         // Refresh named destinations list
  676.         foreach ($this->_namedTargets as $name => $namedTarget) {
  677.             if ($namedTarget instanceof Zend_Pdf_Destination_Explicit) {
  678.                 // Named target is an explicit destination
  679.                 if ($this->resolveDestination($namedTarget, false) === null) {
  680.                     unset($this->_namedTargets[$name]);
  681.                 }
  682.             } else if ($namedTarget instanceof Zend_Pdf_Action) {
  683.                 // Named target is an action
  684.                 if ($this->_cleanUpAction($namedTarget, false) === null) {
  685.                     // Action is a GoTo action with an unresolved destination
  686.                     unset($this->_namedTargets[$name]);
  687.                 }
  688.             } else {
  689.                 require_once 'Zend/Pdf/Exception.php';
  690.                 throw new Zend_Pdf_Exception('Wrong type of named targed (\'' . get_class($namedTarget) . '\').');
  691.             }
  692.         }
  693.  
  694.         // Refresh outlines
  695.         require_once 'Zend/Pdf/RecursivelyIteratableObjectsContainer.php';
  696.         $iterator = new RecursiveIteratorIterator(new Zend_Pdf_RecursivelyIteratableObjectsContainer($this->outlines), RecursiveIteratorIterator::SELF_FIRST);
  697.         foreach ($iterator as $outline) {
  698.             $target = $outline->getTarget();
  699.  
  700.             if ($target !== null) {
  701.                 if ($target instanceof Zend_Pdf_Destination) {
  702.                     // Outline target is a destination
  703.                     if ($this->resolveDestination($target, false) === null) {
  704.                         $outline->setTarget(null);
  705.                     }
  706.                 } else if ($target instanceof Zend_Pdf_Action) {
  707.                     // Outline target is an action
  708.                     if ($this->_cleanUpAction($target, false) === null) {
  709.                         // Action is a GoTo action with an unresolved destination
  710.                         $outline->setTarget(null);
  711.                     }
  712.                 } else {
  713.                     require_once 'Zend/Pdf/Exception.php';
  714.                     throw new Zend_Pdf_Exception('Wrong outline target.');
  715.                 }
  716.             }
  717.         }
  718.  
  719.         $openAction = $this->getOpenAction();
  720.         if ($openAction !== null) {
  721.             if ($openAction instanceof Zend_Pdf_Action) {
  722.                 // OpenAction is an action
  723.                 if ($this->_cleanUpAction($openAction, false) === null) {
  724.                     // Action is a GoTo action with an unresolved destination
  725.                     $this->setOpenAction(null);
  726.                 }
  727.             } else if ($openAction instanceof Zend_Pdf_Destination) {
  728.                 // OpenAction target is a destination
  729.                 if ($this->resolveDestination($openAction, false) === null) {
  730.                     $this->setOpenAction(null);
  731.                 }
  732.             } else {
  733.                 require_once 'Zend/Pdf/Exception.php';
  734.                 throw new Zend_Pdf_Exception('OpenAction has to be either PDF Action or Destination.');
  735.             }
  736.         }
  737.     }
  738.  
  739.     /**
  740.      * Dump named destinations
  741.      *
  742.      * @todo Create a balanced tree instead of plain structure.
  743.      */
  744.     protected function _dumpNamedDestinations()
  745.     {
  746.         ksort($this->_namedTargets, SORT_STRING);
  747.  
  748.         $destArrayItems = array();
  749.         foreach ($this->_namedTargets as $name => $destination) {
  750.             $destArrayItems[] = new Zend_Pdf_Element_String($name);
  751.  
  752.             if ($destination instanceof Zend_Pdf_Target) {
  753.                 $destArrayItems[] = $destination->getResource();
  754.             } else {
  755.                 require_once 'Zend/Pdf/Exception.php';
  756.                 throw new Zend_Pdf_Exception('PDF named destinations must be a Zend_Pdf_Target object.');
  757.             }
  758.         }
  759.         $destArray = $this->_objFactory->newObject(new Zend_Pdf_Element_Array($destArrayItems));
  760.  
  761.         $DestTree = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
  762.         $DestTree->Names = $destArray;
  763.  
  764.         $root = $this->_trailer->Root;
  765.  
  766.         if ($root->Names === null) {
  767.             $root->touch();
  768.             $root->Names = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
  769.         } else {
  770.             $root->Names->touch();
  771.         }
  772.         $root->Names->Dests = $DestTree;
  773.     }
  774.  
  775.     /**
  776.      * Dump outlines recursively
  777.      */
  778.     protected function _dumpOutlines()
  779.     {
  780.         $root = $this->_trailer->Root;
  781.  
  782.         if ($root->Outlines === null) {
  783.             if (count($this->outlines) == 0) {
  784.                 return;
  785.             } else {
  786.                 $root->Outlines = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
  787.                 $root->Outlines->Type = new Zend_Pdf_Element_Name('Outlines');
  788.                 $updateOutlinesNavigation = true;
  789.             }
  790.         } else {
  791.             $updateOutlinesNavigation = false;
  792.             if (count($this->_originalOutlines) != count($this->outlines)) {
  793.                 // If original and current outlines arrays have different size then outlines list was updated
  794.                 $updateOutlinesNavigation = true;
  795.             } else if ( !(array_keys($this->_originalOutlines) === array_keys($this->outlines)) ) {
  796.                 // If original and current outlines arrays have different keys (with a glance to an order) then outlines list was updated
  797.                 $updateOutlinesNavigation = true;
  798.             } else {
  799.                 foreach ($this->outlines as $key => $outline) {
  800.                     if ($this->_originalOutlines[$key] !== $outline) {
  801.                         $updateOutlinesNavigation = true;
  802.                     }
  803.                 }
  804.             }
  805.         }
  806.  
  807.         $lastOutline = null;
  808.         $openOutlinesCount = 0;
  809.         if ($updateOutlinesNavigation) {
  810.             $root->Outlines->touch();
  811.             $root->Outlines->First = null;
  812.  
  813.             foreach ($this->outlines as $outline) {
  814.                 if ($lastOutline === null) {
  815.                     // First pass. Update Outlines dictionary First entry using corresponding value
  816.                     $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines);
  817.                     $root->Outlines->First = $lastOutline;
  818.                 } else {
  819.                     // Update previous outline dictionary Next entry (Prev is updated within dumpOutline() method)
  820.                     $currentOutlineDictionary = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline);
  821.                     $lastOutline->Next = $currentOutlineDictionary;
  822.                     $lastOutline       = $currentOutlineDictionary;
  823.                 }
  824.                 $openOutlinesCount += $outline->openOutlinesCount();
  825.             }
  826.  
  827.             $root->Outlines->Last  = $lastOutline;
  828.         } else {
  829.             foreach ($this->outlines as $outline) {
  830.                 $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline);
  831.                 $openOutlinesCount += $outline->openOutlinesCount();
  832.             }
  833.         }
  834.  
  835.         if ($openOutlinesCount != $this->_originalOpenOutlinesCount) {
  836.             $root->Outlines->touch;
  837.             $root->Outlines->Count = new Zend_Pdf_Element_Numeric($openOutlinesCount);
  838.         }
  839.     }
  840.  
  841.     /**
  842.      * Create page object, attached to the PDF document.
  843.      * Method signatures:
  844.      *
  845.      * 1. Create new page with a specified pagesize.
  846.      *    If $factory is null then it will be created and page must be attached to the document to be
  847.      *    included into output.
  848.      * ---------------------------------------------------------
  849.      * new Zend_Pdf_Page(string $pagesize);
  850.      * ---------------------------------------------------------
  851.      *
  852.      * 2. Create new page with a specified pagesize (in default user space units).
  853.      *    If $factory is null then it will be created and page must be attached to the document to be
  854.      *    included into output.
  855.      * ---------------------------------------------------------
  856.      * new Zend_Pdf_Page(numeric $width, numeric $height);
  857.      * ---------------------------------------------------------
  858.      *
  859.      * @param mixed $param1
  860.      * @param mixed $param2
  861.      * @return Zend_Pdf_Page
  862.      */
  863.     public function newPage($param1, $param2 = null)
  864.     {
  865.         require_once 'Zend/Pdf/Page.php';
  866.         if ($param2 === null) {
  867.             return new Zend_Pdf_Page($param1, $this->_objFactory);
  868.         } else {
  869.             return new Zend_Pdf_Page($param1, $param2, $this->_objFactory);
  870.         }
  871.     }
  872.  
  873.     /**
  874.      * Return the document-level Metadata
  875.      * or null Metadata stream is not presented
  876.      *
  877.      * @return string
  878.      */
  879.     public function getMetadata()
  880.     {
  881.         if ($this->_trailer->Root->Metadata !== null) {
  882.             return $this->_trailer->Root->Metadata->value;
  883.         } else {
  884.             return null;
  885.         }
  886.     }
  887.  
  888.     /**
  889.      * Sets the document-level Metadata (mast be valid XMP document)
  890.      *
  891.      * @param string $metadata
  892.      */
  893.     public function setMetadata($metadata)
  894.     {
  895.         $metadataObject = $this->_objFactory->newStreamObject($metadata);
  896.         $metadataObject->dictionary->Type    = new Zend_Pdf_Element_Name('Metadata');
  897.         $metadataObject->dictionary->Subtype = new Zend_Pdf_Element_Name('XML');
  898.  
  899.         $this->_trailer->Root->Metadata = $metadataObject;
  900.         $this->_trailer->Root->touch();
  901.     }
  902.  
  903.     /**
  904.      * Return the document-level JavaScript
  905.      * or null if there is no JavaScript for this document
  906.      *
  907.      * @return string
  908.      */
  909.     public function getJavaScript()
  910.     {
  911.         return $this->_javaScript;
  912.     }
  913.  
  914.     /**
  915.      * Get open Action
  916.      * Returns Zend_Pdf_Target (Zend_Pdf_Destination or Zend_Pdf_Action object)
  917.      *
  918.      * @return Zend_Pdf_Target
  919.      */
  920.     public function getOpenAction()
  921.     {
  922.         if ($this->_trailer->Root->OpenAction !== null) {
  923.             require_once 'Zend/Pdf/Target.php';
  924.             return Zend_Pdf_Target::load($this->_trailer->Root->OpenAction);
  925.         } else {
  926.             return null;
  927.         }
  928.     }
  929.  
  930.     /**
  931.      * Set open Action which is actually Zend_Pdf_Destination or Zend_Pdf_Action object
  932.      *
  933.      * @param Zend_Pdf_Target $openAction
  934.      * @returns Zend_Pdf
  935.      */
  936.     public function setOpenAction(Zend_Pdf_Target $openAction = null)
  937.     {
  938.         $root = $this->_trailer->Root;
  939.         $root->touch();
  940.  
  941.         if ($openAction === null) {
  942.             $root->OpenAction = null;
  943.         } else {
  944.             $root->OpenAction = $openAction->getResource();
  945.  
  946.             if ($openAction instanceof Zend_Pdf_Action)  {
  947.                 $openAction->dumpAction($this->_objFactory);
  948.             }
  949.         }
  950.  
  951.         return $this;
  952.     }
  953.  
  954.     /**
  955.      * Return an associative array containing all the named destinations (or GoTo actions) in the PDF.
  956.      * Named targets can be used to reference from outside
  957.      * the PDF, ex: 'http://www.something.com/mydocument.pdf#MyAction'
  958.      *
  959.      * @return array
  960.      */
  961.     public function getNamedDestinations()
  962.     {
  963.         return $this->_namedTargets;
  964.     }
  965.  
  966.     /**
  967.      * Return specified named destination
  968.      *
  969.      * @param string $name
  970.      * @return Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo
  971.      */
  972.     public function getNamedDestination($name)
  973.     {
  974.         if (isset($this->_namedTargets[$name])) {
  975.             return $this->_namedTargets[$name];
  976.         } else {
  977.             return null;
  978.         }
  979.     }
  980.  
  981.     /**
  982.      * Set specified named destination
  983.      *
  984.      * @param string $name
  985.      * @param Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo $target
  986.      */
  987.     public function setNamedDestination($name, $destination = null)
  988.     {
  989.         if ($destination !== null  &&
  990.             !$destination instanceof Zend_Pdf_Action_GoTo  &&
  991.             !$destination instanceof Zend_Pdf_Destination_Explicit) {
  992.             require_once 'Zend/Pdf/Exception.php';
  993.             throw new Zend_Pdf_Exception('PDF named destination must refer an explicit destination or a GoTo PDF action.');
  994.         }
  995.  
  996.         if ($destination !== null) {
  997.            $this->_namedTargets[$name] = $destination;
  998.         } else {
  999.             unset($this->_namedTargets[$name]);
  1000.         }
  1001.     }
  1002.  
  1003.     /**
  1004.      * Pages collection hash:
  1005.      * <page dictionary object hash id> => Zend_Pdf_Page
  1006.      *
  1007.      * @var SplObjectStorage
  1008.      */
  1009.     protected $_pageReferences = null;
  1010.  
  1011.     /**
  1012.      * Pages collection hash:
  1013.      * <page number> => Zend_Pdf_Page
  1014.      *
  1015.      * @var array
  1016.      */
  1017.     protected $_pageNumbers = null;
  1018.  
  1019.     /**
  1020.      * Refresh page collection hashes
  1021.      *
  1022.      * @return Zend_Pdf
  1023.      */
  1024.     protected function _refreshPagesHash()
  1025.     {
  1026.         $this->_pageReferences = array();
  1027.         $this->_pageNumbers    = array();
  1028.         $count = 1;
  1029.         foreach ($this->pages as $page) {
  1030.             $pageDictionaryHashId = spl_object_hash($page->getPageDictionary()->getObject());
  1031.             $this->_pageReferences[$pageDictionaryHashId] = $page;
  1032.             $this->_pageNumbers[$count++]                 = $page;
  1033.         }
  1034.  
  1035.         return $this;
  1036.     }
  1037.  
  1038.     /**
  1039.      * Resolve destination.
  1040.      *
  1041.      * Returns Zend_Pdf_Page page object or null if destination is not found within PDF document.
  1042.      *
  1043.      * @param Zend_Pdf_Destination $destination  Destination to resolve
  1044.      * @param boolean $refreshPagesHash  Refresh page collection hashes before processing
  1045.      * @return Zend_Pdf_Page|null
  1046.      * @throws Zend_Pdf_Exception
  1047.      */
  1048.     public function resolveDestination(Zend_Pdf_Destination $destination, $refreshPageCollectionHashes = true)
  1049.     {
  1050.         if ($this->_pageReferences === null  ||  $refreshPageCollectionHashes) {
  1051.             $this->_refreshPagesHash();
  1052.         }
  1053.  
  1054.         if ($destination instanceof Zend_Pdf_Destination_Named) {
  1055.             if (!isset($this->_namedTargets[$destination->getName()])) {
  1056.                 return null;
  1057.             }
  1058.             $destination = $this->getNamedDestination($destination->getName());
  1059.  
  1060.             if ($destination instanceof Zend_Pdf_Action) {
  1061.                 if (!$destination instanceof Zend_Pdf_Action_GoTo) {
  1062.                     return null;
  1063.                 }
  1064.                 $destination = $destination->getDestination();
  1065.             }
  1066.  
  1067.             if (!$destination instanceof Zend_Pdf_Destination_Explicit) {
  1068.                 require_once 'Zend/Pdf/Exception.php';
  1069.                 throw new Zend_Pdf_Exception('Named destination target has to be an explicit destination.');
  1070.             }
  1071.         }
  1072.  
  1073.         // Named target is an explicit destination
  1074.         $pageElement = $destination->getResource()->items[0];
  1075.  
  1076.         if ($pageElement->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
  1077.             // Page reference is a PDF number
  1078.             if (!isset($this->_pageNumbers[$pageElement->value])) {
  1079.                 return null;
  1080.             }
  1081.  
  1082.             return $this->_pageNumbers[$pageElement->value];
  1083.         }
  1084.  
  1085.         // Page reference is a PDF page dictionary reference
  1086.         $pageDictionaryHashId = spl_object_hash($pageElement->getObject());
  1087.         if (!isset($this->_pageReferences[$pageDictionaryHashId])) {
  1088.             return null;
  1089.         }
  1090.         return $this->_pageReferences[$pageDictionaryHashId];
  1091.     }
  1092.  
  1093.     /**
  1094.      * Walk through action and its chained actions tree and remove nodes
  1095.      * if they are GoTo actions with an unresolved target.
  1096.      *
  1097.      * Returns null if root node is deleted or updated action overwise.
  1098.      *
  1099.      * @todo Give appropriate name and make method public
  1100.      *
  1101.      * @param Zend_Pdf_Action $action
  1102.      * @param boolean $refreshPagesHash  Refresh page collection hashes before processing
  1103.      * @return Zend_Pdf_Action|null
  1104.      */
  1105.     protected function _cleanUpAction(Zend_Pdf_Action $action, $refreshPageCollectionHashes = true)
  1106.     {
  1107.         if ($this->_pageReferences === null  ||  $refreshPageCollectionHashes) {
  1108.             $this->_refreshPagesHash();
  1109.         }
  1110.  
  1111.         // Named target is an action
  1112.         if ($action instanceof Zend_Pdf_Action_GoTo  &&
  1113.             $this->resolveDestination($action->getDestination(), false) === null) {
  1114.             // Action itself is a GoTo action with an unresolved destination
  1115.             return null;
  1116.         }
  1117.  
  1118.         // Walk through child actions
  1119.         $iterator = new RecursiveIteratorIterator($action, RecursiveIteratorIterator::SELF_FIRST);
  1120.  
  1121.         $actionsToClean        = array();
  1122.         $deletionCandidateKeys = array();
  1123.         foreach ($iterator as $chainedAction) {
  1124.             if ($chainedAction instanceof Zend_Pdf_Action_GoTo  &&
  1125.                 $this->resolveDestination($chainedAction->getDestination(), false) === null) {
  1126.                 // Some child action is a GoTo action with an unresolved destination
  1127.                 // Mark it as a candidate for deletion
  1128.                 $actionsToClean[]        = $iterator->getSubIterator();
  1129.                 $deletionCandidateKeys[] = $iterator->getSubIterator()->key();
  1130.             }
  1131.         }
  1132.         foreach ($actionsToClean as $id => $action) {
  1133.             unset($action->next[$deletionCandidateKeys[$id]]);
  1134.         }
  1135.  
  1136.         return $action;
  1137.     }
  1138.  
  1139.     /**
  1140.      * Extract fonts attached to the document
  1141.      *
  1142.      * returns array of Zend_Pdf_Resource_Font_Extracted objects
  1143.      *
  1144.      * @return array
  1145.      * @throws Zend_Pdf_Exception
  1146.      */
  1147.     public function extractFonts()
  1148.     {
  1149.         $fontResourcesUnique = array();
  1150.         foreach ($this->pages as $page) {
  1151.             $pageResources = $page->extractResources();
  1152.  
  1153.             if ($pageResources->Font === null) {
  1154.                 // Page doesn't contain have any font reference
  1155.                 continue;
  1156.             }
  1157.  
  1158.             $fontResources = $pageResources->Font;
  1159.  
  1160.             foreach ($fontResources->getKeys() as $fontResourceName) {
  1161.                 $fontDictionary = $fontResources->$fontResourceName;
  1162.  
  1163.                 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
  1164.                        $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
  1165.                     require_once 'Zend/Pdf/Exception.php';
  1166.                     throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
  1167.                 }
  1168.  
  1169.                 $fontResourcesUnique[spl_object_hash($fontDictionary->getObject())] = $fontDictionary;
  1170.             }
  1171.         }
  1172.  
  1173.         $fonts = array();
  1174.         require_once 'Zend/Pdf/Exception.php';
  1175.         foreach ($fontResourcesUnique as $resourceId => $fontDictionary) {
  1176.             try {
  1177.                 // Try to extract font
  1178.                 require_once 'Zend/Pdf/Resource/Font/Extracted.php';
  1179.                 $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
  1180.  
  1181.                 $fonts[$resourceId] = $extractedFont;
  1182.             } catch (Zend_Pdf_Exception $e) {
  1183.                 if ($e->getMessage() != 'Unsupported font type.') {
  1184.                     throw $e;
  1185.                 }
  1186.             }
  1187.         }
  1188.  
  1189.         return $fonts;
  1190.     }
  1191.  
  1192.     /**
  1193.      * Extract font attached to the page by specific font name
  1194.      *
  1195.      * $fontName should be specified in UTF-8 encoding
  1196.      *
  1197.      * @return Zend_Pdf_Resource_Font_Extracted|null
  1198.      * @throws Zend_Pdf_Exception
  1199.      */
  1200.     public function extractFont($fontName)
  1201.     {
  1202.         $fontResourcesUnique = array();
  1203.         require_once 'Zend/Pdf/Exception.php';
  1204.         foreach ($this->pages as $page) {
  1205.             $pageResources = $page->extractResources();
  1206.  
  1207.             if ($pageResources->Font === null) {
  1208.                 // Page doesn't contain have any font reference
  1209.                 continue;
  1210.             }
  1211.  
  1212.             $fontResources = $pageResources->Font;
  1213.  
  1214.             foreach ($fontResources->getKeys() as $fontResourceName) {
  1215.                 $fontDictionary = $fontResources->$fontResourceName;
  1216.  
  1217.                 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
  1218.                        $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
  1219.                     require_once 'Zend/Pdf/Exception.php';
  1220.                     throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
  1221.                 }
  1222.  
  1223.                 $resourceId = spl_object_hash($fontDictionary->getObject());
  1224.                 if (isset($fontResourcesUnique[$resourceId])) {
  1225.                     continue;
  1226.                 } else {
  1227.                     // Mark resource as processed
  1228.                     $fontResourcesUnique[$resourceId] = 1;
  1229.                 }
  1230.  
  1231.                 if ($fontDictionary->BaseFont->value != $fontName) {
  1232.                     continue;
  1233.                 }
  1234.  
  1235.                 try {
  1236.                     // Try to extract font
  1237.                     require_once 'Zend/Pdf/Resource/Font/Extracted.php';
  1238.                     return new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
  1239.                 } catch (Zend_Pdf_Exception $e) {
  1240.                     if ($e->getMessage() != 'Unsupported font type.') {
  1241.                         throw $e;
  1242.                     }
  1243.                     // Continue searhing
  1244.                 }
  1245.             }
  1246.         }
  1247.  
  1248.         return null;
  1249.     }
  1250.  
  1251.     /**
  1252.      * Render the completed PDF to a string.
  1253.      * If $newSegmentOnly is true and it's not a new document,
  1254.      * then only appended part of PDF is returned.
  1255.      *
  1256.      * @param boolean $newSegmentOnly
  1257.      * @param resource $outputStream
  1258.      * @return string
  1259.      * @throws Zend_Pdf_Exception
  1260.      */
  1261.     public function render($newSegmentOnly = false, $outputStream = null)
  1262.     {
  1263.         if ($this->_isNewDocument) {
  1264.             // Drop full document first time even $newSegmentOnly is set to true
  1265.             $newSegmentOnly = false;
  1266.             $this->_isNewDocument = false;
  1267.         }
  1268.  
  1269.         // Save document properties if necessary
  1270.         if ($this->properties != $this->_originalProperties) {
  1271.             $docInfo = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
  1272.  
  1273.             foreach ($this->properties as $key => $value) {
  1274.                 switch ($key) {
  1275.                     case 'Trapped':
  1276.                         switch ($value) {
  1277.                             case true:
  1278.                                 $docInfo->$key = new Zend_Pdf_Element_Name('True');
  1279.                                 break;
  1280.  
  1281.                             case false:
  1282.                                 $docInfo->$key = new Zend_Pdf_Element_Name('False');
  1283.                                 break;
  1284.  
  1285.                             case null:
  1286.                                 $docInfo->$key = new Zend_Pdf_Element_Name('Unknown');
  1287.                                 break;
  1288.  
  1289.                             default:
  1290.                                 require_once 'Zend/Pdf/Exception.php';
  1291.                                 throw new Zend_Pdf_Exception('Wrong Trapped document property vale: \'' . $value . '\'. Only true, false and null values are allowed.');
  1292.                                 break;
  1293.                         }
  1294.  
  1295.                     case 'CreationDate':
  1296.                         // break intentionally omitted
  1297.                     case 'ModDate':
  1298.                         $docInfo->$key = new Zend_Pdf_Element_String((string)$value);
  1299.                         break;
  1300.  
  1301.                     case 'Title':
  1302.                         // break intentionally omitted
  1303.                     case 'Author':
  1304.                         // break intentionally omitted
  1305.                     case 'Subject':
  1306.                         // break intentionally omitted
  1307.                     case 'Keywords':
  1308.                         // break intentionally omitted
  1309.                     case 'Creator':
  1310.                         // break intentionally omitted
  1311.                     case 'Producer':
  1312.                         if (extension_loaded('mbstring') === true) {
  1313.                             $detected = mb_detect_encoding($value);
  1314.                             if ($detected !== 'ASCII') {
  1315.                                 $value = "\xfe\xff" . mb_convert_encoding($value, 'UTF-8', $detected);
  1316.                             }
  1317.                         }
  1318.                         $docInfo->$key = new Zend_Pdf_Element_String((string)$value);
  1319.                         break;
  1320.  
  1321.                     default:
  1322.                         // Set property using PDF type based on PHP type
  1323.                         $docInfo->$key = Zend_Pdf_Element::phpToPdf($value);
  1324.                         break;
  1325.                 }
  1326.             }
  1327.  
  1328.             $this->_trailer->Info = $docInfo;
  1329.         }
  1330.  
  1331.         $this->_dumpPages();
  1332.         $this->_dumpNamedDestinations();
  1333.         $this->_dumpOutlines();
  1334.  
  1335.         // Check, that PDF file was modified
  1336.         // File is always modified by _dumpPages() now, but future implementations may eliminate this.
  1337.         if (!$this->_objFactory->isModified()) {
  1338.             if ($newSegmentOnly) {
  1339.                 // Do nothing, return
  1340.                 return '';
  1341.             }
  1342.  
  1343.             if ($outputStream === null) {
  1344.                 return $this->_trailer->getPDFString();
  1345.             } else {
  1346.                 $pdfData = $this->_trailer->getPDFString();
  1347.                 while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) {
  1348.                     $pdfData = substr($pdfData, $byteCount);
  1349.                 }
  1350.  
  1351.                 return '';
  1352.             }
  1353.         }
  1354.  
  1355.         // offset (from a start of PDF file) of new PDF file segment
  1356.         $offset = $this->_trailer->getPDFLength();
  1357.         // Last Object number in a list of free objects
  1358.         $lastFreeObject = $this->_trailer->getLastFreeObject();
  1359.  
  1360.         // Array of cross-reference table subsections
  1361.         $xrefTable = array();
  1362.         // Object numbers of first objects in each subsection
  1363.         $xrefSectionStartNums = array();
  1364.  
  1365.         // Last cross-reference table subsection
  1366.         $xrefSection = array();
  1367.         // Dummy initialization of the first element (specail case - header of linked list of free objects).
  1368.         $xrefSection[] = 0;
  1369.         $xrefSectionStartNums[] = 0;
  1370.         // Object number of last processed PDF object.
  1371.         // Used to manage cross-reference subsections.
  1372.         // Initialized by zero (specail case - header of linked list of free objects).
  1373.         $lastObjNum = 0;
  1374.  
  1375.         if ($outputStream !== null) {
  1376.             if (!$newSegmentOnly) {
  1377.                 $pdfData = $this->_trailer->getPDFString();
  1378.                 while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) {
  1379.                     $pdfData = substr($pdfData, $byteCount);
  1380.                 }
  1381.             }
  1382.         } else {
  1383.             $pdfSegmentBlocks = ($newSegmentOnly) ? array() : array($this->_trailer->getPDFString());
  1384.         }
  1385.  
  1386.         // Iterate objects to create new reference table
  1387.         foreach ($this->_objFactory->listModifiedObjects() as $updateInfo) {
  1388.             $objNum = $updateInfo->getObjNum();
  1389.  
  1390.             if ($objNum - $lastObjNum != 1) {
  1391.                 // Save cross-reference table subsection and start new one
  1392.                 $xrefTable[] = $xrefSection;
  1393.                 $xrefSection = array();
  1394.                 $xrefSectionStartNums[] = $objNum;
  1395.             }
  1396.  
  1397.             if ($updateInfo->isFree()) {
  1398.                 // Free object cross-reference table entry
  1399.                 $xrefSection[]  = sprintf("%010d %05d f \n", $lastFreeObject, $updateInfo->getGenNum());
  1400.                 $lastFreeObject = $objNum;
  1401.             } else {
  1402.                 // In-use object cross-reference table entry
  1403.                 $xrefSection[]  = sprintf("%010d %05d n \n", $offset, $updateInfo->getGenNum());
  1404.  
  1405.                 $pdfBlock = $updateInfo->getObjectDump();
  1406.                 $offset += strlen($pdfBlock);
  1407.  
  1408.                 if ($outputStream === null) {
  1409.                     $pdfSegmentBlocks[] = $pdfBlock;
  1410.                 } else {
  1411.                     while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) {
  1412.                         $pdfBlock = substr($pdfBlock, $byteCount);
  1413.                     }
  1414.                 }
  1415.             }
  1416.             $lastObjNum = $objNum;
  1417.         }
  1418.         // Save last cross-reference table subsection
  1419.         $xrefTable[] = $xrefSection;
  1420.  
  1421.         // Modify first entry (specail case - header of linked list of free objects).
  1422.         $xrefTable[0][0] = sprintf("%010d 65535 f \n", $lastFreeObject);
  1423.  
  1424.         $xrefTableStr = "xref\n";
  1425.         foreach ($xrefTable as $sectId => $xrefSection) {
  1426.             $xrefTableStr .= sprintf("%d %d \n", $xrefSectionStartNums[$sectId], count($xrefSection));
  1427.             foreach ($xrefSection as $xrefTableEntry) {
  1428.                 $xrefTableStr .= $xrefTableEntry;
  1429.             }
  1430.         }
  1431.  
  1432.         $this->_trailer->Size->value = $this->_objFactory->getObjectCount();
  1433.  
  1434.         $pdfBlock = $xrefTableStr
  1435.                  .  $this->_trailer->toString()
  1436.                  . "startxref\n" . $offset . "\n"
  1437.                  . "%%EOF\n";
  1438.  
  1439.         $this->_objFactory->cleanEnumerationShiftCache();
  1440.  
  1441.         if ($outputStream === null) {
  1442.             $pdfSegmentBlocks[] = $pdfBlock;
  1443.  
  1444.             return implode('', $pdfSegmentBlocks);
  1445.         } else {
  1446.             while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) {
  1447.                 $pdfBlock = substr($pdfBlock, $byteCount);
  1448.             }
  1449.  
  1450.             return '';
  1451.         }
  1452.     }
  1453.  
  1454.  
  1455.     /**
  1456.      * Set the document-level JavaScript
  1457.      *
  1458.      * @param string $javascript
  1459.      */
  1460.     public function setJavaScript($javascript)
  1461.     {
  1462.         $this->_javaScript = $javascript;
  1463.     }
  1464.  
  1465.  
  1466.     /**
  1467.      * Convert date to PDF format (it's close to ASN.1 (Abstract Syntax Notation
  1468.      * One) defined in ISO/IEC 8824).
  1469.      *
  1470.      * @todo This really isn't the best location for this method. It should
  1471.      *   probably actually exist as Zend_Pdf_Element_Date or something like that.
  1472.      *
  1473.      * @todo Address the following E_STRICT issue:
  1474.      *   PHP Strict Standards:  date(): It is not safe to rely on the system's
  1475.      *   timezone settings. Please use the date.timezone setting, the TZ
  1476.      *   environment variable or the date_default_timezone_set() function. In
  1477.      *   case you used any of those methods and you are still getting this
  1478.      *   warning, you most likely misspelled the timezone identifier.
  1479.      *
  1480.      * @param integer $timestamp (optional) If omitted, uses the current time.
  1481.      * @return string
  1482.      */
  1483.     public static function pdfDate($timestamp = null)
  1484.     {
  1485.         if ($timestamp === null) {
  1486.             $date = date('\D\:YmdHisO');
  1487.         } else {
  1488.             $date = date('\D\:YmdHisO', $timestamp);
  1489.         }
  1490.         return substr_replace($date, '\'', -2, 0) . '\'';
  1491.     }
  1492.  
  1493. }
Advertisement
Add Comment
Please, Sign In to add comment