PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Pdf.php

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