PageRenderTime 93ms CodeModel.GetById 56ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/vendor/zend/Zend/Pdf.php

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