File indexing completed on 2024-12-22 05:37:17
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Pdf 0017 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0018 * @license http://framework.zend.com/license/new-bsd New BSD License 0019 * @version $Id$ 0020 */ 0021 0022 0023 /** User land classes and interfaces turned on by Zend/Pdf.php file inclusion. */ 0024 /** @todo Section should be removed with ZF 2.0 release as obsolete */ 0025 0026 /** Zend_Pdf_Page */ 0027 // require_once 'Zend/Pdf/Page.php'; 0028 0029 /** Zend_Pdf_Style */ 0030 // require_once 'Zend/Pdf/Style.php'; 0031 0032 /** Zend_Pdf_Color_GrayScale */ 0033 // require_once 'Zend/Pdf/Color/GrayScale.php'; 0034 0035 /** Zend_Pdf_Color_Rgb */ 0036 // require_once 'Zend/Pdf/Color/Rgb.php'; 0037 0038 /** Zend_Pdf_Color_Cmyk */ 0039 // require_once 'Zend/Pdf/Color/Cmyk.php'; 0040 0041 /** Zend_Pdf_Color_Html */ 0042 // require_once 'Zend/Pdf/Color/Html.php'; 0043 0044 /** Zend_Pdf_Image */ 0045 // require_once 'Zend/Pdf/Image.php'; 0046 0047 /** Zend_Pdf_Font */ 0048 // require_once 'Zend/Pdf/Font.php'; 0049 0050 /** Zend_Pdf_Resource_Extractor */ 0051 // require_once 'Zend/Pdf/Resource/Extractor.php'; 0052 0053 /** Zend_Pdf_Canvas */ 0054 // require_once 'Zend/Pdf/Canvas.php'; 0055 0056 0057 /** Internally used classes */ 0058 // require_once 'Zend/Pdf/Element.php'; 0059 // require_once 'Zend/Pdf/Element/Array.php'; 0060 // require_once 'Zend/Pdf/Element/String/Binary.php'; 0061 // require_once 'Zend/Pdf/Element/Boolean.php'; 0062 // require_once 'Zend/Pdf/Element/Dictionary.php'; 0063 // require_once 'Zend/Pdf/Element/Name.php'; 0064 // require_once 'Zend/Pdf/Element/Null.php'; 0065 // require_once 'Zend/Pdf/Element/Numeric.php'; 0066 // require_once 'Zend/Pdf/Element/String.php'; 0067 0068 0069 /** 0070 * General entity which describes PDF document. 0071 * It implements document abstraction with a document level operations. 0072 * 0073 * Class is used to create new PDF document or load existing document. 0074 * See details in a class constructor description 0075 * 0076 * Class agregates document level properties and entities (pages, bookmarks, 0077 * document level actions, attachments, form object, etc) 0078 * 0079 * @category Zend 0080 * @package Zend_Pdf 0081 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0082 * @license http://framework.zend.com/license/new-bsd New BSD License 0083 */ 0084 class Zend_Pdf 0085 { 0086 /**** Class Constants ****/ 0087 0088 /** 0089 * Version number of generated PDF documents. 0090 */ 0091 const PDF_VERSION = '1.4'; 0092 0093 /** 0094 * PDF file header. 0095 */ 0096 const PDF_HEADER = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n"; 0097 0098 /** 0099 * Form field options 0100 */ 0101 const PDF_FORM_FIELD_READONLY = 1; 0102 const PDF_FORM_FIELD_REQUIRED = 2; 0103 const PDF_FORM_FIELD_NOEXPORT = 4; 0104 0105 /** 0106 * Pages collection 0107 * 0108 * @todo implement it as a class, which supports ArrayAccess and Iterator interfaces, 0109 * to provide incremental parsing and pages tree updating. 0110 * That will give good performance and memory (PDF size) benefits. 0111 * 0112 * @var array - array of Zend_Pdf_Page object 0113 */ 0114 public $pages = array(); 0115 0116 /** 0117 * Document properties 0118 * 0119 * It's an associative array with PDF meta information, values may 0120 * be string, boolean or float. 0121 * Returned array could be used directly to access, add, modify or remove 0122 * document properties. 0123 * 0124 * Standard document properties: Title (must be set for PDF/X documents), Author, 0125 * Subject, Keywords (comma separated list), Creator (the name of the application, 0126 * that created document, if it was converted from other format), Trapped (must be 0127 * true, false or null, can not be null for PDF/X documents) 0128 * 0129 * @var array 0130 */ 0131 public $properties = array(); 0132 0133 /** 0134 * Original properties set. 0135 * 0136 * Used for tracking properties changes 0137 * 0138 * @var array 0139 */ 0140 protected $_originalProperties = array(); 0141 0142 /** 0143 * Document level javascript 0144 * 0145 * @var string 0146 */ 0147 protected $_javaScript = null; 0148 0149 /** 0150 * Document named destinations or "GoTo..." actions, used to refer 0151 * document parts from outside PDF 0152 * 0153 * @var array - array of Zend_Pdf_Target objects 0154 */ 0155 protected $_namedTargets = array(); 0156 0157 /** 0158 * Document outlines 0159 * 0160 * @var array - array of Zend_Pdf_Outline objects 0161 */ 0162 public $outlines = array(); 0163 0164 /** 0165 * Original document outlines list 0166 * Used to track outlines update 0167 * 0168 * @var array - array of Zend_Pdf_Outline objects 0169 */ 0170 protected $_originalOutlines = array(); 0171 0172 /** 0173 * Original document outlines open elements count 0174 * Used to track outlines update 0175 * 0176 * @var integer 0177 */ 0178 protected $_originalOpenOutlinesCount = 0; 0179 0180 /** 0181 * Pdf trailer (last or just created) 0182 * 0183 * @var Zend_Pdf_Trailer 0184 */ 0185 protected $_trailer = null; 0186 0187 /** 0188 * PDF objects factory. 0189 * 0190 * @var Zend_Pdf_ElementFactory_Interface 0191 */ 0192 protected $_objFactory = null; 0193 0194 /** 0195 * Memory manager for stream objects 0196 * 0197 * @var Zend_Memory_Manager|null 0198 */ 0199 protected static $_memoryManager = null; 0200 0201 /** 0202 * Pdf file parser. 0203 * It's not used, but has to be destroyed only with Zend_Pdf object 0204 * 0205 * @var Zend_Pdf_Parser 0206 */ 0207 protected $_parser; 0208 0209 /** 0210 * List of inheritable attributesfor pages tree 0211 * 0212 * @var array 0213 */ 0214 protected static $_inheritableAttributes = array('Resources', 'MediaBox', 'CropBox', 'Rotate'); 0215 0216 /** 0217 * List of form fields 0218 * 0219 * @var array - Associative array, key: name of form field, value: Zend_Pdf_Element 0220 */ 0221 protected $_formFields = array(); 0222 0223 /** 0224 * True if the object is a newly created PDF document (affects save() method behavior) 0225 * False otherwise 0226 * 0227 * @var boolean 0228 */ 0229 protected $_isNewDocument = true; 0230 0231 /** 0232 * Request used memory manager 0233 * 0234 * @return Zend_Memory_Manager 0235 */ 0236 static public function getMemoryManager() 0237 { 0238 if (self::$_memoryManager === null) { 0239 // require_once 'Zend/Memory.php'; 0240 self::$_memoryManager = Zend_Memory::factory('none'); 0241 } 0242 0243 return self::$_memoryManager; 0244 } 0245 0246 /** 0247 * Set user defined memory manager 0248 * 0249 * @param Zend_Memory_Manager $memoryManager 0250 */ 0251 static public function setMemoryManager(Zend_Memory_Manager $memoryManager) 0252 { 0253 self::$_memoryManager = $memoryManager; 0254 } 0255 0256 0257 /** 0258 * Create new PDF document from a $source string 0259 * 0260 * @param string $source 0261 * @param integer $revision 0262 * @return Zend_Pdf 0263 */ 0264 public static function parse(&$source = null, $revision = null) 0265 { 0266 return new Zend_Pdf($source, $revision); 0267 } 0268 0269 /** 0270 * Load PDF document from a file 0271 * 0272 * @param string $source 0273 * @param integer $revision 0274 * @return Zend_Pdf 0275 */ 0276 public static function load($source = null, $revision = null) 0277 { 0278 return new Zend_Pdf($source, $revision, true); 0279 } 0280 0281 /** 0282 * Render PDF document and save it. 0283 * 0284 * If $updateOnly is true and it's not a new document, then it only 0285 * appends new section to the end of file. 0286 * 0287 * @param string $filename 0288 * @param boolean $updateOnly 0289 * @throws Zend_Pdf_Exception 0290 */ 0291 public function save($filename, $updateOnly = false) 0292 { 0293 if (($file = @fopen($filename, $updateOnly ? 'ab':'wb')) === false ) { 0294 // require_once 'Zend/Pdf/Exception.php'; 0295 throw new Zend_Pdf_Exception( "Can not open '$filename' file for writing." ); 0296 } 0297 0298 $this->render($updateOnly, $file); 0299 0300 fclose($file); 0301 } 0302 0303 /** 0304 * Creates or loads PDF document. 0305 * 0306 * If $source is null, then it creates a new document. 0307 * 0308 * If $source is a string and $load is false, then it loads document 0309 * from a binary string. 0310 * 0311 * If $source is a string and $load is true, then it loads document 0312 * from a file. 0313 * $revision used to roll back document to specified version 0314 * (0 - current version, 1 - previous version, 2 - ...) 0315 * 0316 * @param string $source - PDF file to load 0317 * @param integer $revision 0318 * @param bool $load 0319 * @throws Zend_Pdf_Exception 0320 * @return Zend_Pdf 0321 */ 0322 public function __construct($source = null, $revision = null, $load = false) 0323 { 0324 // require_once 'Zend/Pdf/ElementFactory.php'; 0325 $this->_objFactory = Zend_Pdf_ElementFactory::createFactory(1); 0326 0327 if ($source !== null) { 0328 // require_once 'Zend/Pdf/Parser.php'; 0329 $this->_parser = new Zend_Pdf_Parser($source, $this->_objFactory, $load); 0330 $this->_pdfHeaderVersion = $this->_parser->getPDFVersion(); 0331 $this->_trailer = $this->_parser->getTrailer(); 0332 if ($this->_trailer->Encrypt !== null) { 0333 // require_once 'Zend/Pdf/Exception.php'; 0334 throw new Zend_Pdf_Exception('Encrypted document modification is not supported'); 0335 } 0336 if ($revision !== null) { 0337 $this->rollback($revision); 0338 } else { 0339 $this->_loadPages($this->_trailer->Root->Pages); 0340 } 0341 0342 $this->_loadNamedDestinations($this->_trailer->Root, $this->_parser->getPDFVersion()); 0343 $this->_loadOutlines($this->_trailer->Root); 0344 $this->_loadJavaScript($this->_trailer->Root); 0345 $this->_loadFormFields($this->_trailer->Root); 0346 0347 if ($this->_trailer->Info !== null) { 0348 $this->properties = $this->_trailer->Info->toPhp(); 0349 0350 if (isset($this->properties['Trapped'])) { 0351 switch ($this->properties['Trapped']) { 0352 case 'True': 0353 $this->properties['Trapped'] = true; 0354 break; 0355 0356 case 'False': 0357 $this->properties['Trapped'] = false; 0358 break; 0359 0360 case 'Unknown': 0361 $this->properties['Trapped'] = null; 0362 break; 0363 0364 default: 0365 // Wrong property value 0366 // Do nothing 0367 break; 0368 } 0369 } 0370 0371 $this->_originalProperties = $this->properties; 0372 } 0373 0374 $this->_isNewDocument = false; 0375 } else { 0376 $this->_pdfHeaderVersion = Zend_Pdf::PDF_VERSION; 0377 0378 $trailerDictionary = new Zend_Pdf_Element_Dictionary(); 0379 0380 /** 0381 * Document id 0382 */ 0383 $docId = md5(uniqid(rand(), true)); // 32 byte (128 bit) identifier 0384 $docIdLow = substr($docId, 0, 16); // first 16 bytes 0385 $docIdHigh = substr($docId, 16, 16); // second 16 bytes 0386 0387 $trailerDictionary->ID = new Zend_Pdf_Element_Array(); 0388 $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdLow); 0389 $trailerDictionary->ID->items[] = new Zend_Pdf_Element_String_Binary($docIdHigh); 0390 0391 $trailerDictionary->Size = new Zend_Pdf_Element_Numeric(0); 0392 0393 // require_once 'Zend/Pdf/Trailer/Generator.php'; 0394 $this->_trailer = new Zend_Pdf_Trailer_Generator($trailerDictionary); 0395 0396 /** 0397 * Document catalog indirect object. 0398 */ 0399 $docCatalog = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary()); 0400 $docCatalog->Type = new Zend_Pdf_Element_Name('Catalog'); 0401 $docCatalog->Version = new Zend_Pdf_Element_Name(Zend_Pdf::PDF_VERSION); 0402 $this->_trailer->Root = $docCatalog; 0403 0404 /** 0405 * Pages container 0406 */ 0407 $docPages = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary()); 0408 $docPages->Type = new Zend_Pdf_Element_Name('Pages'); 0409 $docPages->Kids = new Zend_Pdf_Element_Array(); 0410 $docPages->Count = new Zend_Pdf_Element_Numeric(0); 0411 $docCatalog->Pages = $docPages; 0412 } 0413 } 0414 0415 /** 0416 * Retrive number of revisions. 0417 * 0418 * @return integer 0419 */ 0420 public function revisions() 0421 { 0422 $revisions = 1; 0423 $currentTrailer = $this->_trailer; 0424 0425 while ($currentTrailer->getPrev() !== null && $currentTrailer->getPrev()->Root !== null ) { 0426 $revisions++; 0427 $currentTrailer = $currentTrailer->getPrev(); 0428 } 0429 0430 return $revisions++; 0431 } 0432 0433 /** 0434 * Rollback document $steps number of revisions. 0435 * This method must be invoked before any changes, applied to the document. 0436 * Otherwise behavior is undefined. 0437 * 0438 * @param integer $steps 0439 */ 0440 public function rollback($steps) 0441 { 0442 for ($count = 0; $count < $steps; $count++) { 0443 if ($this->_trailer->getPrev() !== null && $this->_trailer->getPrev()->Root !== null) { 0444 $this->_trailer = $this->_trailer->getPrev(); 0445 } else { 0446 break; 0447 } 0448 } 0449 $this->_objFactory->setObjectCount($this->_trailer->Size->value); 0450 0451 // Mark content as modified to force new trailer generation at render time 0452 $this->_trailer->Root->touch(); 0453 0454 $this->pages = array(); 0455 $this->_loadPages($this->_trailer->Root->Pages); 0456 } 0457 0458 /** 0459 * Load pages recursively 0460 * 0461 * @param Zend_Pdf_Element_Reference $pages 0462 * @param array|null $attributes 0463 * @throws Zend_Pdf_Exception 0464 */ 0465 protected function _loadPages(Zend_Pdf_Element_Reference $pages, $attributes = array()) 0466 { 0467 if ($pages->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) { 0468 // require_once 'Zend/Pdf/Exception.php'; 0469 throw new Zend_Pdf_Exception('Wrong argument'); 0470 } 0471 0472 foreach ($pages->getKeys() as $property) { 0473 if (in_array($property, self::$_inheritableAttributes)) { 0474 $attributes[$property] = $pages->$property; 0475 $pages->$property = null; 0476 } 0477 } 0478 0479 0480 foreach ($pages->Kids->items as $child) { 0481 if ($child->Type->value == 'Pages') { 0482 $this->_loadPages($child, $attributes); 0483 } else if ($child->Type->value == 'Page') { 0484 foreach (self::$_inheritableAttributes as $property) { 0485 if ($child->$property === null && array_key_exists($property, $attributes)) { 0486 /** 0487 * Important note. 0488 * If any attribute or dependant object is an indirect object, then it's still 0489 * shared between pages. 0490 */ 0491 if ($attributes[$property] instanceof Zend_Pdf_Element_Object || 0492 $attributes[$property] instanceof Zend_Pdf_Element_Reference) { 0493 $child->$property = $attributes[$property]; 0494 } else { 0495 $child->$property = $this->_objFactory->newObject($attributes[$property]); 0496 } 0497 } 0498 } 0499 0500 // require_once 'Zend/Pdf/Page.php'; 0501 $this->pages[] = new Zend_Pdf_Page($child, $this->_objFactory); 0502 } 0503 } 0504 } 0505 0506 /** 0507 * Load named destinations recursively 0508 * 0509 * @param Zend_Pdf_Element_Reference $root Document catalog entry 0510 * @param string $pdfHeaderVersion 0511 * @throws Zend_Pdf_Exception 0512 */ 0513 protected function _loadNamedDestinations(Zend_Pdf_Element_Reference $root, $pdfHeaderVersion) 0514 { 0515 if ($root->Version !== null && version_compare($root->Version->value, $pdfHeaderVersion, '>')) { 0516 $versionIs_1_2_plus = version_compare($root->Version->value, '1.1', '>'); 0517 } else { 0518 $versionIs_1_2_plus = version_compare($pdfHeaderVersion, '1.1', '>'); 0519 } 0520 0521 if ($versionIs_1_2_plus) { 0522 // PDF version is 1.2+ 0523 // Look for Destinations structure at Name dictionary 0524 if ($root->Names !== null && $root->Names->Dests !== null) { 0525 // require_once 'Zend/Pdf/NameTree.php'; 0526 // require_once 'Zend/Pdf/Target.php'; 0527 foreach (new Zend_Pdf_NameTree($root->Names->Dests) as $name => $destination) { 0528 $this->_namedTargets[$name] = Zend_Pdf_Target::load($destination); 0529 } 0530 } 0531 } else { 0532 // PDF version is 1.1 (or earlier) 0533 // Look for Destinations sructure at Dest entry of document catalog 0534 if ($root->Dests !== null) { 0535 if ($root->Dests->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) { 0536 // require_once 'Zend/Pdf/Exception.php'; 0537 throw new Zend_Pdf_Exception('Document catalog Dests entry must be a dictionary.'); 0538 } 0539 0540 // require_once 'Zend/Pdf/Target.php'; 0541 foreach ($root->Dests->getKeys() as $destKey) { 0542 $this->_namedTargets[$destKey] = Zend_Pdf_Target::load($root->Dests->$destKey); 0543 } 0544 } 0545 } 0546 } 0547 0548 /** 0549 * Load outlines recursively 0550 * 0551 * @param Zend_Pdf_Element_Reference $root Document catalog entry 0552 * @throws Zend_Pdf_Exception 0553 */ 0554 protected function _loadOutlines(Zend_Pdf_Element_Reference $root) 0555 { 0556 if ($root->Outlines === null) { 0557 return; 0558 } 0559 0560 if ($root->Outlines->getType() != Zend_Pdf_Element::TYPE_DICTIONARY) { 0561 // require_once 'Zend/Pdf/Exception.php'; 0562 throw new Zend_Pdf_Exception('Document catalog Outlines entry must be a dictionary.'); 0563 } 0564 0565 if ($root->Outlines->Type !== null && $root->Outlines->Type->value != 'Outlines') { 0566 // require_once 'Zend/Pdf/Exception.php'; 0567 throw new Zend_Pdf_Exception('Outlines Type entry must be an \'Outlines\' string.'); 0568 } 0569 0570 if ($root->Outlines->First === null) { 0571 return; 0572 } 0573 0574 $outlineDictionary = $root->Outlines->First; 0575 $processedDictionaries = new SplObjectStorage(); 0576 while ($outlineDictionary !== null && !$processedDictionaries->contains($outlineDictionary)) { 0577 $processedDictionaries->attach($outlineDictionary); 0578 0579 // require_once 'Zend/Pdf/Outline/Loaded.php'; 0580 $this->outlines[] = new Zend_Pdf_Outline_Loaded($outlineDictionary); 0581 0582 $outlineDictionary = $outlineDictionary->Next; 0583 } 0584 0585 $this->_originalOutlines = $this->outlines; 0586 0587 if ($root->Outlines->Count !== null) { 0588 $this->_originalOpenOutlinesCount = $root->Outlines->Count->value; 0589 } 0590 } 0591 0592 /** 0593 * Load JavaScript 0594 * 0595 * Populates the _javaScript string, for later use of getJavaScript method. 0596 * 0597 * @param Zend_Pdf_Element_Reference $root Document catalog entry 0598 */ 0599 protected function _loadJavaScript(Zend_Pdf_Element_Reference $root) 0600 { 0601 if (null === $root->Names || null === $root->Names->JavaScript 0602 || null === $root->Names->JavaScript->Names 0603 ) { 0604 return; 0605 } 0606 0607 foreach ($root->Names->JavaScript->Names->items as $item) { 0608 if ($item instanceof Zend_Pdf_Element_Reference 0609 && $item->S->value === 'JavaScript' 0610 ) { 0611 $this->_javaScript[] = $item->JS->value; 0612 } 0613 } 0614 } 0615 0616 /** 0617 * Load form fields 0618 * 0619 * Populates the _formFields array, for later lookup of fields by name 0620 * 0621 * @param Zend_Pdf_Element_Reference $root Document catalog entry 0622 */ 0623 protected function _loadFormFields(Zend_Pdf_Element_Reference $root) 0624 { 0625 if ($root->AcroForm === null || $root->AcroForm->Fields === null) { 0626 return; 0627 } 0628 0629 foreach ($root->AcroForm->Fields->items as $field) { 0630 /* We only support fields that are textfields and have a name */ 0631 if ($field->FT && $field->FT->value == 'Tx' && $field->T 0632 && $field->T !== null 0633 ) { 0634 $this->_formFields[$field->T->value] = $field; 0635 } 0636 } 0637 0638 if (!$root->AcroForm->NeedAppearances 0639 || !$root->AcroForm->NeedAppearances->value 0640 ) { 0641 /* Ask the .pdf viewer to generate its own appearance data, so we do not have to */ 0642 $root->AcroForm->add( 0643 new Zend_Pdf_Element_Name('NeedAppearances'), 0644 new Zend_Pdf_Element_Boolean(true) 0645 ); 0646 $root->AcroForm->touch(); 0647 } 0648 } 0649 0650 /** 0651 * Retrieves a list with the names of the AcroForm textfields in the PDF 0652 * 0653 * @return array of strings 0654 */ 0655 public function getTextFieldNames() 0656 { 0657 return array_keys($this->_formFields); 0658 } 0659 0660 /** 0661 * Sets the value of an AcroForm text field 0662 * 0663 * @param string $name Name of textfield 0664 * @param string $value Value 0665 * @throws Zend_Pdf_Exception if the textfield does not exist in the pdf 0666 */ 0667 public function setTextField($name, $value) 0668 { 0669 if (!isset($this->_formFields[$name])) { 0670 throw new Zend_Pdf_Exception( 0671 "Field '$name' does not exist or is not a textfield" 0672 ); 0673 } 0674 0675 /** @var Zend_Pdf_Element $field */ 0676 $field = $this->_formFields[$name]; 0677 $field->add( 0678 new Zend_Pdf_Element_Name('V'), new Zend_Pdf_Element_String($value) 0679 ); 0680 $field->touch(); 0681 } 0682 0683 /** 0684 * Sets the properties for an AcroForm text field 0685 * 0686 * @param string $name 0687 * @param mixed $bitmask 0688 * @throws Zend_Pdf_Exception 0689 */ 0690 public function setTextFieldProperties($name, $bitmask) 0691 { 0692 if (!isset($this->_formFields[$name])) { 0693 throw new Zend_Pdf_Exception( 0694 "Field '$name' does not exist or is not a textfield" 0695 ); 0696 } 0697 0698 $field = $this->_formFields[$name]; 0699 $field->add( 0700 new Zend_Pdf_Element_Name('Ff'), 0701 new Zend_Pdf_Element_Numeric($bitmask) 0702 ); 0703 $field->touch(); 0704 } 0705 0706 /** 0707 * Marks an AcroForm text field as read only 0708 * 0709 * @param string $name 0710 */ 0711 public function markTextFieldAsReadOnly($name) 0712 { 0713 $this->setTextFieldProperties($name, self::PDF_FORM_FIELD_READONLY); 0714 } 0715 0716 /** 0717 * Orginize pages to tha pages tree structure. 0718 * 0719 * @todo atomatically attach page to the document, if it's not done yet. 0720 * @todo check, that page is attached to the current document 0721 * 0722 * @todo Dump pages as a balanced tree instead of a plain set. 0723 */ 0724 protected function _dumpPages() 0725 { 0726 $root = $this->_trailer->Root; 0727 $pagesContainer = $root->Pages; 0728 0729 $pagesContainer->touch(); 0730 $pagesContainer->Kids->items = array(); 0731 0732 foreach ($this->pages as $page ) { 0733 $page->render($this->_objFactory); 0734 0735 $pageDictionary = $page->getPageDictionary(); 0736 $pageDictionary->touch(); 0737 $pageDictionary->Parent = $pagesContainer; 0738 0739 $pagesContainer->Kids->items[] = $pageDictionary; 0740 } 0741 0742 $this->_refreshPagesHash(); 0743 0744 $pagesContainer->Count->touch(); 0745 $pagesContainer->Count->value = count($this->pages); 0746 0747 0748 // Refresh named destinations list 0749 foreach ($this->_namedTargets as $name => $namedTarget) { 0750 if ($namedTarget instanceof Zend_Pdf_Destination_Explicit) { 0751 // Named target is an explicit destination 0752 if ($this->resolveDestination($namedTarget, false) === null) { 0753 unset($this->_namedTargets[$name]); 0754 } 0755 } else if ($namedTarget instanceof Zend_Pdf_Action) { 0756 // Named target is an action 0757 if ($this->_cleanUpAction($namedTarget, false) === null) { 0758 // Action is a GoTo action with an unresolved destination 0759 unset($this->_namedTargets[$name]); 0760 } 0761 } else { 0762 // require_once 'Zend/Pdf/Exception.php'; 0763 throw new Zend_Pdf_Exception('Wrong type of named targed (\'' . get_class($namedTarget) . '\').'); 0764 } 0765 } 0766 0767 // Refresh outlines 0768 // require_once 'Zend/Pdf/RecursivelyIteratableObjectsContainer.php'; 0769 $iterator = new RecursiveIteratorIterator(new Zend_Pdf_RecursivelyIteratableObjectsContainer($this->outlines), RecursiveIteratorIterator::SELF_FIRST); 0770 foreach ($iterator as $outline) { 0771 $target = $outline->getTarget(); 0772 0773 if ($target !== null) { 0774 if ($target instanceof Zend_Pdf_Destination) { 0775 // Outline target is a destination 0776 if ($this->resolveDestination($target, false) === null) { 0777 $outline->setTarget(null); 0778 } 0779 } else if ($target instanceof Zend_Pdf_Action) { 0780 // Outline target is an action 0781 if ($this->_cleanUpAction($target, false) === null) { 0782 // Action is a GoTo action with an unresolved destination 0783 $outline->setTarget(null); 0784 } 0785 } else { 0786 // require_once 'Zend/Pdf/Exception.php'; 0787 throw new Zend_Pdf_Exception('Wrong outline target.'); 0788 } 0789 } 0790 } 0791 0792 $openAction = $this->getOpenAction(); 0793 if ($openAction !== null) { 0794 if ($openAction instanceof Zend_Pdf_Action) { 0795 // OpenAction is an action 0796 if ($this->_cleanUpAction($openAction, false) === null) { 0797 // Action is a GoTo action with an unresolved destination 0798 $this->setOpenAction(null); 0799 } 0800 } else if ($openAction instanceof Zend_Pdf_Destination) { 0801 // OpenAction target is a destination 0802 if ($this->resolveDestination($openAction, false) === null) { 0803 $this->setOpenAction(null); 0804 } 0805 } else { 0806 // require_once 'Zend/Pdf/Exception.php'; 0807 throw new Zend_Pdf_Exception('OpenAction has to be either PDF Action or Destination.'); 0808 } 0809 } 0810 } 0811 0812 /** 0813 * Dump named destinations 0814 * 0815 * @todo Create a balanced tree instead of plain structure. 0816 */ 0817 protected function _dumpNamedDestinations() 0818 { 0819 ksort($this->_namedTargets, SORT_STRING); 0820 0821 $destArrayItems = array(); 0822 foreach ($this->_namedTargets as $name => $destination) { 0823 $destArrayItems[] = new Zend_Pdf_Element_String($name); 0824 0825 if ($destination instanceof Zend_Pdf_Target) { 0826 $destArrayItems[] = $destination->getResource(); 0827 } else { 0828 // require_once 'Zend/Pdf/Exception.php'; 0829 throw new Zend_Pdf_Exception('PDF named destinations must be a Zend_Pdf_Target object.'); 0830 } 0831 } 0832 $destArray = $this->_objFactory->newObject(new Zend_Pdf_Element_Array($destArrayItems)); 0833 0834 $DestTree = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary()); 0835 $DestTree->Names = $destArray; 0836 0837 $root = $this->_trailer->Root; 0838 0839 if ($root->Names === null) { 0840 $root->touch(); 0841 $root->Names = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary()); 0842 } else { 0843 $root->Names->touch(); 0844 } 0845 $root->Names->Dests = $DestTree; 0846 } 0847 0848 /** 0849 * Dump outlines recursively 0850 */ 0851 protected function _dumpOutlines() 0852 { 0853 $root = $this->_trailer->Root; 0854 0855 if ($root->Outlines === null) { 0856 if (count($this->outlines) == 0) { 0857 return; 0858 } else { 0859 $root->Outlines = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary()); 0860 $root->Outlines->Type = new Zend_Pdf_Element_Name('Outlines'); 0861 $updateOutlinesNavigation = true; 0862 } 0863 } else { 0864 $updateOutlinesNavigation = false; 0865 if (count($this->_originalOutlines) != count($this->outlines)) { 0866 // If original and current outlines arrays have different size then outlines list was updated 0867 $updateOutlinesNavigation = true; 0868 } else if ( !(array_keys($this->_originalOutlines) === array_keys($this->outlines)) ) { 0869 // If original and current outlines arrays have different keys (with a glance to an order) then outlines list was updated 0870 $updateOutlinesNavigation = true; 0871 } else { 0872 foreach ($this->outlines as $key => $outline) { 0873 if ($this->_originalOutlines[$key] !== $outline) { 0874 $updateOutlinesNavigation = true; 0875 } 0876 } 0877 } 0878 } 0879 0880 $lastOutline = null; 0881 $openOutlinesCount = 0; 0882 if ($updateOutlinesNavigation) { 0883 $root->Outlines->touch(); 0884 $root->Outlines->First = null; 0885 0886 foreach ($this->outlines as $outline) { 0887 if ($lastOutline === null) { 0888 // First pass. Update Outlines dictionary First entry using corresponding value 0889 $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines); 0890 $root->Outlines->First = $lastOutline; 0891 } else { 0892 // Update previous outline dictionary Next entry (Prev is updated within dumpOutline() method) 0893 $currentOutlineDictionary = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline); 0894 $lastOutline->Next = $currentOutlineDictionary; 0895 $lastOutline = $currentOutlineDictionary; 0896 } 0897 $openOutlinesCount += $outline->openOutlinesCount(); 0898 } 0899 0900 $root->Outlines->Last = $lastOutline; 0901 } else { 0902 foreach ($this->outlines as $outline) { 0903 $lastOutline = $outline->dumpOutline($this->_objFactory, $updateOutlinesNavigation, $root->Outlines, $lastOutline); 0904 $openOutlinesCount += $outline->openOutlinesCount(); 0905 } 0906 } 0907 0908 if ($openOutlinesCount != $this->_originalOpenOutlinesCount) { 0909 $root->Outlines->touch; 0910 $root->Outlines->Count = new Zend_Pdf_Element_Numeric($openOutlinesCount); 0911 } 0912 } 0913 0914 /** 0915 * Create page object, attached to the PDF document. 0916 * Method signatures: 0917 * 0918 * 1. Create new page with a specified pagesize. 0919 * If $factory is null then it will be created and page must be attached to the document to be 0920 * included into output. 0921 * --------------------------------------------------------- 0922 * new Zend_Pdf_Page(string $pagesize); 0923 * --------------------------------------------------------- 0924 * 0925 * 2. Create new page with a specified pagesize (in default user space units). 0926 * If $factory is null then it will be created and page must be attached to the document to be 0927 * included into output. 0928 * --------------------------------------------------------- 0929 * new Zend_Pdf_Page(numeric $width, numeric $height); 0930 * --------------------------------------------------------- 0931 * 0932 * @param mixed $param1 0933 * @param mixed $param2 0934 * @return Zend_Pdf_Page 0935 */ 0936 public function newPage($param1, $param2 = null) 0937 { 0938 // require_once 'Zend/Pdf/Page.php'; 0939 if ($param2 === null) { 0940 return new Zend_Pdf_Page($param1, $this->_objFactory); 0941 } else { 0942 return new Zend_Pdf_Page($param1, $param2, $this->_objFactory); 0943 } 0944 } 0945 0946 /** 0947 * Return the document-level Metadata 0948 * or null Metadata stream is not presented 0949 * 0950 * @return string 0951 */ 0952 public function getMetadata() 0953 { 0954 if ($this->_trailer->Root->Metadata !== null) { 0955 return $this->_trailer->Root->Metadata->value; 0956 } else { 0957 return null; 0958 } 0959 } 0960 0961 /** 0962 * Sets the document-level Metadata (mast be valid XMP document) 0963 * 0964 * @param string $metadata 0965 */ 0966 public function setMetadata($metadata) 0967 { 0968 $metadataObject = $this->_objFactory->newStreamObject($metadata); 0969 $metadataObject->dictionary->Type = new Zend_Pdf_Element_Name('Metadata'); 0970 $metadataObject->dictionary->Subtype = new Zend_Pdf_Element_Name('XML'); 0971 0972 $this->_trailer->Root->Metadata = $metadataObject; 0973 $this->_trailer->Root->touch(); 0974 } 0975 0976 /** 0977 * Return the document-level JavaScript 0978 * or null if there is no JavaScript for this document 0979 * 0980 * @return string 0981 */ 0982 public function getJavaScript() 0983 { 0984 return $this->_javaScript; 0985 } 0986 0987 /** 0988 * Get open Action 0989 * Returns Zend_Pdf_Target (Zend_Pdf_Destination or Zend_Pdf_Action object) 0990 * 0991 * @return Zend_Pdf_Target 0992 */ 0993 public function getOpenAction() 0994 { 0995 if ($this->_trailer->Root->OpenAction !== null) { 0996 // require_once 'Zend/Pdf/Target.php'; 0997 return Zend_Pdf_Target::load($this->_trailer->Root->OpenAction); 0998 } else { 0999 return null; 1000 } 1001 } 1002 1003 /** 1004 * Set open Action which is actually Zend_Pdf_Destination or Zend_Pdf_Action object 1005 * 1006 * @param Zend_Pdf_Target $openAction 1007 * @returns Zend_Pdf 1008 */ 1009 public function setOpenAction(Zend_Pdf_Target $openAction = null) 1010 { 1011 $root = $this->_trailer->Root; 1012 $root->touch(); 1013 1014 if ($openAction === null) { 1015 $root->OpenAction = null; 1016 } else { 1017 $root->OpenAction = $openAction->getResource(); 1018 1019 if ($openAction instanceof Zend_Pdf_Action) { 1020 $openAction->dumpAction($this->_objFactory); 1021 } 1022 } 1023 1024 return $this; 1025 } 1026 1027 /** 1028 * Return an associative array containing all the named destinations (or GoTo actions) in the PDF. 1029 * Named targets can be used to reference from outside 1030 * the PDF, ex: 'http://www.something.com/mydocument.pdf#MyAction' 1031 * 1032 * @return array 1033 */ 1034 public function getNamedDestinations() 1035 { 1036 return $this->_namedTargets; 1037 } 1038 1039 /** 1040 * Return specified named destination 1041 * 1042 * @param string $name 1043 * @return Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo 1044 */ 1045 public function getNamedDestination($name) 1046 { 1047 if (isset($this->_namedTargets[$name])) { 1048 return $this->_namedTargets[$name]; 1049 } else { 1050 return null; 1051 } 1052 } 1053 1054 /** 1055 * Set specified named destination 1056 * 1057 * @param string $name 1058 * @param Zend_Pdf_Destination_Explicit|Zend_Pdf_Action_GoTo $destination 1059 * @throws Zend_Pdf_Exception 1060 */ 1061 public function setNamedDestination($name, $destination = null) 1062 { 1063 if ($destination !== null && 1064 !$destination instanceof Zend_Pdf_Action_GoTo && 1065 !$destination instanceof Zend_Pdf_Destination_Explicit) { 1066 // require_once 'Zend/Pdf/Exception.php'; 1067 throw new Zend_Pdf_Exception('PDF named destination must refer an explicit destination or a GoTo PDF action.'); 1068 } 1069 1070 if ($destination !== null) { 1071 $this->_namedTargets[$name] = $destination; 1072 } else { 1073 unset($this->_namedTargets[$name]); 1074 } 1075 } 1076 1077 /** 1078 * Pages collection hash: 1079 * <page dictionary object hash id> => Zend_Pdf_Page 1080 * 1081 * @var SplObjectStorage 1082 */ 1083 protected $_pageReferences = null; 1084 1085 /** 1086 * Pages collection hash: 1087 * <page number> => Zend_Pdf_Page 1088 * 1089 * @var array 1090 */ 1091 protected $_pageNumbers = null; 1092 1093 /** 1094 * Refresh page collection hashes 1095 * 1096 * @return Zend_Pdf 1097 */ 1098 protected function _refreshPagesHash() 1099 { 1100 $this->_pageReferences = array(); 1101 $this->_pageNumbers = array(); 1102 $count = 1; 1103 foreach ($this->pages as $page) { 1104 $pageDictionaryHashId = spl_object_hash($page->getPageDictionary()->getObject()); 1105 $this->_pageReferences[$pageDictionaryHashId] = $page; 1106 $this->_pageNumbers[$count++] = $page; 1107 } 1108 1109 return $this; 1110 } 1111 1112 /** 1113 * Resolve destination. 1114 * 1115 * Returns Zend_Pdf_Page page object or null if destination is not found within PDF document. 1116 * 1117 * @param Zend_Pdf_Destination $destination Destination to resolve 1118 * @param bool $refreshPageCollectionHashes Refresh page collection hashes before processing 1119 * @return Zend_Pdf_Page|null 1120 * @throws Zend_Pdf_Exception 1121 */ 1122 public function resolveDestination(Zend_Pdf_Destination $destination, $refreshPageCollectionHashes = true) 1123 { 1124 if ($this->_pageReferences === null || $refreshPageCollectionHashes) { 1125 $this->_refreshPagesHash(); 1126 } 1127 1128 if ($destination instanceof Zend_Pdf_Destination_Named) { 1129 if (!isset($this->_namedTargets[$destination->getName()])) { 1130 return null; 1131 } 1132 $destination = $this->getNamedDestination($destination->getName()); 1133 1134 if ($destination instanceof Zend_Pdf_Action) { 1135 if (!$destination instanceof Zend_Pdf_Action_GoTo) { 1136 return null; 1137 } 1138 $destination = $destination->getDestination(); 1139 } 1140 1141 if (!$destination instanceof Zend_Pdf_Destination_Explicit) { 1142 // require_once 'Zend/Pdf/Exception.php'; 1143 throw new Zend_Pdf_Exception('Named destination target has to be an explicit destination.'); 1144 } 1145 } 1146 1147 // Named target is an explicit destination 1148 $pageElement = $destination->getResource()->items[0]; 1149 1150 if ($pageElement->getType() == Zend_Pdf_Element::TYPE_NUMERIC) { 1151 // Page reference is a PDF number 1152 if (!isset($this->_pageNumbers[$pageElement->value])) { 1153 return null; 1154 } 1155 1156 return $this->_pageNumbers[$pageElement->value]; 1157 } 1158 1159 // Page reference is a PDF page dictionary reference 1160 $pageDictionaryHashId = spl_object_hash($pageElement->getObject()); 1161 if (!isset($this->_pageReferences[$pageDictionaryHashId])) { 1162 return null; 1163 } 1164 return $this->_pageReferences[$pageDictionaryHashId]; 1165 } 1166 1167 /** 1168 * Walk through action and its chained actions tree and remove nodes 1169 * if they are GoTo actions with an unresolved target. 1170 * 1171 * Returns null if root node is deleted or updated action overwise. 1172 * 1173 * @todo Give appropriate name and make method public 1174 * 1175 * @param Zend_Pdf_Action $action 1176 * @param bool $refreshPageCollectionHashes Refresh page collection hashes before processing 1177 * @return Zend_Pdf_Action|null 1178 */ 1179 protected function _cleanUpAction(Zend_Pdf_Action $action, $refreshPageCollectionHashes = true) 1180 { 1181 if ($this->_pageReferences === null || $refreshPageCollectionHashes) { 1182 $this->_refreshPagesHash(); 1183 } 1184 1185 // Named target is an action 1186 if ($action instanceof Zend_Pdf_Action_GoTo && 1187 $this->resolveDestination($action->getDestination(), false) === null) { 1188 // Action itself is a GoTo action with an unresolved destination 1189 return null; 1190 } 1191 1192 // Walk through child actions 1193 $iterator = new RecursiveIteratorIterator($action, RecursiveIteratorIterator::SELF_FIRST); 1194 1195 $actionsToClean = array(); 1196 $deletionCandidateKeys = array(); 1197 foreach ($iterator as $chainedAction) { 1198 if ($chainedAction instanceof Zend_Pdf_Action_GoTo && 1199 $this->resolveDestination($chainedAction->getDestination(), false) === null) { 1200 // Some child action is a GoTo action with an unresolved destination 1201 // Mark it as a candidate for deletion 1202 $actionsToClean[] = $iterator->getSubIterator(); 1203 $deletionCandidateKeys[] = $iterator->getSubIterator()->key(); 1204 } 1205 } 1206 foreach ($actionsToClean as $id => $action) { 1207 unset($action->next[$deletionCandidateKeys[$id]]); 1208 } 1209 1210 return $action; 1211 } 1212 1213 /** 1214 * Extract fonts attached to the document 1215 * 1216 * returns array of Zend_Pdf_Resource_Font_Extracted objects 1217 * 1218 * @return array 1219 * @throws Zend_Pdf_Exception 1220 */ 1221 public function extractFonts() 1222 { 1223 $fontResourcesUnique = array(); 1224 foreach ($this->pages as $page) { 1225 $pageResources = $page->extractResources(); 1226 1227 if ($pageResources->Font === null) { 1228 // Page doesn't contain have any font reference 1229 continue; 1230 } 1231 1232 $fontResources = $pageResources->Font; 1233 1234 foreach ($fontResources->getKeys() as $fontResourceName) { 1235 $fontDictionary = $fontResources->$fontResourceName; 1236 1237 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference || 1238 $fontDictionary instanceof Zend_Pdf_Element_Object) ) { 1239 // require_once 'Zend/Pdf/Exception.php'; 1240 throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.'); 1241 } 1242 1243 $fontResourcesUnique[spl_object_hash($fontDictionary->getObject())] = $fontDictionary; 1244 } 1245 } 1246 1247 $fonts = array(); 1248 // require_once 'Zend/Pdf/Exception.php'; 1249 foreach ($fontResourcesUnique as $resourceId => $fontDictionary) { 1250 try { 1251 // Try to extract font 1252 // require_once 'Zend/Pdf/Resource/Font/Extracted.php'; 1253 $extractedFont = new Zend_Pdf_Resource_Font_Extracted($fontDictionary); 1254 1255 $fonts[$resourceId] = $extractedFont; 1256 } catch (Zend_Pdf_Exception $e) { 1257 if ($e->getMessage() != 'Unsupported font type.') { 1258 throw $e; 1259 } 1260 } 1261 } 1262 1263 return $fonts; 1264 } 1265 1266 /** 1267 * Extract font attached to the page by specific font name 1268 * 1269 * $fontName should be specified in UTF-8 encoding 1270 * 1271 * @param string $fontName 1272 * @return Zend_Pdf_Resource_Font_Extracted|null 1273 * @throws Zend_Pdf_Exception 1274 */ 1275 public function extractFont($fontName) 1276 { 1277 $fontResourcesUnique = array(); 1278 // require_once 'Zend/Pdf/Exception.php'; 1279 foreach ($this->pages as $page) { 1280 $pageResources = $page->extractResources(); 1281 1282 if ($pageResources->Font === null) { 1283 // Page doesn't contain have any font reference 1284 continue; 1285 } 1286 1287 $fontResources = $pageResources->Font; 1288 1289 foreach ($fontResources->getKeys() as $fontResourceName) { 1290 $fontDictionary = $fontResources->$fontResourceName; 1291 1292 if (! ($fontDictionary instanceof Zend_Pdf_Element_Reference || 1293 $fontDictionary instanceof Zend_Pdf_Element_Object) ) { 1294 // require_once 'Zend/Pdf/Exception.php'; 1295 throw new Zend_Pdf_Exception('Font dictionary has to be an indirect object or object reference.'); 1296 } 1297 1298 $resourceId = spl_object_hash($fontDictionary->getObject()); 1299 if (isset($fontResourcesUnique[$resourceId])) { 1300 continue; 1301 } else { 1302 // Mark resource as processed 1303 $fontResourcesUnique[$resourceId] = 1; 1304 } 1305 1306 if ($fontDictionary->BaseFont->value != $fontName) { 1307 continue; 1308 } 1309 1310 try { 1311 // Try to extract font 1312 // require_once 'Zend/Pdf/Resource/Font/Extracted.php'; 1313 return new Zend_Pdf_Resource_Font_Extracted($fontDictionary); 1314 } catch (Zend_Pdf_Exception $e) { 1315 if ($e->getMessage() != 'Unsupported font type.') { 1316 throw $e; 1317 } 1318 // Continue searhing 1319 } 1320 } 1321 } 1322 1323 return null; 1324 } 1325 1326 /** 1327 * Render the completed PDF to a string. 1328 * If $newSegmentOnly is true and it's not a new document, 1329 * then only appended part of PDF is returned. 1330 * 1331 * @param boolean $newSegmentOnly 1332 * @param resource $outputStream 1333 * @return string 1334 * @throws Zend_Pdf_Exception 1335 */ 1336 public function render($newSegmentOnly = false, $outputStream = null) 1337 { 1338 if ($this->_isNewDocument) { 1339 // Drop full document first time even $newSegmentOnly is set to true 1340 $newSegmentOnly = false; 1341 $this->_isNewDocument = false; 1342 } 1343 1344 // Save document properties if necessary 1345 if ($this->properties != $this->_originalProperties) { 1346 $docInfo = $this->_objFactory->newObject(new Zend_Pdf_Element_Dictionary()); 1347 1348 foreach ($this->properties as $key => $value) { 1349 switch ($key) { 1350 case 'Trapped': 1351 switch ($value) { 1352 case true: 1353 $docInfo->$key = new Zend_Pdf_Element_Name('True'); 1354 break; 1355 1356 case false: 1357 $docInfo->$key = new Zend_Pdf_Element_Name('False'); 1358 break; 1359 1360 case null: 1361 $docInfo->$key = new Zend_Pdf_Element_Name('Unknown'); 1362 break; 1363 1364 default: 1365 // require_once 'Zend/Pdf/Exception.php'; 1366 throw new Zend_Pdf_Exception('Wrong Trapped document property vale: \'' . $value . '\'. Only true, false and null values are allowed.'); 1367 break; 1368 } 1369 1370 case 'CreationDate': 1371 // break intentionally omitted 1372 case 'ModDate': 1373 $docInfo->$key = new Zend_Pdf_Element_String((string)$value); 1374 break; 1375 1376 case 'Title': 1377 // break intentionally omitted 1378 case 'Author': 1379 // break intentionally omitted 1380 case 'Subject': 1381 // break intentionally omitted 1382 case 'Keywords': 1383 // break intentionally omitted 1384 case 'Creator': 1385 // break intentionally omitted 1386 case 'Producer': 1387 if (extension_loaded('mbstring') === true) { 1388 $detected = mb_detect_encoding($value); 1389 if ($detected !== 'ASCII') { 1390 $value = "\xfe\xff" . mb_convert_encoding($value, 'UTF-16', $detected); 1391 } 1392 } 1393 $docInfo->$key = new Zend_Pdf_Element_String((string)$value); 1394 break; 1395 1396 default: 1397 // Set property using PDF type based on PHP type 1398 $docInfo->$key = Zend_Pdf_Element::phpToPdf($value); 1399 break; 1400 } 1401 } 1402 1403 $this->_trailer->Info = $docInfo; 1404 } 1405 1406 $this->_dumpPages(); 1407 $this->_dumpNamedDestinations(); 1408 $this->_dumpOutlines(); 1409 1410 // Check, that PDF file was modified 1411 // File is always modified by _dumpPages() now, but future implementations may eliminate this. 1412 if (!$this->_objFactory->isModified()) { 1413 if ($newSegmentOnly) { 1414 // Do nothing, return 1415 return ''; 1416 } 1417 1418 if ($outputStream === null) { 1419 return $this->_trailer->getPDFString(); 1420 } else { 1421 $pdfData = $this->_trailer->getPDFString(); 1422 while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) { 1423 $pdfData = substr($pdfData, $byteCount); 1424 } 1425 1426 return ''; 1427 } 1428 } 1429 1430 // offset (from a start of PDF file) of new PDF file segment 1431 $offset = $this->_trailer->getPDFLength(); 1432 // Last Object number in a list of free objects 1433 $lastFreeObject = $this->_trailer->getLastFreeObject(); 1434 1435 // Array of cross-reference table subsections 1436 $xrefTable = array(); 1437 // Object numbers of first objects in each subsection 1438 $xrefSectionStartNums = array(); 1439 1440 // Last cross-reference table subsection 1441 $xrefSection = array(); 1442 // Dummy initialization of the first element (specail case - header of linked list of free objects). 1443 $xrefSection[] = 0; 1444 $xrefSectionStartNums[] = 0; 1445 // Object number of last processed PDF object. 1446 // Used to manage cross-reference subsections. 1447 // Initialized by zero (specail case - header of linked list of free objects). 1448 $lastObjNum = 0; 1449 1450 if ($outputStream !== null) { 1451 if (!$newSegmentOnly) { 1452 $pdfData = $this->_trailer->getPDFString(); 1453 while ( strlen($pdfData) > 0 && ($byteCount = fwrite($outputStream, $pdfData)) != false ) { 1454 $pdfData = substr($pdfData, $byteCount); 1455 } 1456 } 1457 } else { 1458 $pdfSegmentBlocks = ($newSegmentOnly) ? array() : array($this->_trailer->getPDFString()); 1459 } 1460 1461 // Iterate objects to create new reference table 1462 foreach ($this->_objFactory->listModifiedObjects() as $updateInfo) { 1463 $objNum = $updateInfo->getObjNum(); 1464 1465 if ($objNum - $lastObjNum != 1) { 1466 // Save cross-reference table subsection and start new one 1467 $xrefTable[] = $xrefSection; 1468 $xrefSection = array(); 1469 $xrefSectionStartNums[] = $objNum; 1470 } 1471 1472 if ($updateInfo->isFree()) { 1473 // Free object cross-reference table entry 1474 $xrefSection[] = sprintf("%010d %05d f \n", $lastFreeObject, $updateInfo->getGenNum()); 1475 $lastFreeObject = $objNum; 1476 } else { 1477 // In-use object cross-reference table entry 1478 $xrefSection[] = sprintf("%010d %05d n \n", $offset, $updateInfo->getGenNum()); 1479 1480 $pdfBlock = $updateInfo->getObjectDump(); 1481 $offset += strlen($pdfBlock); 1482 1483 if ($outputStream === null) { 1484 $pdfSegmentBlocks[] = $pdfBlock; 1485 } else { 1486 while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) { 1487 $pdfBlock = substr($pdfBlock, $byteCount); 1488 } 1489 } 1490 } 1491 $lastObjNum = $objNum; 1492 } 1493 // Save last cross-reference table subsection 1494 $xrefTable[] = $xrefSection; 1495 1496 // Modify first entry (specail case - header of linked list of free objects). 1497 $xrefTable[0][0] = sprintf("%010d 65535 f \n", $lastFreeObject); 1498 1499 $xrefTableStr = "xref\n"; 1500 foreach ($xrefTable as $sectId => $xrefSection) { 1501 $xrefTableStr .= sprintf("%d %d \n", $xrefSectionStartNums[$sectId], count($xrefSection)); 1502 foreach ($xrefSection as $xrefTableEntry) { 1503 $xrefTableStr .= $xrefTableEntry; 1504 } 1505 } 1506 1507 $this->_trailer->Size->value = $this->_objFactory->getObjectCount(); 1508 1509 $pdfBlock = $xrefTableStr 1510 . $this->_trailer->toString() 1511 . "startxref\n" . $offset . "\n" 1512 . "%%EOF\n"; 1513 1514 $this->_objFactory->cleanEnumerationShiftCache(); 1515 1516 if ($outputStream === null) { 1517 $pdfSegmentBlocks[] = $pdfBlock; 1518 1519 return implode('', $pdfSegmentBlocks); 1520 } else { 1521 while ( strlen($pdfBlock) > 0 && ($byteCount = fwrite($outputStream, $pdfBlock)) != false ) { 1522 $pdfBlock = substr($pdfBlock, $byteCount); 1523 } 1524 1525 return ''; 1526 } 1527 } 1528 1529 /** 1530 * Sets the document-level JavaScript 1531 * 1532 * Resets and appends 1533 * 1534 * @param string|array $javaScript 1535 */ 1536 public function setJavaScript($javaScript) 1537 { 1538 $this->resetJavaScript(); 1539 1540 $this->addJavaScript($javaScript); 1541 } 1542 1543 /** 1544 * Resets the document-level JavaScript 1545 */ 1546 public function resetJavaScript() 1547 { 1548 $this->_javaScript = null; 1549 1550 $root = $this->_trailer->Root; 1551 if (null === $root->Names || null === $root->Names->JavaScript) { 1552 return; 1553 } 1554 $root->Names->JavaScript = null; 1555 } 1556 1557 /** 1558 * Appends JavaScript to the document-level JavaScript 1559 * 1560 * @param string|array $javaScript 1561 * @throws Zend_Pdf_Exception 1562 */ 1563 public function addJavaScript($javaScript) 1564 { 1565 if (empty($javaScript)) { 1566 throw new Zend_Pdf_Exception( 1567 'JavaScript must be a non empty string or array of strings' 1568 ); 1569 } 1570 1571 if (!is_array($javaScript)) { 1572 $javaScript = array($javaScript); 1573 } 1574 1575 if (null === $this->_javaScript) { 1576 $this->_javaScript = $javaScript; 1577 } else { 1578 $this->_javaScript = array_merge($this->_javaScript, $javaScript); 1579 } 1580 1581 if (!empty($this->_javaScript)) { 1582 $items = array(); 1583 1584 foreach ($this->_javaScript as $javaScript) { 1585 $jsCode = array( 1586 'S' => new Zend_Pdf_Element_Name('JavaScript'), 1587 'JS' => new Zend_Pdf_Element_String($javaScript) 1588 ); 1589 $items[] = new Zend_Pdf_Element_String('EmbeddedJS'); 1590 $items[] = $this->_objFactory->newObject( 1591 new Zend_Pdf_Element_Dictionary($jsCode) 1592 ); 1593 } 1594 1595 $jsRef = $this->_objFactory->newObject( 1596 new Zend_Pdf_Element_Dictionary( 1597 array('Names' => new Zend_Pdf_Element_Array($items)) 1598 ) 1599 ); 1600 1601 if (null === $this->_trailer->Root->Names) { 1602 $this->_trailer->Root->Names = new Zend_Pdf_Element_Dictionary(); 1603 } 1604 $this->_trailer->Root->Names->JavaScript = $jsRef; 1605 } 1606 } 1607 1608 /** 1609 * Convert date to PDF format (it's close to ASN.1 (Abstract Syntax Notation 1610 * One) defined in ISO/IEC 8824). 1611 * 1612 * @todo This really isn't the best location for this method. It should 1613 * probably actually exist as Zend_Pdf_Element_Date or something like that. 1614 * 1615 * @todo Address the following E_STRICT issue: 1616 * PHP Strict Standards: date(): It is not safe to rely on the system's 1617 * timezone settings. Please use the date.timezone setting, the TZ 1618 * environment variable or the date_default_timezone_set() function. In 1619 * case you used any of those methods and you are still getting this 1620 * warning, you most likely misspelled the timezone identifier. 1621 * 1622 * @param integer $timestamp (optional) If omitted, uses the current time. 1623 * @return string 1624 */ 1625 public static function pdfDate($timestamp = null) 1626 { 1627 if ($timestamp === null) { 1628 $date = date('\D\:YmdHisO'); 1629 } else { 1630 $date = date('\D\:YmdHisO', $timestamp); 1631 } 1632 return substr_replace($date, '\'', -2, 0) . '\''; 1633 } 1634 1635 }