web/lib/Zend/Pdf.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     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 license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Pdf
       
    17  * @copyright  Copyright (c) 2005-2010 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 22908 2010-08-25 20:52:47Z alexander $
       
    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-2010 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      * Request used memory manager
       
   214      *
       
   215      * @return Zend_Memory_Manager
       
   216      */
       
   217     static public function getMemoryManager()
       
   218     {
       
   219         if (self::$_memoryManager === null) {
       
   220             require_once 'Zend/Memory.php';
       
   221             self::$_memoryManager = Zend_Memory::factory('none');
       
   222         }
       
   223 
       
   224         return self::$_memoryManager;
       
   225     }
       
   226 
       
   227     /**
       
   228      * Set user defined memory manager
       
   229      *
       
   230      * @param Zend_Memory_Manager $memoryManager
       
   231      */
       
   232     static public function setMemoryManager(Zend_Memory_Manager $memoryManager)
       
   233     {
       
   234         self::$_memoryManager = $memoryManager;
       
   235     }
       
   236 
       
   237 
       
   238     /**
       
   239      * Create new PDF document from a $source string
       
   240      *
       
   241      * @param string $source
       
   242      * @param integer $revision
       
   243      * @return Zend_Pdf
       
   244      */
       
   245     public static function parse(&$source = null, $revision = null)
       
   246     {
       
   247         return new Zend_Pdf($source, $revision);
       
   248     }
       
   249 
       
   250     /**
       
   251      * Load PDF document from a file
       
   252      *
       
   253      * @param string $source
       
   254      * @param integer $revision
       
   255      * @return Zend_Pdf
       
   256      */
       
   257     public static function load($source = null, $revision = null)
       
   258     {
       
   259         return new Zend_Pdf($source, $revision, true);
       
   260     }
       
   261 
       
   262     /**
       
   263      * Render PDF document and save it.
       
   264      *
       
   265      * If $updateOnly is true, then it only appends new section to the end of file.
       
   266      *
       
   267      * @param string $filename
       
   268      * @param boolean $updateOnly
       
   269      * @throws Zend_Pdf_Exception
       
   270      */
       
   271     public function save($filename, $updateOnly = false)
       
   272     {
       
   273         if (($file = @fopen($filename, $updateOnly ? 'ab':'wb')) === false ) {
       
   274             require_once 'Zend/Pdf/Exception.php';
       
   275             throw new Zend_Pdf_Exception( "Can not open '$filename' file for writing." );
       
   276         }
       
   277 
       
   278         $this->render($updateOnly, $file);
       
   279 
       
   280         fclose($file);
       
   281     }
       
   282 
       
   283     /**
       
   284      * Creates or loads PDF document.
       
   285      *
       
   286      * If $source is null, then it creates a new document.
       
   287      *
       
   288      * If $source is a string and $load is false, then it loads document
       
   289      * from a binary string.
       
   290      *
       
   291      * If $source is a string and $load is true, then it loads document
       
   292      * from a file.
       
   293 
       
   294      * $revision used to roll back document to specified version
       
   295      * (0 - current version, 1 - previous version, 2 - ...)
       
   296      *
       
   297      * @param string  $source - PDF file to load
       
   298      * @param integer $revision
       
   299      * @throws Zend_Pdf_Exception
       
   300      * @return Zend_Pdf
       
   301      */
       
   302     public function __construct($source = null, $revision = null, $load = false)
       
   303     {
       
   304         require_once 'Zend/Pdf/ElementFactory.php';
       
   305         $this->_objFactory = Zend_Pdf_ElementFactory::createFactory(1);
       
   306 
       
   307         if ($source !== null) {
       
   308             require_once 'Zend/Pdf/Parser.php';
       
   309             $this->_parser           = new Zend_Pdf_Parser($source, $this->_objFactory, $load);
       
   310             $this->_pdfHeaderVersion = $this->_parser->getPDFVersion();
       
   311             $this->_trailer          = $this->_parser->getTrailer();
       
   312             if ($this->_trailer->Encrypt !== null) {
       
   313                 require_once 'Zend/Pdf/Exception.php';
       
   314                 throw new Zend_Pdf_Exception('Encrypted document modification is not supported');
       
   315             }
       
   316             if ($revision !== null) {
       
   317                 $this->rollback($revision);
       
   318             } else {
       
   319                 $this->_loadPages($this->_trailer->Root->Pages);
       
   320             }
       
   321 
       
   322             $this->_loadNamedDestinations($this->_trailer->Root, $this->_parser->getPDFVersion());
       
   323             $this->_loadOutlines($this->_trailer->Root);
       
   324 
       
   325             if ($this->_trailer->Info !== null) {
       
   326                 $this->properties = $this->_trailer->Info->toPhp();
       
   327 
       
   328                 if (isset($this->properties['Trapped'])) {
       
   329                     switch ($this->properties['Trapped']) {
       
   330                         case 'True':
       
   331                             $this->properties['Trapped'] = true;
       
   332                             break;
       
   333 
       
   334                         case 'False':
       
   335                             $this->properties['Trapped'] = false;
       
   336                             break;
       
   337 
       
   338                         case 'Unknown':
       
   339                             $this->properties['Trapped'] = null;
       
   340                             break;
       
   341 
       
   342                         default:
       
   343                             // Wrong property value
       
   344                             // Do nothing
       
   345                             break;
       
   346                     }
       
   347                 }
       
   348 
       
   349                 $this->_originalProperties = $this->properties;
       
   350             }
       
   351         } else {
       
   352             $this->_pdfHeaderVersion = Zend_Pdf::PDF_VERSION;
       
   353 
       
   354             $trailerDictionary = new Zend_Pdf_Element_Dictionary();
       
   355 
       
   356             /**
       
   357              * Document id
       
   358              */
       
   359             $docId = md5(uniqid(rand(), true));   // 32 byte (128 bit) identifier
       
   360             $docIdLow  = substr($docId,  0, 16);  // first 16 bytes
       
   361             $docIdHigh = substr($docId, 16, 16);  // second 16 bytes
       
   362 
       
   363             $trailerDictionary->ID = new Zend_Pdf_Element_Array();
       
   364             $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdLow);
       
   365             $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdHigh);
       
   366 
       
   367             $trailerDictionary->Size = new Zend_Pdf_Element_Numeric(0);
       
   368 
       
   369             require_once 'Zend/Pdf/Trailer/Generator.php';
       
   370             $this->_trailer = new Zend_Pdf_Trailer_Generator($trailerDictionary);
       
   371 
       
   372             /**
       
   373              * Document catalog indirect object.
       
   374              */
       
   375             $docCatalog = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
       
   376             $docCatalog->Type    = new Zend_Pdf_Element_Name('Catalog');
       
   377             $docCatalog->Version = new Zend_Pdf_Element_Name(Zend_Pdf::PDF_VERSION);
       
   378             $this->_trailer->Root = $docCatalog;
       
   379 
       
   380             /**
       
   381              * Pages container
       
   382              */
       
   383             $docPages = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
       
   384             $docPages->Type  = new Zend_Pdf_Element_Name('Pages');
       
   385             $docPages->Kids  = new Zend_Pdf_Element_Array();
       
   386             $docPages->Count = new Zend_Pdf_Element_Numeric(0);
       
   387             $docCatalog->Pages = $docPages;
       
   388         }
       
   389     }
       
   390 
       
   391     /**
       
   392      * Retrive number of revisions.
       
   393      *
       
   394      * @return integer
       
   395      */
       
   396     public function revisions()
       
   397     {
       
   398         $revisions = 1;
       
   399         $currentTrailer = $this->_trailer;
       
   400 
       
   401         while ($currentTrailer->getPrev() !== null && $currentTrailer->getPrev()->Root !== null ) {
       
   402             $revisions++;
       
   403             $currentTrailer = $currentTrailer->getPrev();
       
   404         }
       
   405 
       
   406         return $revisions++;
       
   407     }
       
   408 
       
   409     /**
       
   410      * Rollback document $steps number of revisions.
       
   411      * This method must be invoked before any changes, applied to the document.
       
   412      * Otherwise behavior is undefined.
       
   413      *
       
   414      * @param integer $steps
       
   415      */
       
   416     public function rollback($steps)
       
   417     {
       
   418         for ($count = 0; $count < $steps; $count++) {
       
   419             if ($this->_trailer->getPrev() !== null && $this->_trailer->getPrev()->Root !== null) {
       
   420                 $this->_trailer = $this->_trailer->getPrev();
       
   421             } else {
       
   422                 break;
       
   423             }
       
   424         }
       
   425         $this->_objFactory->setObjectCount($this->_trailer->Size->value);
       
   426 
       
   427         // Mark content as modified to force new trailer generation at render time
       
   428         $this->_trailer->Root->touch();
       
   429 
       
   430         $this->pages = array();
       
   431         $this->_loadPages($this->_trailer->Root->Pages);
       
   432     }
       
   433 
       
   434 
       
   435     /**
       
   436      * Load pages recursively
       
   437      *
       
   438      * @param Zend_Pdf_Element_Reference $pages
       
   439      * @param array|null $attributes
       
   440      */
       
   441     protected function _loadPages(Zend_Pdf_Element_Reference $pages, $attributes = array())
       
   442     {
       
   443         if ($pages->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
       
   444             require_once 'Zend/Pdf/Exception.php';
       
   445             throw new Zend_Pdf_Exception('Wrong argument');
       
   446         }
       
   447 
       
   448         foreach ($pages->getKeys() as $property) {
       
   449             if (in_array($property, self::$_inheritableAttributes)) {
       
   450                 $attributes[$property] = $pages->$property;
       
   451                 $pages->$property = null;
       
   452             }
       
   453         }
       
   454 
       
   455 
       
   456         foreach ($pages->Kids->items as $child) {
       
   457             if ($child->Type->value == 'Pages') {
       
   458                 $this->_loadPages($child, $attributes);
       
   459             } else if ($child->Type->value == 'Page') {
       
   460                 foreach (self::$_inheritableAttributes as $property) {
       
   461                     if ($child->$property === null && array_key_exists($property, $attributes)) {
       
   462                         /**
       
   463                          * Important note.
       
   464                          * If any attribute or dependant object is an indirect object, then it's still
       
   465                          * shared between pages.
       
   466                          */
       
   467                         if ($attributes[$property] instanceof Zend_Pdf_Element_Object  ||
       
   468                             $attributes[$property] instanceof Zend_Pdf_Element_Reference) {
       
   469                             $child->$property = $attributes[$property];
       
   470                         } else {
       
   471                             $child->$property = $this->_objFactory->newObject($attributes[$property]);
       
   472                         }
       
   473                     }
       
   474                 }
       
   475 
       
   476                 require_once 'Zend/Pdf/Page.php';
       
   477                 $this->pages[] = new Zend_Pdf_Page($child, $this->_objFactory);
       
   478             }
       
   479         }
       
   480     }
       
   481 
       
   482     /**
       
   483      * Load named destinations recursively
       
   484      *
       
   485      * @param Zend_Pdf_Element_Reference $root Document catalog entry
       
   486      * @param string $pdfHeaderVersion
       
   487      * @throws Zend_Pdf_Exception
       
   488      */
       
   489     protected function _loadNamedDestinations(Zend_Pdf_Element_Reference $root, $pdfHeaderVersion)
       
   490     {
       
   491         if ($root->Version !== null  &&  version_compare($root->Version->value, $pdfHeaderVersion, '>')) {
       
   492             $versionIs_1_2_plus = version_compare($root->Version->value,    '1.1', '>');
       
   493         } else {
       
   494             $versionIs_1_2_plus = version_compare($pdfHeaderVersion, '1.1', '>');
       
   495         }
       
   496 
       
   497         if ($versionIs_1_2_plus) {
       
   498             // PDF version is 1.2+
       
   499             // Look for Destinations structure at Name dictionary
       
   500             if ($root->Names !== null  &&  $root->Names->Dests !== null) {
       
   501                 require_once 'Zend/Pdf/NameTree.php';
       
   502                 require_once 'Zend/Pdf/Target.php';
       
   503                 foreach (new Zend_Pdf_NameTree($root->Names->Dests) as $name => $destination) {
       
   504                     $this->_namedTargets[$name] = Zend_Pdf_Target::load($destination);
       
   505                 }
       
   506             }
       
   507         } else {
       
   508             // PDF version is 1.1 (or earlier)
       
   509             // Look for Destinations sructure at Dest entry of document catalog
       
   510             if ($root->Dests !== null) {
       
   511                 if ($root->Dests->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
       
   512                     require_once 'Zend/Pdf/Exception.php';
       
   513                     throw new Zend_Pdf_Exception('Document catalog Dests entry must be a dictionary.');
       
   514                 }
       
   515 
       
   516                 require_once 'Zend/Pdf/Target.php';
       
   517                 foreach ($root->Dests->getKeys() as $destKey) {
       
   518                     $this->_namedTargets[$destKey] = Zend_Pdf_Target::load($root->Dests->$destKey);
       
   519                 }
       
   520             }
       
   521         }
       
   522     }
       
   523 
       
   524     /**
       
   525      * Load outlines recursively
       
   526      *
       
   527      * @param Zend_Pdf_Element_Reference $root Document catalog entry
       
   528      */
       
   529     protected function _loadOutlines(Zend_Pdf_Element_Reference $root)
       
   530     {
       
   531         if ($root->Outlines === null) {
       
   532             return;
       
   533         }
       
   534 
       
   535         if ($root->Outlines->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
       
   536             require_once 'Zend/Pdf/Exception.php';
       
   537             throw new Zend_Pdf_Exception('Document catalog Outlines entry must be a dictionary.');
       
   538         }
       
   539 
       
   540         if ($root->Outlines->Type !== null  &&  $root->Outlines->Type->value != 'Outlines') {
       
   541             require_once 'Zend/Pdf/Exception.php';
       
   542             throw new Zend_Pdf_Exception('Outlines Type entry must be an \'Outlines\' string.');
       
   543         }
       
   544 
       
   545         if ($root->Outlines->First === null) {
       
   546             return;
       
   547         }
       
   548 
       
   549         $outlineDictionary = $root->Outlines->First;
       
   550         $processedDictionaries = new SplObjectStorage();
       
   551         while ($outlineDictionary !== null  &&  !$processedDictionaries->contains($outlineDictionary)) {
       
   552             $processedDictionaries->attach($outlineDictionary);
       
   553 
       
   554             require_once 'Zend/Pdf/Outline/Loaded.php';
       
   555             $this->outlines[] = new Zend_Pdf_Outline_Loaded($outlineDictionary);
       
   556 
       
   557             $outlineDictionary = $outlineDictionary->Next;
       
   558         }
       
   559 
       
   560         $this->_originalOutlines = $this->outlines;
       
   561 
       
   562         if ($root->Outlines->Count !== null) {
       
   563             $this->_originalOpenOutlinesCount = $root->Outlines->Count->value;
       
   564         }
       
   565     }
       
   566 
       
   567     /**
       
   568      * Orginize pages to tha pages tree structure.
       
   569      *
       
   570      * @todo atomatically attach page to the document, if it's not done yet.
       
   571      * @todo check, that page is attached to the current document
       
   572      *
       
   573      * @todo Dump pages as a balanced tree instead of a plain set.
       
   574      */
       
   575     protected function _dumpPages()
       
   576     {
       
   577         $root = $this->_trailer->Root;
       
   578         $pagesContainer = $root->Pages;
       
   579 
       
   580         $pagesContainer->touch();
       
   581         $pagesContainer->Kids->items = array();
       
   582 
       
   583         foreach ($this->pages as $page ) {
       
   584             $page->render($this->_objFactory);
       
   585 
       
   586             $pageDictionary = $page->getPageDictionary();
       
   587             $pageDictionary->touch();
       
   588             $pageDictionary->Parent = $pagesContainer;
       
   589 
       
   590             $pagesContainer->Kids->items[] = $pageDictionary;
       
   591         }
       
   592 
       
   593         $this->_refreshPagesHash();
       
   594 
       
   595         $pagesContainer->Count->touch();
       
   596         $pagesContainer->Count->value = count($this->pages);
       
   597 
       
   598 
       
   599         // Refresh named destinations list
       
   600         foreach ($this->_namedTargets as $name => $namedTarget) {
       
   601             if ($namedTarget instanceof Zend_Pdf_Destination_Explicit) {
       
   602                 // Named target is an explicit destination
       
   603                 if ($this->resolveDestination($namedTarget, false) === null) {
       
   604                     unset($this->_namedTargets[$name]);
       
   605                 }
       
   606             } else if ($namedTarget instanceof Zend_Pdf_Action) {
       
   607                 // Named target is an action
       
   608                 if ($this->_cleanUpAction($namedTarget, false) === null) {
       
   609                     // Action is a GoTo action with an unresolved destination
       
   610                     unset($this->_namedTargets[$name]);
       
   611                 }
       
   612             } else {
       
   613                 require_once 'Zend/Pdf/Exception.php';
       
   614                 throw new Zend_Pdf_Exception('Wrong type of named targed (\'' . get_class($namedTarget) . '\').');
       
   615             }
       
   616         }
       
   617 
       
   618         // Refresh outlines
       
   619         require_once 'Zend/Pdf/RecursivelyIteratableObjectsContainer.php';
       
   620         $iterator = new RecursiveIteratorIterator(new Zend_Pdf_RecursivelyIteratableObjectsContainer($this->outlines), RecursiveIteratorIterator::SELF_FIRST);
       
   621         foreach ($iterator as $outline) {
       
   622             $target = $outline->getTarget();
       
   623 
       
   624             if ($target !== null) {
       
   625                 if ($target instanceof Zend_Pdf_Destination) {
       
   626                     // Outline target is a destination
       
   627                     if ($this->resolveDestination($target, false) === null) {
       
   628                         $outline->setTarget(null);
       
   629                     }
       
   630                 } else if ($target instanceof Zend_Pdf_Action) {
       
   631                     // Outline target is an action
       
   632                     if ($this->_cleanUpAction($target, false) === null) {
       
   633                         // Action is a GoTo action with an unresolved destination
       
   634                         $outline->setTarget(null);
       
   635                     }
       
   636                 } else {
       
   637                     require_once 'Zend/Pdf/Exception.php';
       
   638                     throw new Zend_Pdf_Exception('Wrong outline target.');
       
   639                 }
       
   640             }
       
   641         }
       
   642 
       
   643         $openAction = $this->getOpenAction();
       
   644         if ($openAction !== null) {
       
   645             if ($openAction instanceof Zend_Pdf_Action) {
       
   646                 // OpenAction is an action
       
   647                 if ($this->_cleanUpAction($openAction, false) === null) {
       
   648                     // Action is a GoTo action with an unresolved destination
       
   649                     $this->setOpenAction(null);
       
   650                 }
       
   651             } else if ($openAction instanceof Zend_Pdf_Destination) {
       
   652                 // OpenAction target is a destination
       
   653                 if ($this->resolveDestination($openAction, false) === null) {
       
   654                     $this->setOpenAction(null);
       
   655                 }
       
   656             } else {
       
   657                 require_once 'Zend/Pdf/Exception.php';
       
   658                 throw new Zend_Pdf_Exception('OpenAction has to be either PDF Action or Destination.');
       
   659             }
       
   660         }
       
   661     }
       
   662 
       
   663     /**
       
   664      * Dump named destinations
       
   665      *
       
   666      * @todo Create a balanced tree instead of plain structure.
       
   667      */
       
   668     protected function _dumpNamedDestinations()
       
   669     {
       
   670         ksort($this->_namedTargets, SORT_STRING);
       
   671 
       
   672         $destArrayItems = array();
       
   673         foreach ($this->_namedTargets as $name => $destination) {
       
   674             $destArrayItems[] = new Zend_Pdf_Element_String($name);
       
   675 
       
   676             if ($destination instanceof Zend_Pdf_Target) {
       
   677                 $destArrayItems[] = $destination->getResource();
       
   678             } else {
       
   679                 require_once 'Zend/Pdf/Exception.php';
       
   680                 throw new Zend_Pdf_Exception('PDF named destinations must be a Zend_Pdf_Target object.');
       
   681             }
       
   682         }
       
   683         $destArray = $this->_objFactory->newObject(new Zend_Pdf_Element_Array($destArrayItems));
       
   684 
       
   685         $DestTree = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
       
   686         $DestTree->Names = $destArray;
       
   687 
       
   688         $root = $this->_trailer->Root;
       
   689 
       
   690         if ($root->Names === null) {
       
   691             $root->touch();
       
   692             $root->Names = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
       
   693         } else {
       
   694             $root->Names->touch();
       
   695         }
       
   696         $root->Names->Dests = $DestTree;
       
   697     }
       
   698 
       
   699     /**
       
   700      * Dump outlines recursively
       
   701      */
       
   702     protected function _dumpOutlines()
       
   703     {
       
   704         $root = $this->_trailer->Root;
       
   705 
       
   706         if ($root->Outlines === null) {
       
   707             if (count($this->outlines) == 0) {
       
   708                 return;
       
   709             } else {
       
   710                 $root->Outlines = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
       
   711                 $root->Outlines->Type = new Zend_Pdf_Element_Name('Outlines');
       
   712                 $updateOutlinesNavigation = true;
       
   713             }
       
   714         } else {
       
   715             $updateOutlinesNavigation = false;
       
   716             if (count($this->_originalOutlines) != count($this->outlines)) {
       
   717                 // If original and current outlines arrays have different size then outlines list was updated
       
   718                 $updateOutlinesNavigation = true;
       
   719             } else if ( !(array_keys($this->_originalOutlines) === array_keys($this->outlines)) ) {
       
   720                 // If original and current outlines arrays have different keys (with a glance to an order) then outlines list was updated
       
   721                 $updateOutlinesNavigation = true;
       
   722             } else {
       
   723                 foreach ($this->outlines as $key => $outline) {
       
   724                     if ($this->_originalOutlines[$key] !== $outline) {
       
   725                         $updateOutlinesNavigation = true;
       
   726                     }
       
   727                 }
       
   728             }
       
   729         }
       
   730 
       
   731         $lastOutline = null;
       
   732         $openOutlinesCount = 0;
       
   733         if ($updateOutlinesNavigation) {
       
   734             $root->Outlines->touch();
       
   735             $root->Outlines->First = null;
       
   736 
       
   737             foreach ($this->outlines as $outline) {
       
   738                 if ($lastOutline === null) {
       
   739                     // First pass. Update Outlines dictionary First entry using corresponding value
       
   740                     $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines);
       
   741                     $root->Outlines->First = $lastOutline;
       
   742                 } else {
       
   743                     // Update previous outline dictionary Next entry (Prev is updated within dumpOutline() method)
       
   744                     $currentOutlineDictionary = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline);
       
   745                     $lastOutline->Next = $currentOutlineDictionary;
       
   746                     $lastOutline       = $currentOutlineDictionary;
       
   747                 }
       
   748                 $openOutlinesCount += $outline->openOutlinesCount();
       
   749             }
       
   750 
       
   751             $root->Outlines->Last  = $lastOutline;
       
   752         } else {
       
   753             foreach ($this->outlines as $outline) {
       
   754                 $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline);
       
   755                 $openOutlinesCount += $outline->openOutlinesCount();
       
   756             }
       
   757         }
       
   758 
       
   759         if ($openOutlinesCount != $this->_originalOpenOutlinesCount) {
       
   760             $root->Outlines->touch;
       
   761             $root->Outlines->Count = new Zend_Pdf_Element_Numeric($openOutlinesCount);
       
   762         }
       
   763     }
       
   764 
       
   765     /**
       
   766      * Create page object, attached to the PDF document.
       
   767      * Method signatures:
       
   768      *
       
   769      * 1. Create new page with a specified pagesize.
       
   770      *    If $factory is null then it will be created and page must be attached to the document to be
       
   771      *    included into output.
       
   772      * ---------------------------------------------------------
       
   773      * new Zend_Pdf_Page(string $pagesize);
       
   774      * ---------------------------------------------------------
       
   775      *
       
   776      * 2. Create new page with a specified pagesize (in default user space units).
       
   777      *    If $factory is null then it will be created and page must be attached to the document to be
       
   778      *    included into output.
       
   779      * ---------------------------------------------------------
       
   780      * new Zend_Pdf_Page(numeric $width, numeric $height);
       
   781      * ---------------------------------------------------------
       
   782      *
       
   783      * @param mixed $param1
       
   784      * @param mixed $param2
       
   785      * @return Zend_Pdf_Page
       
   786      */
       
   787     public function newPage($param1, $param2 = null)
       
   788     {
       
   789         require_once 'Zend/Pdf/Page.php';
       
   790         if ($param2 === null) {
       
   791             return new Zend_Pdf_Page($param1, $this->_objFactory);
       
   792         } else {
       
   793             return new Zend_Pdf_Page($param1, $param2, $this->_objFactory);
       
   794         }
       
   795     }
       
   796 
       
   797     /**
       
   798      * Return the document-level Metadata
       
   799      * or null Metadata stream is not presented
       
   800      *
       
   801      * @return string
       
   802      */
       
   803     public function getMetadata()
       
   804     {
       
   805         if ($this->_trailer->Root->Metadata !== null) {
       
   806             return $this->_trailer->Root->Metadata->value;
       
   807         } else {
       
   808             return null;
       
   809         }
       
   810     }
       
   811 
       
   812     /**
       
   813      * Sets the document-level Metadata (mast be valid XMP document)
       
   814      *
       
   815      * @param string $metadata
       
   816      */
       
   817     public function setMetadata($metadata)
       
   818     {
       
   819         $metadataObject = $this->_objFactory->newStreamObject($metadata);
       
   820         $metadataObject->dictionary->Type    = new Zend_Pdf_Element_Name('Metadata');
       
   821         $metadataObject->dictionary->Subtype = new Zend_Pdf_Element_Name('XML');
       
   822 
       
   823         $this->_trailer->Root->Metadata = $metadataObject;
       
   824         $this->_trailer->Root->touch();
       
   825     }
       
   826 
       
   827     /**
       
   828      * Return the document-level JavaScript
       
   829      * or null if there is no JavaScript for this document
       
   830      *
       
   831      * @return string
       
   832      */
       
   833     public function getJavaScript()
       
   834     {
       
   835         return $this->_javaScript;
       
   836     }
       
   837 
       
   838     /**
       
   839      * Get open Action
       
   840      * Returns Zend_Pdf_Target (Zend_Pdf_Destination or Zend_Pdf_Action object)
       
   841      *
       
   842      * @return Zend_Pdf_Target
       
   843      */
       
   844     public function getOpenAction()
       
   845     {
       
   846         if ($this->_trailer->Root->OpenAction !== null) {
       
   847             require_once 'Zend/Pdf/Target.php';
       
   848             return Zend_Pdf_Target::load($this->_trailer->Root->OpenAction);
       
   849         } else {
       
   850             return null;
       
   851         }
       
   852     }
       
   853 
       
   854     /**
       
   855      * Set open Action which is actually Zend_Pdf_Destination or Zend_Pdf_Action object
       
   856      *
       
   857      * @param Zend_Pdf_Target $openAction
       
   858      * @returns Zend_Pdf
       
   859      */
       
   860     public function setOpenAction(Zend_Pdf_Target $openAction = null)
       
   861     {
       
   862         $root = $this->_trailer->Root;
       
   863         $root->touch();
       
   864 
       
   865         if ($openAction === null) {
       
   866             $root->OpenAction = null;
       
   867         } else {
       
   868             $root->OpenAction = $openAction->getResource();
       
   869 
       
   870             if ($openAction instanceof Zend_Pdf_Action)  {
       
   871                 $openAction->dumpAction($this->_objFactory);
       
   872             }
       
   873         }
       
   874 
       
   875         return $this;
       
   876     }
       
   877 
       
   878     /**
       
   879      * Return an associative array containing all the named destinations (or GoTo actions) in the PDF.
       
   880      * Named targets can be used to reference from outside
       
   881      * the PDF, ex: 'http://www.something.com/mydocument.pdf#MyAction'
       
   882      *
       
   883      * @return array
       
   884      */
       
   885     public function getNamedDestinations()
       
   886     {
       
   887         return $this->_namedTargets;
       
   888     }
       
   889 
       
   890     /**
       
   891      * Return specified named destination
       
   892      *
       
   893      * @param string $name
       
   894      * @return Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo
       
   895      */
       
   896     public function getNamedDestination($name)
       
   897     {
       
   898         if (isset($this->_namedTargets[$name])) {
       
   899             return $this->_namedTargets[$name];
       
   900         } else {
       
   901             return null;
       
   902         }
       
   903     }
       
   904 
       
   905     /**
       
   906      * Set specified named destination
       
   907      *
       
   908      * @param string $name
       
   909      * @param Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo $target
       
   910      */
       
   911     public function setNamedDestination($name, $destination = null)
       
   912     {
       
   913         if ($destination !== null  &&
       
   914             !$destination instanceof Zend_Pdf_Action_GoTo  &&
       
   915             !$destination instanceof Zend_Pdf_Destination_Explicit) {
       
   916             require_once 'Zend/Pdf/Exception.php';
       
   917             throw new Zend_Pdf_Exception('PDF named destination must refer an explicit destination or a GoTo PDF action.');
       
   918         }
       
   919 
       
   920         if ($destination !== null) {
       
   921            $this->_namedTargets[$name] = $destination;
       
   922         } else {
       
   923             unset($this->_namedTargets[$name]);
       
   924         }
       
   925     }
       
   926 
       
   927     /**
       
   928      * Pages collection hash:
       
   929      * <page dictionary object hash id> => Zend_Pdf_Page
       
   930      *
       
   931      * @var SplObjectStorage
       
   932      */
       
   933     protected $_pageReferences = null;
       
   934 
       
   935     /**
       
   936      * Pages collection hash:
       
   937      * <page number> => Zend_Pdf_Page
       
   938      *
       
   939      * @var array
       
   940      */
       
   941     protected $_pageNumbers = null;
       
   942 
       
   943     /**
       
   944      * Refresh page collection hashes
       
   945      *
       
   946      * @return Zend_Pdf
       
   947      */
       
   948     protected function _refreshPagesHash()
       
   949     {
       
   950         $this->_pageReferences = array();
       
   951         $this->_pageNumbers    = array();
       
   952         $count = 1;
       
   953         foreach ($this->pages as $page) {
       
   954             $pageDictionaryHashId = spl_object_hash($page->getPageDictionary()->getObject());
       
   955             $this->_pageReferences[$pageDictionaryHashId] = $page;
       
   956             $this->_pageNumbers[$count++]                 = $page;
       
   957         }
       
   958 
       
   959         return $this;
       
   960     }
       
   961 
       
   962     /**
       
   963      * Resolve destination.
       
   964      *
       
   965      * Returns Zend_Pdf_Page page object or null if destination is not found within PDF document.
       
   966      *
       
   967      * @param Zend_Pdf_Destination $destination  Destination to resolve
       
   968      * @param boolean $refreshPagesHash  Refresh page collection hashes before processing
       
   969      * @return Zend_Pdf_Page|null
       
   970      * @throws Zend_Pdf_Exception
       
   971      */
       
   972     public function resolveDestination(Zend_Pdf_Destination $destination, $refreshPageCollectionHashes = true)
       
   973     {
       
   974         if ($this->_pageReferences === null  ||  $refreshPageCollectionHashes) {
       
   975             $this->_refreshPagesHash();
       
   976         }
       
   977 
       
   978         if ($destination instanceof Zend_Pdf_Destination_Named) {
       
   979             if (!isset($this->_namedTargets[$destination->getName()])) {
       
   980                 return null;
       
   981             }
       
   982             $destination = $this->getNamedDestination($destination->getName());
       
   983 
       
   984             if ($destination instanceof Zend_Pdf_Action) {
       
   985                 if (!$destination instanceof Zend_Pdf_Action_GoTo) {
       
   986                     return null;
       
   987                 }
       
   988                 $destination = $destination->getDestination();
       
   989             }
       
   990 
       
   991             if (!$destination instanceof Zend_Pdf_Destination_Explicit) {
       
   992                 require_once 'Zend/Pdf/Exception.php';
       
   993                 throw new Zend_Pdf_Exception('Named destination target has to be an explicit destination.');
       
   994             }
       
   995         }
       
   996 
       
   997         // Named target is an explicit destination
       
   998         $pageElement = $destination->getResource()->items[0];
       
   999 
       
  1000         if ($pageElement->getType() == Zend_Pdf_Element::TYPE_NUMERIC) {
       
  1001             // Page reference is a PDF number
       
  1002             if (!isset($this->_pageNumbers[$pageElement->value])) {
       
  1003                 return null;
       
  1004             }
       
  1005 
       
  1006             return $this->_pageNumbers[$pageElement->value];
       
  1007         }
       
  1008 
       
  1009         // Page reference is a PDF page dictionary reference
       
  1010         $pageDictionaryHashId = spl_object_hash($pageElement->getObject());
       
  1011         if (!isset($this->_pageReferences[$pageDictionaryHashId])) {
       
  1012             return null;
       
  1013         }
       
  1014         return $this->_pageReferences[$pageDictionaryHashId];
       
  1015     }
       
  1016 
       
  1017     /**
       
  1018      * Walk through action and its chained actions tree and remove nodes
       
  1019      * if they are GoTo actions with an unresolved target.
       
  1020      *
       
  1021      * Returns null if root node is deleted or updated action overwise.
       
  1022      *
       
  1023      * @todo Give appropriate name and make method public
       
  1024      *
       
  1025      * @param Zend_Pdf_Action $action
       
  1026      * @param boolean $refreshPagesHash  Refresh page collection hashes before processing
       
  1027      * @return Zend_Pdf_Action|null
       
  1028      */
       
  1029     protected function _cleanUpAction(Zend_Pdf_Action $action, $refreshPageCollectionHashes = true)
       
  1030     {
       
  1031         if ($this->_pageReferences === null  ||  $refreshPageCollectionHashes) {
       
  1032             $this->_refreshPagesHash();
       
  1033         }
       
  1034 
       
  1035         // Named target is an action
       
  1036         if ($action instanceof Zend_Pdf_Action_GoTo  &&
       
  1037             $this->resolveDestination($action->getDestination(), false) === null) {
       
  1038             // Action itself is a GoTo action with an unresolved destination
       
  1039             return null;
       
  1040         }
       
  1041 
       
  1042         // Walk through child actions
       
  1043         $iterator = new RecursiveIteratorIterator($action, RecursiveIteratorIterator::SELF_FIRST);
       
  1044 
       
  1045         $actionsToClean        = array();
       
  1046         $deletionCandidateKeys = array();
       
  1047         foreach ($iterator as $chainedAction) {
       
  1048             if ($chainedAction instanceof Zend_Pdf_Action_GoTo  &&
       
  1049                 $this->resolveDestination($chainedAction->getDestination(), false) === null) {
       
  1050                 // Some child action is a GoTo action with an unresolved destination
       
  1051                 // Mark it as a candidate for deletion
       
  1052                 $actionsToClean[]        = $iterator->getSubIterator();
       
  1053                 $deletionCandidateKeys[] = $iterator->getSubIterator()->key();
       
  1054             }
       
  1055         }
       
  1056         foreach ($actionsToClean as $id => $action) {
       
  1057             unset($action->next[$deletionCandidateKeys[$id]]);
       
  1058         }
       
  1059 
       
  1060         return $action;
       
  1061     }
       
  1062 
       
  1063     /**
       
  1064      * Extract fonts attached to the document
       
  1065      *
       
  1066      * returns array of Zend_Pdf_Resource_Font_Extracted objects
       
  1067      *
       
  1068      * @return array
       
  1069      * @throws Zend_Pdf_Exception
       
  1070      */
       
  1071     public function extractFonts()
       
  1072     {
       
  1073         $fontResourcesUnique = array();
       
  1074         foreach ($this->pages as $page) {
       
  1075             $pageResources = $page->extractResources();
       
  1076 
       
  1077             if ($pageResources->Font === null) {
       
  1078                 // Page doesn't contain have any font reference
       
  1079                 continue;
       
  1080             }
       
  1081 
       
  1082             $fontResources = $pageResources->Font;
       
  1083 
       
  1084             foreach ($fontResources->getKeys() as $fontResourceName) {
       
  1085                 $fontDictionary = $fontResources->$fontResourceName;
       
  1086 
       
  1087                 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
       
  1088                        $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
       
  1089                     require_once 'Zend/Pdf/Exception.php';
       
  1090                     throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
       
  1091                 }
       
  1092 
       
  1093                 $fontResourcesUnique[spl_object_hash($fontDictionary->getObject())] = $fontDictionary;
       
  1094             }
       
  1095         }
       
  1096 
       
  1097         $fonts = array();
       
  1098         require_once 'Zend/Pdf/Exception.php';
       
  1099         foreach ($fontResourcesUnique as $resourceId => $fontDictionary) {
       
  1100             try {
       
  1101                 // Try to extract font
       
  1102                 require_once 'Zend/Pdf/Resource/Font/Extracted.php';
       
  1103                 $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
       
  1104 
       
  1105                 $fonts[$resourceId] = $extractedFont;
       
  1106             } catch (Zend_Pdf_Exception $e) {
       
  1107                 if ($e->getMessage() != 'Unsupported font type.') {
       
  1108                     throw $e;
       
  1109                 }
       
  1110             }
       
  1111         }
       
  1112 
       
  1113         return $fonts;
       
  1114     }
       
  1115 
       
  1116     /**
       
  1117      * Extract font attached to the page by specific font name
       
  1118      *
       
  1119      * $fontName should be specified in UTF-8 encoding
       
  1120      *
       
  1121      * @return Zend_Pdf_Resource_Font_Extracted|null
       
  1122      * @throws Zend_Pdf_Exception
       
  1123      */
       
  1124     public function extractFont($fontName)
       
  1125     {
       
  1126         $fontResourcesUnique = array();
       
  1127         require_once 'Zend/Pdf/Exception.php';
       
  1128         foreach ($this->pages as $page) {
       
  1129             $pageResources = $page->extractResources();
       
  1130 
       
  1131             if ($pageResources->Font === null) {
       
  1132                 // Page doesn't contain have any font reference
       
  1133                 continue;
       
  1134             }
       
  1135 
       
  1136             $fontResources = $pageResources->Font;
       
  1137 
       
  1138             foreach ($fontResources->getKeys() as $fontResourceName) {
       
  1139                 $fontDictionary = $fontResources->$fontResourceName;
       
  1140 
       
  1141                 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
       
  1142                        $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
       
  1143                     require_once 'Zend/Pdf/Exception.php';
       
  1144                     throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.');
       
  1145                 }
       
  1146 
       
  1147                 $resourceId = spl_object_hash($fontDictionary->getObject());
       
  1148                 if (isset($fontResourcesUnique[$resourceId])) {
       
  1149                     continue;
       
  1150                 } else {
       
  1151                     // Mark resource as processed
       
  1152                     $fontResourcesUnique[$resourceId] = 1;
       
  1153                 }
       
  1154 
       
  1155                 if ($fontDictionary->BaseFont->value != $fontName) {
       
  1156                     continue;
       
  1157                 }
       
  1158 
       
  1159                 try {
       
  1160                     // Try to extract font
       
  1161                     require_once 'Zend/Pdf/Resource/Font/Extracted.php';
       
  1162                     return new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
       
  1163                 } catch (Zend_Pdf_Exception $e) {
       
  1164                     if ($e->getMessage() != 'Unsupported font type.') {
       
  1165                         throw $e;
       
  1166                     }
       
  1167                     // Continue searhing
       
  1168                 }
       
  1169             }
       
  1170         }
       
  1171 
       
  1172         return null;
       
  1173     }
       
  1174 
       
  1175     /**
       
  1176      * Render the completed PDF to a string.
       
  1177      * If $newSegmentOnly is true, then only appended part of PDF is returned.
       
  1178      *
       
  1179      * @param boolean $newSegmentOnly
       
  1180      * @param resource $outputStream
       
  1181      * @return string
       
  1182      * @throws Zend_Pdf_Exception
       
  1183      */
       
  1184     public function render($newSegmentOnly = false, $outputStream = null)
       
  1185     {
       
  1186         // Save document properties if necessary
       
  1187         if ($this->properties != $this->_originalProperties) {
       
  1188             $docInfo = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
       
  1189 
       
  1190             foreach ($this->properties as $key => $value) {
       
  1191                 switch ($key) {
       
  1192                     case 'Trapped':
       
  1193                         switch ($value) {
       
  1194                             case true:
       
  1195                                 $docInfo->$key = new Zend_Pdf_Element_Name('True');
       
  1196                                 break;
       
  1197 
       
  1198                             case false:
       
  1199                                 $docInfo->$key = new Zend_Pdf_Element_Name('False');
       
  1200                                 break;
       
  1201 
       
  1202                             case null:
       
  1203                                 $docInfo->$key = new Zend_Pdf_Element_Name('Unknown');
       
  1204                                 break;
       
  1205 
       
  1206                             default:
       
  1207                                 require_once 'Zend/Pdf/Exception.php';
       
  1208                                 throw new Zend_Pdf_Exception('Wrong Trapped document property vale: \'' . $value . '\'. Only true, false and null values are allowed.');
       
  1209                                 break;
       
  1210                         }
       
  1211 
       
  1212                     case 'CreationDate':
       
  1213                         // break intentionally omitted
       
  1214                     case 'ModDate':
       
  1215                         $docInfo->$key = new Zend_Pdf_Element_String((string)$value);
       
  1216                         break;
       
  1217 
       
  1218                     case 'Title':
       
  1219                         // break intentionally omitted
       
  1220                     case 'Author':
       
  1221                         // break intentionally omitted
       
  1222                     case 'Subject':
       
  1223                         // break intentionally omitted
       
  1224                     case 'Keywords':
       
  1225                         // break intentionally omitted
       
  1226                     case 'Creator':
       
  1227                         // break intentionally omitted
       
  1228                     case 'Producer':
       
  1229                         if (extension_loaded('mbstring') === true) {
       
  1230                             $detected = mb_detect_encoding($value);
       
  1231                             if ($detected !== 'ASCII') {
       
  1232                                 $value = "\xfe\xff" . mb_convert_encoding($value, 'UTF-16', $detected);
       
  1233                             }
       
  1234                         }
       
  1235                         $docInfo->$key = new Zend_Pdf_Element_String((string)$value);
       
  1236                         break;
       
  1237 
       
  1238                     default:
       
  1239                         // Set property using PDF type based on PHP type
       
  1240                         $docInfo->$key = Zend_Pdf_Element::phpToPdf($value);
       
  1241                         break;
       
  1242                 }
       
  1243             }
       
  1244 
       
  1245             $this->_trailer->Info = $docInfo;
       
  1246         }
       
  1247 
       
  1248         $this->_dumpPages();
       
  1249         $this->_dumpNamedDestinations();
       
  1250         $this->_dumpOutlines();
       
  1251 
       
  1252         // Check, that PDF file was modified
       
  1253         // File is always modified by _dumpPages() now, but future implementations may eliminate this.
       
  1254         if (!$this->_objFactory->isModified()) {
       
  1255             if ($newSegmentOnly) {
       
  1256                 // Do nothing, return
       
  1257                 return '';
       
  1258             }
       
  1259 
       
  1260             if ($outputStream === null) {
       
  1261                 return $this->_trailer->getPDFString();
       
  1262             } else {
       
  1263                 $pdfData = $this->_trailer->getPDFString();
       
  1264                 while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) {
       
  1265                     $pdfData = substr($pdfData, $byteCount);
       
  1266                 }
       
  1267 
       
  1268                 return '';
       
  1269             }
       
  1270         }
       
  1271 
       
  1272         // offset (from a start of PDF file) of new PDF file segment
       
  1273         $offset = $this->_trailer->getPDFLength();
       
  1274         // Last Object number in a list of free objects
       
  1275         $lastFreeObject = $this->_trailer->getLastFreeObject();
       
  1276 
       
  1277         // Array of cross-reference table subsections
       
  1278         $xrefTable = array();
       
  1279         // Object numbers of first objects in each subsection
       
  1280         $xrefSectionStartNums = array();
       
  1281 
       
  1282         // Last cross-reference table subsection
       
  1283         $xrefSection = array();
       
  1284         // Dummy initialization of the first element (specail case - header of linked list of free objects).
       
  1285         $xrefSection[] = 0;
       
  1286         $xrefSectionStartNums[] = 0;
       
  1287         // Object number of last processed PDF object.
       
  1288         // Used to manage cross-reference subsections.
       
  1289         // Initialized by zero (specail case - header of linked list of free objects).
       
  1290         $lastObjNum = 0;
       
  1291 
       
  1292         if ($outputStream !== null) {
       
  1293             if (!$newSegmentOnly) {
       
  1294                 $pdfData = $this->_trailer->getPDFString();
       
  1295                 while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) {
       
  1296                     $pdfData = substr($pdfData, $byteCount);
       
  1297                 }
       
  1298             }
       
  1299         } else {
       
  1300             $pdfSegmentBlocks = ($newSegmentOnly) ? array() : array($this->_trailer->getPDFString());
       
  1301         }
       
  1302 
       
  1303         // Iterate objects to create new reference table
       
  1304         foreach ($this->_objFactory->listModifiedObjects() as $updateInfo) {
       
  1305             $objNum = $updateInfo->getObjNum();
       
  1306 
       
  1307             if ($objNum - $lastObjNum != 1) {
       
  1308                 // Save cross-reference table subsection and start new one
       
  1309                 $xrefTable[] = $xrefSection;
       
  1310                 $xrefSection = array();
       
  1311                 $xrefSectionStartNums[] = $objNum;
       
  1312             }
       
  1313 
       
  1314             if ($updateInfo->isFree()) {
       
  1315                 // Free object cross-reference table entry
       
  1316                 $xrefSection[]  = sprintf("%010d %05d f \n", $lastFreeObject, $updateInfo->getGenNum());
       
  1317                 $lastFreeObject = $objNum;
       
  1318             } else {
       
  1319                 // In-use object cross-reference table entry
       
  1320                 $xrefSection[]  = sprintf("%010d %05d n \n", $offset, $updateInfo->getGenNum());
       
  1321 
       
  1322                 $pdfBlock = $updateInfo->getObjectDump();
       
  1323                 $offset += strlen($pdfBlock);
       
  1324 
       
  1325                 if ($outputStream === null) {
       
  1326                     $pdfSegmentBlocks[] = $pdfBlock;
       
  1327                 } else {
       
  1328                     while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) {
       
  1329                         $pdfBlock = substr($pdfBlock, $byteCount);
       
  1330                     }
       
  1331                 }
       
  1332             }
       
  1333             $lastObjNum = $objNum;
       
  1334         }
       
  1335         // Save last cross-reference table subsection
       
  1336         $xrefTable[] = $xrefSection;
       
  1337 
       
  1338         // Modify first entry (specail case - header of linked list of free objects).
       
  1339         $xrefTable[0][0] = sprintf("%010d 65535 f \n", $lastFreeObject);
       
  1340 
       
  1341         $xrefTableStr = "xref\n";
       
  1342         foreach ($xrefTable as $sectId => $xrefSection) {
       
  1343             $xrefTableStr .= sprintf("%d %d \n", $xrefSectionStartNums[$sectId], count($xrefSection));
       
  1344             foreach ($xrefSection as $xrefTableEntry) {
       
  1345                 $xrefTableStr .= $xrefTableEntry;
       
  1346             }
       
  1347         }
       
  1348 
       
  1349         $this->_trailer->Size->value = $this->_objFactory->getObjectCount();
       
  1350 
       
  1351         $pdfBlock = $xrefTableStr
       
  1352                  .  $this->_trailer->toString()
       
  1353                  . "startxref\n" . $offset . "\n"
       
  1354                  . "%%EOF\n";
       
  1355 
       
  1356         $this->_objFactory->cleanEnumerationShiftCache();
       
  1357 
       
  1358         if ($outputStream === null) {
       
  1359             $pdfSegmentBlocks[] = $pdfBlock;
       
  1360 
       
  1361             return implode('', $pdfSegmentBlocks);
       
  1362         } else {
       
  1363             while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) {
       
  1364                 $pdfBlock = substr($pdfBlock, $byteCount);
       
  1365             }
       
  1366 
       
  1367             return '';
       
  1368         }
       
  1369     }
       
  1370 
       
  1371 
       
  1372     /**
       
  1373      * Set the document-level JavaScript
       
  1374      *
       
  1375      * @param string $javascript
       
  1376      */
       
  1377     public function setJavaScript($javascript)
       
  1378     {
       
  1379         $this->_javaScript = $javascript;
       
  1380     }
       
  1381 
       
  1382 
       
  1383     /**
       
  1384      * Convert date to PDF format (it's close to ASN.1 (Abstract Syntax Notation
       
  1385      * One) defined in ISO/IEC 8824).
       
  1386      *
       
  1387      * @todo This really isn't the best location for this method. It should
       
  1388      *   probably actually exist as Zend_Pdf_Element_Date or something like that.
       
  1389      *
       
  1390      * @todo Address the following E_STRICT issue:
       
  1391      *   PHP Strict Standards:  date(): It is not safe to rely on the system's
       
  1392      *   timezone settings. Please use the date.timezone setting, the TZ
       
  1393      *   environment variable or the date_default_timezone_set() function. In
       
  1394      *   case you used any of those methods and you are still getting this
       
  1395      *   warning, you most likely misspelled the timezone identifier.
       
  1396      *
       
  1397      * @param integer $timestamp (optional) If omitted, uses the current time.
       
  1398      * @return string
       
  1399      */
       
  1400     public static function pdfDate($timestamp = null)
       
  1401     {
       
  1402         if ($timestamp === null) {
       
  1403             $date = date('\D\:YmdHisO');
       
  1404         } else {
       
  1405             $date = date('\D\:YmdHisO', $timestamp);
       
  1406         }
       
  1407         return substr_replace($date, '\'', -2, 0) . '\'';
       
  1408     }
       
  1409 
       
  1410 }