PageRenderTime 708ms CodeModel.GetById 72ms app.highlight 522ms RepoModel.GetById 101ms app.codeStats 1ms

/library/Zend/Pdf.php

https://bitbucket.org/luizbrandaoj/mini-blog
PHP | 1428 lines | 756 code | 194 blank | 478 comment | 167 complexity | 20f59d0d5435052f1c6a00525db2cc96 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file