PageRenderTime 159ms CodeModel.GetById 71ms app.highlight 37ms RepoModel.GetById 43ms app.codeStats 0ms

/library/Zend/Pdf.php

https://bitbucket.org/baruffaldi/cms-php-bfcms
PHP | 900 lines | 412 code | 140 blank | 348 comment | 77 complexity | ca8b7a57ea4e4b601fda5136005fa57a 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-2008 Zend Technologies USA Inc. (http://www.zend.com)
 18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 19 */
 20
 21
 22/** Zend_Pdf_Exception */
 23require_once 'Zend/Pdf/Exception.php';
 24
 25/** Zend_Pdf_Page */
 26require_once 'Zend/Pdf/Page.php';
 27
 28/** Zend_Pdf_Cmap */
 29require_once 'Zend/Pdf/Cmap.php';
 30
 31/** Zend_Pdf_Font */
 32require_once 'Zend/Pdf/Font.php';
 33
 34/** Zend_Pdf_Style */
 35require_once 'Zend/Pdf/Style.php';
 36
 37/** Zend_Pdf_Parser */
 38require_once 'Zend/Pdf/Parser.php';
 39
 40/** Zend_Pdf_Trailer */
 41require_once 'Zend/Pdf/Trailer.php';
 42
 43/** Zend_Pdf_Trailer_Generator */
 44require_once 'Zend/Pdf/Trailer/Generator.php';
 45
 46/** Zend_Pdf_Color */
 47require_once 'Zend/Pdf/Color.php';
 48
 49/** Zend_Pdf_Color_GrayScale */
 50require_once 'Zend/Pdf/Color/GrayScale.php';
 51
 52/** Zend_Pdf_Color_Rgb */
 53require_once 'Zend/Pdf/Color/Rgb.php';
 54
 55/** Zend_Pdf_Color_Cmyk */
 56require_once 'Zend/Pdf/Color/Cmyk.php';
 57
 58/** Zend_Pdf_Color_Html */
 59require_once 'Zend/Pdf/Color/Html.php';
 60
 61/** Zend_Pdf_Image */
 62require_once 'Zend/Pdf/Resource/Image.php';
 63
 64/** Zend_Pdf_Image */
 65require_once 'Zend/Pdf/Image.php';
 66
 67/** Zend_Pdf_Image_Jpeg */
 68require_once 'Zend/Pdf/Resource/Image/Jpeg.php';
 69
 70/** Zend_Pdf_Image_Tiff */
 71require_once 'Zend/Pdf/Resource/Image/Tiff.php';
 72
 73/** Zend_Pdf_Image_Png */
 74require_once 'Zend/Pdf/Resource/Image/Png.php';
 75
 76
 77/** Zend_Memory */
 78require_once 'Zend/Memory.php';
 79
 80
 81/**
 82 * General entity which describes PDF document.
 83 * It implements document abstraction with a document level operations.
 84 *
 85 * Class is used to create new PDF document or load existing document.
 86 * See details in a class constructor description
 87 *
 88 * Class agregates document level properties and entities (pages, bookmarks,
 89 * document level actions, attachments, form object, etc)
 90 *
 91 * @category   Zend
 92 * @package    Zend_Pdf
 93 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 94 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 95 */
 96class Zend_Pdf
 97{
 98  /**** Class Constants ****/
 99
100    /**
101     * Version number of generated PDF documents.
102     */
103    const PDF_VERSION = 1.4;
104
105    /**
106     * PDF file header.
107     */
108    const PDF_HEADER  = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
109
110
111
112    /**
113     * Pages collection
114     *
115     * @todo implement it as a class, which supports ArrayAccess and Iterator interfaces,
116     *       to provide incremental parsing and pages tree updating.
117     *       That will give good performance and memory (PDF size) benefits.
118     *
119     * @var array   - array of Zend_Pdf_Page object
120     */
121    public $pages = array();
122
123    /**
124     * Document properties
125     *
126     * It's an associative array with PDF meta information, values may
127     * be string, boolean or float.
128     * Returned array could be used directly to access, add, modify or remove
129     * document properties.
130     *
131     * Standard document properties: Title (must be set for PDF/X documents), Author,
132     * Subject, Keywords (comma separated list), Creator (the name of the application,
133     * that created document, if it was converted from other format), Trapped (must be
134     * true, false or null, can not be null for PDF/X documents)
135     *
136     * @var array
137     */
138    public $properties = array();
139
140    /**
141     * Original properties set.
142     *
143     * Used for tracking properties changes
144     *
145     * @var array
146     */
147    private $_originalProperties = array();
148
149    /**
150     * Document level javascript
151     *
152     * @var string
153     */
154    private $_javaScript = null;
155
156    /**
157     * Document named actions
158     * "GoTo..." actions, used to refer document parts
159     * from outside PDF
160     *
161     * @var array   - array of Zend_Pdf_Action objects
162     */
163    private $_namedActions = array();
164
165
166    /**
167     * Pdf trailer (last or just created)
168     *
169     * @var Zend_Pdf_Trailer
170     */
171    private $_trailer = null;
172
173
174    /**
175     * PDF objects factory.
176     *
177     * @var Zend_Pdf_ElementFactory_Interface
178     */
179    private $_objFactory = null;
180
181    /**
182     * Memory manager for stream objects
183     *
184     * @var Zend_Memory_Manager|null
185     */
186    private static $_memoryManager = null;
187
188    /**
189     * Pdf file parser.
190     * It's not used, but has to be destroyed only with Zend_Pdf object
191     *
192     * @var Zend_Pdf_Parser
193     */
194    private $_parser;
195
196    /**
197     * Request used memory manager
198     *
199     * @return Zend_Memory_Manager
200     */
201    static public function getMemoryManager()
202    {
203        if (self::$_memoryManager === null) {
204            self::$_memoryManager = Zend_Memory::factory('none');
205        }
206
207        return self::$_memoryManager;
208    }
209
210    /**
211     * Set user defined memory manager
212     *
213     * @param Zend_Memory_Manager $memoryManager
214     */
215    static public function setMemoryManager(Zend_Memory_Manager $memoryManager)
216    {
217        self::$_memoryManager = $memoryManager;
218    }
219
220
221    /**
222     * Create new PDF document from a $source string
223     *
224     * @param string $source
225     * @param integer $revision
226     * @return Zend_Pdf
227     */
228    public static function parse(&$source = null, $revision = null)
229    {
230        return new Zend_Pdf($source, $revision);
231    }
232
233    /**
234     * Load PDF document from a file
235     *
236     * @param string $source
237     * @param integer $revision
238     * @return Zend_Pdf
239     */
240    public static function load($source = null, $revision = null)
241    {
242        return new Zend_Pdf($source, $revision, true);
243    }
244
245    /**
246     * Render PDF document and save it.
247     *
248     * If $updateOnly is true, then it only appends new section to the end of file.
249     *
250     * @param string $filename
251     * @param boolean $updateOnly
252     * @throws Zend_Pdf_Exception
253     */
254    public function save($filename, $updateOnly = false)
255    {
256        if (($file = @fopen($filename, $updateOnly ? 'ab':'wb')) === false ) {
257            throw new Zend_Pdf_Exception( "Can not open '$filename' file for writing." );
258        }
259
260        $this->render($updateOnly, $file);
261
262        fclose($file);
263    }
264
265    /**
266     * Creates or loads PDF document.
267     *
268     * If $source is null, then it creates a new document.
269     *
270     * If $source is a string and $load is false, then it loads document
271     * from a binary string.
272     *
273     * If $source is a string and $load is true, then it loads document
274     * from a file.
275
276     * $revision used to roll back document to specified version
277     * (0 - currtent version, 1 - previous version, 2 - ...)
278     *
279     * @param string  $source - PDF file to load
280     * @param integer $revision
281     * @throws Zend_Pdf_Exception
282     * @return Zend_Pdf
283     */
284    public function __construct($source = null, $revision = null, $load = false)
285    {
286        $this->_objFactory = Zend_Pdf_ElementFactory::createFactory(1);
287
288        if ($source !== null) {
289            $this->_parser  = new Zend_Pdf_Parser($source, $this->_objFactory, $load);
290            $this->_trailer = $this->_parser->getTrailer();
291            if ($revision !== null) {
292                $this->rollback($revision);
293            } else {
294                $this->_loadPages($this->_trailer->Root->Pages);
295            }
296
297            if ($this->_trailer->Info !== null) {
298                foreach ($this->_trailer->Info->getKeys() as $key) {
299                    $this->properties[$key] = $this->_trailer->Info->$key->value;
300                }
301
302                if (isset($this->properties['Trapped'])) {
303                    switch ($this->properties['Trapped']) {
304                        case 'True':
305                            $this->properties['Trapped'] = true;
306                            break;
307
308                        case 'False':
309                            $this->properties['Trapped'] = false;
310                            break;
311
312                        case 'Unknown':
313                            $this->properties['Trapped'] = null;
314                            break;
315
316                        default:
317                            // Wrong property value
318                            // Do nothing
319                            break;
320                    }
321                }
322
323                $this->_originalProperties = $this->properties;
324            }
325        } else {
326            $trailerDictionary = new Zend_Pdf_Element_Dictionary();
327
328            /**
329             * Document id
330             */
331            $docId = md5(uniqid(rand(), true));   // 32 byte (128 bit) identifier
332            $docIdLow  = substr($docId,  0, 16);  // first 16 bytes
333            $docIdHigh = substr($docId, 16, 16);  // second 16 bytes
334
335            $trailerDictionary->ID = new Zend_Pdf_Element_Array();
336            $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdLow);
337            $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdHigh);
338
339            $trailerDictionary->Size = new Zend_Pdf_Element_Numeric(0);
340
341            $this->_trailer    = new Zend_Pdf_Trailer_Generator($trailerDictionary);
342
343            /**
344             * Document catalog indirect object.
345             */
346            $docCatalog = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
347            $docCatalog->Type    = new Zend_Pdf_Element_Name('Catalog');
348            $docCatalog->Version = new Zend_Pdf_Element_Name(Zend_Pdf::PDF_VERSION);
349            $this->_trailer->Root = $docCatalog;
350
351            /**
352             * Pages container
353             */
354            $docPages = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
355            $docPages->Type  = new Zend_Pdf_Element_Name('Pages');
356            $docPages->Kids  = new Zend_Pdf_Element_Array();
357            $docPages->Count = new Zend_Pdf_Element_Numeric(0);
358            $docCatalog->Pages = $docPages;
359        }
360    }
361
362    /**
363     * Retrive number of revisions.
364     *
365     * @return integer
366     */
367    public function revisions()
368    {
369        $revisions = 1;
370        $currentTrailer = $this->_trailer;
371
372        while ($currentTrailer->getPrev() !== null && $currentTrailer->getPrev()->Root !== null ) {
373            $revisions++;
374            $currentTrailer = $currentTrailer->getPrev();
375        }
376
377        return $revisions++;
378    }
379
380    /**
381     * Rollback document $steps number of revisions.
382     * This method must be invoked before any changes, applied to the document.
383     * Otherwise behavior is undefined.
384     *
385     * @param integer $steps
386     */
387    public function rollback($steps)
388    {
389        for ($count = 0; $count < $steps; $count++) {
390            if ($this->_trailer->getPrev() !== null && $this->_trailer->getPrev()->Root !== null) {
391                $this->_trailer = $this->_trailer->getPrev();
392            } else {
393                break;
394            }
395        }
396        $this->_objFactory->setObjectCount($this->_trailer->Size->value);
397
398        // Mark content as modified to force new trailer generation at render time
399        $this->_trailer->Root->touch();
400
401        $this->pages = array();
402        $this->_loadPages($this->_trailer->Root->Pages);
403    }
404
405
406
407    /**
408     * List of inheritable attributesfor pages tree
409     *
410     * @var array
411     */
412    private static $_inheritableAttributes = array('Resources', 'MediaBox', 'CropBox', 'Rotate');
413
414
415    /**
416     * Load pages recursively
417     *
418     * @param Zend_Pdf_Element_Reference $pages
419     * @param array|null $attributes
420     */
421    private function _loadPages(Zend_Pdf_Element_Reference $pages, $attributes = array())
422    {
423        if ($pages->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) {
424            throw new Zend_Pdf_Exception('Wrong argument');
425        }
426
427        foreach ($pages->getKeys() as $property) {
428            if (in_array($property, self::$_inheritableAttributes)) {
429                $attributes[$property] = $pages->$property;
430                $pages->$property = null;
431            }
432        }
433
434
435        foreach ($pages->Kids->items as $child) {
436            if ($child->Type->value == 'Pages') {
437                $this->_loadPages($child, $attributes);
438            } else if ($child->Type->value == 'Page') {
439                foreach (self::$_inheritableAttributes as $property) {
440                    if ($child->$property === null && array_key_exists($property, $attributes)) {
441                        /**
442                         * Important note.
443                         * If any attribute or dependant object is an indirect object, then it's still
444                         * shared between pages.
445                         */
446                        if ($attributes[$property] instanceof Zend_Pdf_Element_Object) {
447                            $child->$property = $attributes[$property];
448                        } else {
449                            $child->$property = $this->_objFactory->newObject($attributes[$property]);
450                        }
451                    }
452                }
453                $this->pages[] = new Zend_Pdf_Page($child, $this->_objFactory);
454            }
455        }
456    }
457
458
459    /**
460     * Orginize pages to tha pages tree structure.
461     *
462     * @todo atomatically attach page to the document, if it's not done yet.
463     * @todo check, that page is attached to the current document
464     *
465     * @todo Dump pages as a balanced tree instead of a plain set.
466     */
467    private function _dumpPages()
468    {
469        $pagesContainer = $this->_trailer->Root->Pages;
470        $pagesContainer->touch();
471        $pagesContainer->Kids->items->clear();
472
473        foreach ($this->pages as $page ) {
474            $page->render($this->_objFactory);
475
476            $pageDictionary = $page->getPageDictionary();
477            $pageDictionary->touch();
478            $pageDictionary->Parent = $pagesContainer;
479
480            $pagesContainer->Kids->items[] = $pageDictionary;
481        }
482
483        $pagesContainer->Count->touch();
484        $pagesContainer->Count->value = count($this->pages);
485    }
486
487
488    /**
489     * Create page object, attached to the PDF document.
490     * Method signatures:
491     *
492     * 1. Create new page with a specified pagesize.
493     *    If $factory is null then it will be created and page must be attached to the document to be
494     *    included into output.
495     * ---------------------------------------------------------
496     * new Zend_Pdf_Page(string $pagesize);
497     * ---------------------------------------------------------
498     *
499     * 2. Create new page with a specified pagesize (in default user space units).
500     *    If $factory is null then it will be created and page must be attached to the document to be
501     *    included into output.
502     * ---------------------------------------------------------
503     * new Zend_Pdf_Page(numeric $width, numeric $height);
504     * ---------------------------------------------------------
505     *
506     * @param mixed $param1
507     * @param mixed $param2
508     * @return Zend_Pdf_Page
509     */
510    public function newPage($param1, $param2 = null)
511    {
512        if ($param2 === null) {
513            return new Zend_Pdf_Page($param1, $this->_objFactory);
514        } else {
515            return new Zend_Pdf_Page($param1, $param2, $this->_objFactory);
516        }
517    }
518
519    /**
520     * Return the document-level Metadata
521     * or null Metadata stream is not presented
522     *
523     * @return string
524     */
525    public function getMetadata()
526    {
527        if ($this->_trailer->Root->Metadata !== null) {
528            return $this->_trailer->Root->Metadata->value;
529        } else {
530            return null;
531        }
532    }
533
534    /**
535     * Sets the document-level Metadata (mast be valid XMP document)
536     *
537     * @param string $metadata
538     */
539    public function setMetadata($metadata)
540    {
541        $metadataObject = $this->_objFactory->newStreamObject($metadata);
542        $metadataObject->dictionary->Type    = new Zend_Pdf_Element_Name('Metadata');
543        $metadataObject->dictionary->Subtype = new Zend_Pdf_Element_Name('XML');
544
545        $this->_trailer->Root->Metadata = $metadataObject;
546        $this->_trailer->Root->touch();
547    }
548
549    /**
550     * Return the document-level JavaScript
551     * or null if there is no JavaScript for this document
552     *
553     * @return string
554     */
555    public function getJavaScript()
556    {
557        return $this->_javaScript;
558    }
559
560
561    /**
562     * Return an associative array containing all the named actions in the PDF.
563     * Named actions (it's always "GoTo" actions) can be used to reference from outside
564     * the PDF, ex: 'http://www.something.com/mydocument.pdf#MyAction'
565     *
566     * @return array
567     */
568    public function getNamedActions()
569    {
570        return $this->_namedActions;
571    }
572
573    /**
574     * Extract fonts attached to the document
575     *
576     * returns array of Zend_Pdf_Resource_Font_Extracted objects
577     * 
578     * @return array
579     */
580    public function extractFonts()
581    {
582        $fontResourcesUnique = array();
583        foreach ($this->pages as $page) {
584            $pageResources = $page->extractResources();
585
586            if ($pageResources->Font === null) {
587                // Page doesn't contain have any font reference
588                continue;
589            }
590            
591            $fontResources = $pageResources->Font;
592
593            foreach ($fontResources->getKeys() as $fontResourceName) {
594                $fontDictionary = $fontResources->$fontResourceName;
595    
596                if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
597                       $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
598                    // Font dictionary has to be an indirect object or object reference
599                    continue;
600                }
601    
602                $fontResourcesUnique[$fontDictionary->toString($this->_objFactory)] = $fontDictionary;
603            }
604        }
605        
606        $fonts = array();
607        foreach ($fontResourcesUnique as $resourceReference => $fontDictionary) {
608            try {
609                // Try to extract font
610                $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary);
611
612                $fonts[$resourceReference] = $extractedFont; 
613            } catch (Zend_Pdf_Exception $e) {
614                if ($e->getMessage() != 'Unsupported font type.') {
615                    throw $e;
616                }
617            }
618        }
619        
620        return $fonts;
621    } 
622
623    /**
624     * Extract font attached to the page by specific font name
625     * 
626     * $fontName should be specified in UTF-8 encoding
627     *
628     * @return Zend_Pdf_Resource_Font_Extracted|null
629     */
630    public function extractFont($fontName)
631    {
632        $fontResourcesUnique = array();
633        foreach ($this->pages as $page) {
634            $pageResources = $page->extractResources();
635            
636            if ($pageResources->Font === null) {
637                // Page doesn't contain have any font reference
638                continue;
639            }
640            
641            $fontResources = $pageResources->Font;
642
643            foreach ($fontResources->getKeys() as $fontResourceName) {
644                $fontDictionary = $fontResources->$fontResourceName;
645    
646                if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference  ||
647                       $fontDictionary instanceof Zend_Pdf_Element_Object) ) {
648                    // Font dictionary has to be an indirect object or object reference
649                    continue;
650                }
651                
652                $resourceReference = $fontDictionary->toString($this->_objFactory);
653                if (isset($fontResourcesUnique[$resourceReference])) {
654                    continue;
655                } else {
656                    // Mark resource as processed
657                    $fontResourcesUnique[$resourceReference] = 1;
658                }
659   
660                if ($fontDictionary->BaseFont->value != $fontName) {
661                    continue;
662                }
663                
664                try {
665                    // Try to extract font
666                    return new Zend_Pdf_Resource_Font_Extracted($fontDictionary); 
667                } catch (Zend_Pdf_Exception $e) {
668                    if ($e->getMessage() != 'Unsupported font type.') {
669                        throw $e;
670                    }
671                    // Continue searhing
672                }
673            }
674        }
675
676        return null;
677    } 
678    
679    /**
680     * Render the completed PDF to a string.
681     * If $newSegmentOnly is true, then only appended part of PDF is returned.
682     *
683     * @param boolean $newSegmentOnly
684     * @param resource $outputStream
685     * @return string
686     * @throws Zend_Pdf_Exception
687     */
688    public function render($newSegmentOnly = false, $outputStream = null)
689    {
690        // Save document properties if necessary
691        if ($this->properties != $this->_originalProperties) {
692            $docInfo = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary());
693
694            foreach ($this->properties as $key => $value) {
695                switch ($key) {
696                    case 'Trapped':
697                        switch ($value) {
698                            case true:
699                                $docInfo->$key = new Zend_Pdf_Element_Name('True');
700                                break;
701
702                            case false:
703                                $docInfo->$key = new Zend_Pdf_Element_Name('False');
704                                break;
705
706                            case null:
707                                $docInfo->$key = new Zend_Pdf_Element_Name('Unknown');
708                                break;
709
710                            default:
711                                throw new Zend_Pdf_Exception('Wrong Trapped document property vale: \'' . $value . '\'. Only true, false and null values are allowed.');
712                                break;
713                        }
714
715                    case 'CreationDate':
716                        // break intentionally omitted
717                    case 'ModDate':
718                        $docInfo->$key = new Zend_Pdf_Element_String((string)$value);
719                        break;
720
721                    case 'Title':
722                        // break intentionally omitted
723                    case 'Author':
724                        // break intentionally omitted
725                    case 'Subject':
726                        // break intentionally omitted
727                    case 'Keywords':
728                        // break intentionally omitted
729                    case 'Creator':
730                        // break intentionally omitted
731                    case 'Producer':
732                        // break intentionally omitted
733                    default:
734                        $docInfo->$key = new Zend_Pdf_Element_String((string)$value);
735                        break;
736                }
737            }
738
739            $this->_trailer->Info = $docInfo;
740        }
741
742        $this->_dumpPages();
743
744        // Check, that PDF file was modified
745        // File is always modified by _dumpPages() now, but future implementations may eliminate this.
746        if (!$this->_objFactory->isModified()) {
747            if ($newSegmentOnly) {
748                // Do nothing, return
749                return '';
750            }
751
752            if ($outputStream === null) {
753                return $this->_trailer->getPDFString();
754            } else {
755                $pdfData = $this->_trailer->getPDFString();
756                while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) {
757                    $pdfData = substr($pdfData, $byteCount);
758                }
759
760                return '';
761            }
762        }
763
764        // offset (from a start of PDF file) of new PDF file segment
765        $offset = $this->_trailer->getPDFLength();
766        // Last Object number in a list of free objects
767        $lastFreeObject = $this->_trailer->getLastFreeObject();
768
769        // Array of cross-reference table subsections
770        $xrefTable = array();
771        // Object numbers of first objects in each subsection
772        $xrefSectionStartNums = array();
773
774        // Last cross-reference table subsection
775        $xrefSection = array();
776        // Dummy initialization of the first element (specail case - header of linked list of free objects).
777        $xrefSection[] = 0;
778        $xrefSectionStartNums[] = 0;
779        // Object number of last processed PDF object.
780        // Used to manage cross-reference subsections.
781        // Initialized by zero (specail case - header of linked list of free objects).
782        $lastObjNum = 0;
783
784        if ($outputStream !== null) {
785            if (!$newSegmentOnly) {
786                $pdfData = $this->_trailer->getPDFString();
787                while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) {
788                    $pdfData = substr($pdfData, $byteCount);
789                }
790            }
791        } else {
792            $pdfSegmentBlocks = ($newSegmentOnly) ? array() : array($this->_trailer->getPDFString());
793        }
794
795        // Iterate objects to create new reference table
796        foreach ($this->_objFactory->listModifiedObjects() as $updateInfo) {
797            $objNum = $updateInfo->getObjNum();
798
799            if ($objNum - $lastObjNum != 1) {
800                // Save cross-reference table subsection and start new one
801                $xrefTable[] = $xrefSection;
802                $xrefSection = array();
803                $xrefSectionStartNums[] = $objNum;
804            }
805
806            if ($updateInfo->isFree()) {
807                // Free object cross-reference table entry
808                $xrefSection[]  = sprintf("%010d %05d f \n", $lastFreeObject, $updateInfo->getGenNum());
809                $lastFreeObject = $objNum;
810            } else {
811                // In-use object cross-reference table entry
812                $xrefSection[]  = sprintf("%010d %05d n \n", $offset, $updateInfo->getGenNum());
813
814                $pdfBlock = $updateInfo->getObjectDump();
815                $offset += strlen($pdfBlock);
816
817                if ($outputStream === null) {
818                    $pdfSegmentBlocks[] = $pdfBlock;
819                } else {
820                    while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) {
821                        $pdfBlock = substr($pdfBlock, $byteCount);
822                    }
823                }
824            }
825            $lastObjNum = $objNum;
826        }
827        // Save last cross-reference table subsection
828        $xrefTable[] = $xrefSection;
829
830        // Modify first entry (specail case - header of linked list of free objects).
831        $xrefTable[0][0] = sprintf("%010d 65535 f \n", $lastFreeObject);
832
833        $xrefTableStr = "xref\n";
834        foreach ($xrefTable as $sectId => $xrefSection) {
835            $xrefTableStr .= sprintf("%d %d \n", $xrefSectionStartNums[$sectId], count($xrefSection));
836            foreach ($xrefSection as $xrefTableEntry) {
837                $xrefTableStr .= $xrefTableEntry;
838            }
839        }
840
841        $this->_trailer->Size->value = $this->_objFactory->getObjectCount();
842
843        $pdfBlock = $xrefTableStr
844                 .  $this->_trailer->toString()
845                 . "startxref\n" . $offset . "\n"
846                 . "%%EOF\n";
847
848        if ($outputStream === null) {
849            $pdfSegmentBlocks[] = $pdfBlock;
850
851            return implode('', $pdfSegmentBlocks);
852        } else {
853            while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) {
854                $pdfBlock = substr($pdfBlock, $byteCount);
855            }
856
857            return '';
858        }
859    }
860
861
862    /**
863     * Set the document-level JavaScript
864     *
865     * @param string $javascript
866     */
867    public function setJavaScript($javascript)
868    {
869        $this->_javaScript = $javascript;
870    }
871
872
873    /**
874     * Convert date to PDF format (it's close to ASN.1 (Abstract Syntax Notation
875     * One) defined in ISO/IEC 8824).
876     *
877     * @todo This really isn't the best location for this method. It should
878     *   probably actually exist as Zend_Pdf_Element_Date or something like that.
879     *
880     * @todo Address the following E_STRICT issue:
881     *   PHP Strict Standards:  date(): It is not safe to rely on the system's
882     *   timezone settings. Please use the date.timezone setting, the TZ
883     *   environment variable or the date_default_timezone_set() function. In
884     *   case you used any of those methods and you are still getting this
885     *   warning, you most likely misspelled the timezone identifier.
886     *
887     * @param integer $timestamp (optional) If omitted, uses the current time.
888     * @return string
889     */
890    public static function pdfDate($timestamp = null)
891    {
892        if (is_null($timestamp)) {
893            $date = date('\D\:YmdHisO');
894        } else {
895            $date = date('\D\:YmdHisO', $timestamp);
896        }
897        return substr_replace($date, '\'', -2, 0) . '\'';
898    }
899
900}