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.

1635 lines
56 KiB

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