You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1429 lines
49 KiB

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