PageRenderTime 59ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/application/helper/cpdf.php

https://bitbucket.org/kkfd008/simplephp
PHP | 4011 lines | 2932 code | 166 blank | 913 comment | 450 complexity | d1de512701b9960a35ac7f32df575cb1 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Cpdf
  4. *
  5. * http://www.ros.co.nz/pdf
  6. *
  7. * A PHP class to provide the basic functionality to create a pdf document without
  8. * any requirement for additional modules.
  9. *
  10. * Note that they companion class CezPdf can be used to extend this class and dramatically
  11. * simplify the creation of documents.
  12. *
  13. * IMPORTANT NOTE
  14. * there is no warranty, implied or otherwise with this software.
  15. *
  16. * LICENCE
  17. * This code has been placed in the Public Domain for all to enjoy.
  18. *
  19. * @author Wayne Munro <pdf@ros.co.nz>
  20. * @version 009
  21. * @package Cpdf
  22. */
  23. class helper_cpdf
  24. {
  25. /**
  26. * the current number of pdf objects in the document
  27. */
  28. var $numObj = 0;
  29. /**
  30. * this array contains all of the pdf objects, ready for final assembly
  31. */
  32. var $objects = array();
  33. /**
  34. * the objectId (number within the objects array) of the document catalog
  35. */
  36. var $catalogId;
  37. /**
  38. * array carrying information about the fonts that the system currently
  39. * knows about
  40. * used to ensure that a font is not loaded twice, among other things
  41. */
  42. var $fonts = array();
  43. /**
  44. * a record of the current font
  45. */
  46. var $currentFont = '';
  47. /**
  48. * the current base font
  49. */
  50. var $currentBaseFont = '';
  51. /**
  52. * the number of the current font within the font array
  53. */
  54. var $currentFontNum = 0;
  55. /**
  56. *
  57. */
  58. /**var $currentNode;
  59. /**
  60. * object number of the current page
  61. */
  62. var $currentPage;
  63. /**
  64. * object number of the currently active contents block
  65. */
  66. var $currentContents;
  67. /**
  68. * number of fonts within the system
  69. */
  70. var $numFonts = 0;
  71. /**
  72. * current colour for fill operations, defaults to inactive value, all three
  73. * components should be between 0 and 1 inclusive when active
  74. */
  75. var $currentColour = array(
  76. 'r' => - 1,
  77. 'g' => - 1,
  78. 'b' => - 1
  79. );
  80. /**
  81. * current colour for stroke operations (lines etc.)
  82. */
  83. var $currentStrokeColour = array(
  84. 'r' => - 1,
  85. 'g' => - 1,
  86. 'b' => - 1
  87. );
  88. /**
  89. * current style that lines are drawn in
  90. */
  91. var $currentLineStyle = '';
  92. /**
  93. * an array which is used to save the state of the document, mainly the
  94. * colours and styles
  95. * it is used to temporarily change to another state, the change back to
  96. * what it was before
  97. */
  98. var $stateStack = array();
  99. /**
  100. * number of elements within the state stack
  101. */
  102. var $nStateStack = 0;
  103. /**
  104. * number of page objects within the document
  105. */
  106. var $numPages = 0;
  107. /**
  108. * object Id storage stack
  109. */
  110. var $stack = array();
  111. /**
  112. * number of elements within the object Id storage stack
  113. */
  114. var $nStack = 0;
  115. /**
  116. * an array which contains information about the objects which are not
  117. * firmly attached to pages
  118. * these have been added with the addObject function
  119. */
  120. var $looseObjects = array();
  121. /**
  122. * array contains infomation about how the loose objects are to be added to
  123. * the document
  124. */
  125. var $addLooseObjects = array();
  126. /**
  127. * the objectId of the information object for the document
  128. * this contains authorship, title etc.
  129. */
  130. var $infoObject = 0;
  131. /**
  132. * number of images being tracked within the document
  133. */
  134. var $numImages = 0;
  135. /**
  136. * an array containing options about the document
  137. * it defaults to turning on the compression of the objects
  138. */
  139. var $options = array(
  140. 'compression' => 1
  141. );
  142. /**
  143. * the objectId of the first page of the document
  144. */
  145. var $firstPageId;
  146. /**
  147. * used to track the last used value of the inter-word spacing, this is so
  148. * that it is known
  149. * when the spacing is changed.
  150. */
  151. var $wordSpaceAdjust = 0;
  152. /**
  153. * the object Id of the procset object
  154. */
  155. var $procsetObjectId;
  156. /**
  157. * store the information about the relationship between font families
  158. * this used so that the code knows which font is the bold version of
  159. * another font, etc.
  160. * the value of this array is initialised in the constuctor function.
  161. */
  162. var $fontFamilies = array();
  163. /**
  164. * track if the current font is bolded or italicised
  165. */
  166. var $currentTextState = '';
  167. /**
  168. * messages are stored here during processing, these can be selected
  169. * afterwards to give some useful debug information
  170. */
  171. var $messages = '';
  172. /**
  173. * the ancryption array for the document encryption is stored here
  174. */
  175. var $arc4 = '';
  176. /**
  177. * the object Id of the encryption information
  178. */
  179. var $arc4_objnum = 0;
  180. /**
  181. * the file identifier, used to uniquely identify a pdf document
  182. */
  183. var $fileIdentifier = '';
  184. /**
  185. * a flag to say if a document is to be encrypted or not
  186. */
  187. var $encrypted = 0;
  188. /**
  189. * the ancryption key for the encryption of all the document content
  190. * (structure is not encrypted)
  191. */
  192. var $encryptionKey = '';
  193. /**
  194. * array which forms a stack to keep track of nested callback functions
  195. */
  196. var $callback = array();
  197. /**
  198. * the number of callback functions in the callback array
  199. */
  200. var $nCallback = 0;
  201. /**
  202. * store label->id pairs for named destinations, these will be used to
  203. * replace internal links
  204. * done this way so that destinations can be defined after the location that
  205. * links to them
  206. */
  207. var $destinations = array();
  208. /**
  209. * store the stack for the transaction commands, each item in here is a
  210. * record of the values of all the
  211. * variables within the class, so that the user can rollback at will (from
  212. * each 'start' command)
  213. * note that this includes the objects array, so these can be large.
  214. */
  215. var $checkpoint = '';
  216. /**
  217. * class constructor
  218. * this will start a new document
  219. *
  220. * @var array array of 4 numbers, defining the bottom left and upper right
  221. * corner of the page. first two are normally zero.
  222. *
  223. */
  224. function Cpdf($pageSize = array(0,0,612,792))
  225. {
  226. $this->newDocument( $pageSize );
  227. // also initialize the font families that are known about already
  228. $this->setFontFamily( 'init' );
  229. // $this->fileIdentifier = md5('xxxxxxxx'.time());
  230. }
  231. /**
  232. * Document object methods (internal use only)
  233. *
  234. * There is about one object method for each type of object in the pdf
  235. * document
  236. * Each function has the same call list ($id,$action,$options).
  237. * $id = the object ID of the object, or what it is to be if it is being
  238. * created
  239. * $action = a string specifying the action to be performed, though ALL must
  240. * support:
  241. * 'new' - create the object with the id $id
  242. * 'out' - produce the output for the pdf object
  243. * $options = optional, a string or array containing the various parameters
  244. * for the object
  245. *
  246. * These, in conjunction with the output function are the ONLY way for
  247. * output to be produced
  248. * within the pdf 'file'.
  249. */
  250. /**
  251. * *destination object, used to specify the location for the user to jump
  252. * to, presently on opening
  253. */
  254. function o_destination($id, $action, $options = '')
  255. {
  256. if ( $action != 'new' )
  257. {
  258. $o = & $this->objects[$id];
  259. }
  260. switch ( $action )
  261. {
  262. case 'new' :
  263. $this->objects[$id] = array(
  264. 't' => 'destination',
  265. 'info' => array()
  266. );
  267. $tmp = '';
  268. switch ( $options['type'] )
  269. {
  270. case 'XYZ' :
  271. case 'FitR' :
  272. $tmp = ' ' . $options['p3'] . $tmp;
  273. case 'FitH' :
  274. case 'FitV' :
  275. case 'FitBH' :
  276. case 'FitBV' :
  277. $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
  278. case 'Fit' :
  279. case 'FitB' :
  280. $tmp = $options['type'] . $tmp;
  281. $this->objects[$id]['info']['string'] = $tmp;
  282. $this->objects[$id]['info']['page'] = $options['page'];
  283. }
  284. break;
  285. case 'out' :
  286. $tmp = $o['info'];
  287. $res = "\n" . $id . " 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj\n";
  288. return $res;
  289. break;
  290. }
  291. }
  292. /**
  293. * set the viewer preferences
  294. */
  295. function o_viewerPreferences($id, $action, $options = '')
  296. {
  297. if ( $action != 'new' )
  298. {
  299. $o = & $this->objects[$id];
  300. }
  301. switch ( $action )
  302. {
  303. case 'new' :
  304. $this->objects[$id] = array(
  305. 't' => 'viewerPreferences',
  306. 'info' => array()
  307. );
  308. break;
  309. case 'add' :
  310. foreach ( $options as $k => $v )
  311. {
  312. switch ( $k )
  313. {
  314. case 'HideToolbar' :
  315. case 'HideMenubar' :
  316. case 'HideWindowUI' :
  317. case 'FitWindow' :
  318. case 'CenterWindow' :
  319. case 'NonFullScreenPageMode' :
  320. case 'Direction' :
  321. $o['info'][$k] = $v;
  322. break;
  323. }
  324. }
  325. break;
  326. case 'out' :
  327. $res = "\n" . $id . " 0 obj\n" . '<< ';
  328. foreach ( $o['info'] as $k => $v )
  329. {
  330. $res .= "\n/" . $k . ' ' . $v;
  331. }
  332. $res .= "\n>>\n";
  333. return $res;
  334. break;
  335. }
  336. }
  337. /**
  338. * define the document catalog, the overall controller for the document
  339. */
  340. function o_catalog($id, $action, $options = '')
  341. {
  342. if ( $action != 'new' )
  343. {
  344. $o = & $this->objects[$id];
  345. }
  346. switch ( $action )
  347. {
  348. case 'new' :
  349. $this->objects[$id] = array(
  350. 't' => 'catalog',
  351. 'info' => array()
  352. );
  353. $this->catalogId = $id;
  354. break;
  355. case 'outlines' :
  356. case 'pages' :
  357. case 'openHere' :
  358. $o['info'][$action] = $options;
  359. break;
  360. case 'viewerPreferences' :
  361. if ( ! isset( $o['info']['viewerPreferences'] ) )
  362. {
  363. $this->numObj ++ ;
  364. $this->o_viewerPreferences( $this->numObj, 'new' );
  365. $o['info']['viewerPreferences'] = $this->numObj;
  366. }
  367. $vp = $o['info']['viewerPreferences'];
  368. $this->o_viewerPreferences( $vp, 'add', $options );
  369. break;
  370. case 'out' :
  371. $res = "\n" . $id . " 0 obj\n" . '<< /Type /Catalog';
  372. foreach ( $o['info'] as $k => $v )
  373. {
  374. switch ( $k )
  375. {
  376. case 'outlines' :
  377. $res .= "\n" . '/Outlines ' . $v . ' 0 R';
  378. break;
  379. case 'pages' :
  380. $res .= "\n" . '/Pages ' . $v . ' 0 R';
  381. break;
  382. case 'viewerPreferences' :
  383. $res .= "\n" . '/ViewerPreferences ' . $o['info']['viewerPreferences'] . ' 0 R';
  384. break;
  385. case 'openHere' :
  386. $res .= "\n" . '/OpenAction ' . $o['info']['openHere'] . ' 0 R';
  387. break;
  388. }
  389. }
  390. $res .= " >>\nendobj";
  391. return $res;
  392. break;
  393. }
  394. }
  395. /**
  396. * object which is a parent to the pages in the document
  397. */
  398. function o_pages($id, $action, $options = '')
  399. {
  400. if ( $action != 'new' )
  401. {
  402. $o = & $this->objects[$id];
  403. }
  404. switch ( $action )
  405. {
  406. case 'new' :
  407. $this->objects[$id] = array(
  408. 't' => 'pages',
  409. 'info' => array()
  410. );
  411. $this->o_catalog( $this->catalogId, 'pages', $id );
  412. break;
  413. case 'page' :
  414. if ( ! is_array( $options ) )
  415. {
  416. // then it will just be the id of the new page
  417. $o['info']['pages'][ ] = $options;
  418. }
  419. else
  420. {
  421. // then it should be an array having 'id','rid','pos', where
  422. // rid=the page to which this one will be placed relative
  423. // and pos is either 'before' or 'after', saying where this
  424. // page will fit.
  425. if ( isset( $options['id'] ) && isset( $options['rid'] ) && isset( $options['pos'] ) )
  426. {
  427. $i = array_search( $options['rid'], $o['info']['pages'] );
  428. if ( isset( $o['info']['pages'][$i] ) && $o['info']['pages'][$i] == $options['rid'] )
  429. {
  430. // then there is a match
  431. // make a space
  432. switch ( $options['pos'] )
  433. {
  434. case 'before' :
  435. $k = $i;
  436. break;
  437. case 'after' :
  438. $k = $i + 1;
  439. break;
  440. default :
  441. $k = - 1;
  442. break;
  443. }
  444. if ( $k >= 0 )
  445. {
  446. for ( $j = count( $o['info']['pages'] ) - 1; $j >= $k; $j -- )
  447. {
  448. $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
  449. }
  450. $o['info']['pages'][$k] = $options['id'];
  451. }
  452. }
  453. }
  454. }
  455. break;
  456. case 'procset' :
  457. $o['info']['procset'] = $options;
  458. break;
  459. case 'mediaBox' :
  460. $o['info']['mediaBox'] = $options; // which should be an array of 4
  461. // numbers
  462. break;
  463. case 'font' :
  464. $o['info']['fonts'][ ] = array(
  465. 'objNum' => $options['objNum'],
  466. 'fontNum' => $options['fontNum']
  467. );
  468. break;
  469. case 'xObject' :
  470. $o['info']['xObjects'][ ] = array(
  471. 'objNum' => $options['objNum'],
  472. 'label' => $options['label']
  473. );
  474. break;
  475. case 'out' :
  476. if ( count( $o['info']['pages'] ) )
  477. {
  478. $res = "\n" . $id . " 0 obj\n<< /Type /Pages\n/Kids [";
  479. foreach ( $o['info']['pages'] as $k => $v )
  480. {
  481. $res .= $v . " 0 R\n";
  482. }
  483. $res .= "]\n/Count " . count( $this->objects[$id]['info']['pages'] );
  484. if ( (isset( $o['info']['fonts'] ) && count( $o['info']['fonts'] )) || isset( $o['info']['procset'] ) )
  485. {
  486. $res .= "\n/Resources <<";
  487. if ( isset( $o['info']['procset'] ) )
  488. {
  489. $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
  490. }
  491. if ( isset( $o['info']['fonts'] ) && count( $o['info']['fonts'] ) )
  492. {
  493. $res .= "\n/Font << ";
  494. foreach ( $o['info']['fonts'] as $finfo )
  495. {
  496. $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
  497. }
  498. $res .= " >>";
  499. }
  500. if ( isset( $o['info']['xObjects'] ) && count( $o['info']['xObjects'] ) )
  501. {
  502. $res .= "\n/XObject << ";
  503. foreach ( $o['info']['xObjects'] as $finfo )
  504. {
  505. $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
  506. }
  507. $res .= " >>";
  508. }
  509. $res .= "\n>>";
  510. if ( isset( $o['info']['mediaBox'] ) )
  511. {
  512. $tmp = $o['info']['mediaBox'];
  513. $res .= "\n/MediaBox [" . sprintf( '%.3f', $tmp[0] ) . ' ' . sprintf( '%.3f', $tmp[1] ) . ' ' . sprintf( '%.3f', $tmp[2] ) . ' ' . sprintf( '%.3f', $tmp[3] ) . ']';
  514. }
  515. }
  516. $res .= "\n >>\nendobj";
  517. }
  518. else
  519. {
  520. $res = "\n" . $id . " 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  521. }
  522. return $res;
  523. break;
  524. }
  525. }
  526. /**
  527. * define the outlines in the doc, empty for now
  528. */
  529. function o_outlines($id, $action, $options = '')
  530. {
  531. if ( $action != 'new' )
  532. {
  533. $o = & $this->objects[$id];
  534. }
  535. switch ( $action )
  536. {
  537. case 'new' :
  538. $this->objects[$id] = array(
  539. 't' => 'outlines',
  540. 'info' => array(
  541. 'outlines' => array()
  542. )
  543. );
  544. $this->o_catalog( $this->catalogId, 'outlines', $id );
  545. break;
  546. case 'outline' :
  547. $o['info']['outlines'][ ] = $options;
  548. break;
  549. case 'out' :
  550. if ( count( $o['info']['outlines'] ) )
  551. {
  552. $res = "\n" . $id . " 0 obj\n<< /Type /Outlines /Kids [";
  553. foreach ( $o['info']['outlines'] as $k => $v )
  554. {
  555. $res .= $v . " 0 R ";
  556. }
  557. $res .= "] /Count " . count( $o['info']['outlines'] ) . " >>\nendobj";
  558. }
  559. else
  560. {
  561. $res = "\n" . $id . " 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  562. }
  563. return $res;
  564. break;
  565. }
  566. }
  567. /**
  568. * an object to hold the font description
  569. */
  570. function o_font($id, $action, $options = '')
  571. {
  572. if ( $action != 'new' )
  573. {
  574. $o = & $this->objects[$id];
  575. }
  576. switch ( $action )
  577. {
  578. case 'new' :
  579. $this->objects[$id] = array(
  580. 't' => 'font',
  581. 'info' => array(
  582. 'name' => $options['name'],
  583. 'SubType' => 'Type1'
  584. )
  585. );
  586. $fontNum = $this->numFonts;
  587. $this->objects[$id]['info']['fontNum'] = $fontNum;
  588. // deal with the encoding and the differences
  589. if ( isset( $options['differences'] ) )
  590. {
  591. // then we'll need an encoding dictionary
  592. $this->numObj ++ ;
  593. $this->o_fontEncoding( $this->numObj, 'new', $options );
  594. $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
  595. }
  596. else if ( isset( $options['encoding'] ) )
  597. {
  598. // we can specify encoding here
  599. switch ( $options['encoding'] )
  600. {
  601. case 'WinAnsiEncoding' :
  602. case 'MacRomanEncoding' :
  603. case 'MacExpertEncoding' :
  604. $this->objects[$id]['info']['encoding'] = $options['encoding'];
  605. break;
  606. case 'none' :
  607. break;
  608. default :
  609. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  610. break;
  611. }
  612. }
  613. else
  614. {
  615. $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
  616. }
  617. // also tell the pages node about the new font
  618. $this->o_pages( $this->currentNode, 'font', array(
  619. 'fontNum' => $fontNum,
  620. 'objNum' => $id
  621. ) );
  622. break;
  623. case 'add' :
  624. foreach ( $options as $k => $v )
  625. {
  626. switch ( $k )
  627. {
  628. case 'BaseFont' :
  629. $o['info']['name'] = $v;
  630. break;
  631. case 'FirstChar' :
  632. case 'LastChar' :
  633. case 'Widths' :
  634. case 'FontDescriptor' :
  635. case 'SubType' :
  636. $this->addMessage( 'o_font ' . $k . " : " . $v );
  637. $o['info'][$k] = $v;
  638. break;
  639. }
  640. }
  641. break;
  642. case 'out' :
  643. $res = "\n" . $id . " 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
  644. $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
  645. $res .= "/BaseFont /" . $o['info']['name'] . "\n";
  646. if ( isset( $o['info']['encodingDictionary'] ) )
  647. {
  648. // then place a reference to the dictionary
  649. $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
  650. }
  651. else if ( isset( $o['info']['encoding'] ) )
  652. {
  653. // use the specified encoding
  654. $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
  655. }
  656. if ( isset( $o['info']['FirstChar'] ) )
  657. {
  658. $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
  659. }
  660. if ( isset( $o['info']['LastChar'] ) )
  661. {
  662. $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
  663. }
  664. if ( isset( $o['info']['Widths'] ) )
  665. {
  666. $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
  667. }
  668. if ( isset( $o['info']['FontDescriptor'] ) )
  669. {
  670. $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
  671. }
  672. $res .= ">>\nendobj";
  673. return $res;
  674. break;
  675. }
  676. }
  677. /**
  678. * a font descriptor, needed for including additional fonts
  679. */
  680. function o_fontDescriptor($id, $action, $options = '')
  681. {
  682. if ( $action != 'new' )
  683. {
  684. $o = & $this->objects[$id];
  685. }
  686. switch ( $action )
  687. {
  688. case 'new' :
  689. $this->objects[$id] = array(
  690. 't' => 'fontDescriptor',
  691. 'info' => $options
  692. );
  693. break;
  694. case 'out' :
  695. $res = "\n" . $id . " 0 obj\n<< /Type /FontDescriptor\n";
  696. foreach ( $o['info'] as $label => $value )
  697. {
  698. switch ( $label )
  699. {
  700. case 'Ascent' :
  701. case 'CapHeight' :
  702. case 'Descent' :
  703. case 'Flags' :
  704. case 'ItalicAngle' :
  705. case 'StemV' :
  706. case 'AvgWidth' :
  707. case 'Leading' :
  708. case 'MaxWidth' :
  709. case 'MissingWidth' :
  710. case 'StemH' :
  711. case 'XHeight' :
  712. case 'CharSet' :
  713. if ( strlen( $value ) )
  714. {
  715. $res .= '/' . $label . ' ' . $value . "\n";
  716. }
  717. break;
  718. case 'FontFile' :
  719. case 'FontFile2' :
  720. case 'FontFile3' :
  721. $res .= '/' . $label . ' ' . $value . " 0 R\n";
  722. break;
  723. case 'FontBBox' :
  724. $res .= '/' . $label . ' [' . $value[0] . ' ' . $value[1] . ' ' . $value[2] . ' ' . $value[3] . "]\n";
  725. break;
  726. case 'FontName' :
  727. $res .= '/' . $label . ' /' . $value . "\n";
  728. break;
  729. }
  730. }
  731. $res .= ">>\nendobj";
  732. return $res;
  733. break;
  734. }
  735. }
  736. /**
  737. * the font encoding
  738. */
  739. function o_fontEncoding($id, $action, $options = '')
  740. {
  741. if ( $action != 'new' )
  742. {
  743. $o = & $this->objects[$id];
  744. }
  745. switch ( $action )
  746. {
  747. case 'new' :
  748. // the options array should contain 'differences' and maybe
  749. // 'encoding'
  750. $this->objects[$id] = array(
  751. 't' => 'fontEncoding',
  752. 'info' => $options
  753. );
  754. break;
  755. case 'out' :
  756. $res = "\n" . $id . " 0 obj\n<< /Type /Encoding\n";
  757. if ( ! isset( $o['info']['encoding'] ) )
  758. {
  759. $o['info']['encoding'] = 'WinAnsiEncoding';
  760. }
  761. if ( $o['info']['encoding'] != 'none' )
  762. {
  763. $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
  764. }
  765. $res .= "/Differences \n[";
  766. $onum = - 100;
  767. foreach ( $o['info']['differences'] as $num => $label )
  768. {
  769. if ( $num != $onum + 1 )
  770. {
  771. // we cannot make use of consecutive numbering
  772. $res .= "\n" . $num . " /" . $label;
  773. }
  774. else
  775. {
  776. $res .= " /" . $label;
  777. }
  778. $onum = $num;
  779. }
  780. $res .= "\n]\n>>\nendobj";
  781. return $res;
  782. break;
  783. }
  784. }
  785. /**
  786. * the document procset, solves some problems with printing to old PS
  787. * printers
  788. */
  789. function o_procset($id, $action, $options = '')
  790. {
  791. if ( $action != 'new' )
  792. {
  793. $o = & $this->objects[$id];
  794. }
  795. switch ( $action )
  796. {
  797. case 'new' :
  798. $this->objects[$id] = array(
  799. 't' => 'procset',
  800. 'info' => array(
  801. 'PDF' => 1,
  802. 'Text' => 1
  803. )
  804. );
  805. $this->o_pages( $this->currentNode, 'procset', $id );
  806. $this->procsetObjectId = $id;
  807. break;
  808. case 'add' :
  809. // this is to add new items to the procset list, despite the
  810. // fact that this is considered
  811. // obselete, the items are required for printing to some
  812. // postscript printers
  813. switch ( $options )
  814. {
  815. case 'ImageB' :
  816. case 'ImageC' :
  817. case 'ImageI' :
  818. $o['info'][$options] = 1;
  819. break;
  820. }
  821. break;
  822. case 'out' :
  823. $res = "\n" . $id . " 0 obj\n[";
  824. foreach ( $o['info'] as $label => $val )
  825. {
  826. $res .= '/' . $label . ' ';
  827. }
  828. $res .= "]\nendobj";
  829. return $res;
  830. break;
  831. }
  832. }
  833. /**
  834. * define the document information
  835. */
  836. function o_info($id, $action, $options = '')
  837. {
  838. if ( $action != 'new' )
  839. {
  840. $o = & $this->objects[$id];
  841. }
  842. switch ( $action )
  843. {
  844. case 'new' :
  845. $this->infoObject = $id;
  846. $date = 'D:' . date( 'Ymd' );
  847. $this->objects[$id] = array(
  848. 't' => 'info',
  849. 'info' => array(
  850. 'Creator' => 'R and OS php pdf writer, http://www.ros.co.nz',
  851. 'CreationDate' => $date
  852. )
  853. );
  854. break;
  855. case 'Title' :
  856. case 'Author' :
  857. case 'Subject' :
  858. case 'Keywords' :
  859. case 'Creator' :
  860. case 'Producer' :
  861. case 'CreationDate' :
  862. case 'ModDate' :
  863. case 'Trapped' :
  864. $o['info'][$action] = $options;
  865. break;
  866. case 'out' :
  867. if ( $this->encrypted )
  868. {
  869. $this->encryptInit( $id );
  870. }
  871. $res = "\n" . $id . " 0 obj\n<<\n";
  872. foreach ( $o['info'] as $k => $v )
  873. {
  874. $res .= '/' . $k . ' (';
  875. if ( $this->encrypted )
  876. {
  877. $res .= $this->filterText( $this->ARC4( $v ) );
  878. }
  879. else
  880. {
  881. $res .= $this->filterText( $v );
  882. }
  883. $res .= ")\n";
  884. }
  885. $res .= ">>\nendobj";
  886. return $res;
  887. break;
  888. }
  889. }
  890. /**
  891. * an action object, used to link to URLS initially
  892. */
  893. function o_action($id, $action, $options = '')
  894. {
  895. if ( $action != 'new' )
  896. {
  897. $o = & $this->objects[$id];
  898. }
  899. switch ( $action )
  900. {
  901. case 'new' :
  902. if ( is_array( $options ) )
  903. {
  904. $this->objects[$id] = array(
  905. 't' => 'action',
  906. 'info' => $options,
  907. 'type' => $options['type']
  908. );
  909. }
  910. else
  911. {
  912. // then assume a URI action
  913. $this->objects[$id] = array(
  914. 't' => 'action',
  915. 'info' => $options,
  916. 'type' => 'URI'
  917. );
  918. }
  919. break;
  920. case 'out' :
  921. if ( $this->encrypted )
  922. {
  923. $this->encryptInit( $id );
  924. }
  925. $res = "\n" . $id . " 0 obj\n<< /Type /Action";
  926. switch ( $o['type'] )
  927. {
  928. case 'ilink' :
  929. // there will be an 'label' setting, this is the name of
  930. // the destination
  931. $res .= "\n/S /GoTo\n/D " . $this->destinations[( string )$o['info']['label']] . " 0 R";
  932. break;
  933. case 'URI' :
  934. $res .= "\n/S /URI\n/URI (";
  935. if ( $this->encrypted )
  936. {
  937. $res .= $this->filterText( $this->ARC4( $o['info'] ) );
  938. }
  939. else
  940. {
  941. $res .= $this->filterText( $o['info'] );
  942. }
  943. $res .= ")";
  944. break;
  945. }
  946. $res .= "\n>>\nendobj";
  947. return $res;
  948. break;
  949. }
  950. }
  951. /**
  952. * an annotation object, this will add an annotation to the current page.
  953. * initially will support just link annotations
  954. */
  955. function o_annotation($id, $action, $options = '')
  956. {
  957. if ( $action != 'new' )
  958. {
  959. $o = & $this->objects[$id];
  960. }
  961. switch ( $action )
  962. {
  963. case 'new' :
  964. // add the annotation to the current page
  965. $pageId = $this->currentPage;
  966. $this->o_page( $pageId, 'annot', $id );
  967. // and add the action object which is going to be required
  968. switch ( $options['type'] )
  969. {
  970. case 'link' :
  971. $this->objects[$id] = array(
  972. 't' => 'annotation',
  973. 'info' => $options
  974. );
  975. $this->numObj ++ ;
  976. $this->o_action( $this->numObj, 'new', $options['url'] );
  977. $this->objects[$id]['info']['actionId'] = $this->numObj;
  978. break;
  979. case 'ilink' :
  980. // this is to a named internal link
  981. $label = $options['label'];
  982. $this->objects[$id] = array(
  983. 't' => 'annotation',
  984. 'info' => $options
  985. );
  986. $this->numObj ++ ;
  987. $this->o_action( $this->numObj, 'new', array(
  988. 'type' => 'ilink',
  989. 'label' => $label
  990. ) );
  991. $this->objects[$id]['info']['actionId'] = $this->numObj;
  992. break;
  993. }
  994. break;
  995. case 'out' :
  996. $res = "\n" . $id . " 0 obj\n<< /Type /Annot";
  997. switch ( $o['info']['type'] )
  998. {
  999. case 'link' :
  1000. case 'ilink' :
  1001. $res .= "\n/Subtype /Link";
  1002. break;
  1003. }
  1004. $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
  1005. $res .= "\n/Border [0 0 0]";
  1006. $res .= "\n/H /I";
  1007. $res .= "\n/Rect [ ";
  1008. foreach ( $o['info']['rect'] as $v )
  1009. {
  1010. $res .= sprintf( "%.4f ", $v );
  1011. }
  1012. $res .= "]";
  1013. $res .= "\n>>\nendobj";
  1014. return $res;
  1015. break;
  1016. }
  1017. }
  1018. /**
  1019. * a page object, it also creates a contents object to hold its contents
  1020. */
  1021. function o_page($id, $action, $options = '')
  1022. {
  1023. if ( $action != 'new' )
  1024. {
  1025. $o = & $this->objects[$id];
  1026. }
  1027. switch ( $action )
  1028. {
  1029. case 'new' :
  1030. $this->numPages ++ ;
  1031. $this->objects[$id] = array(
  1032. 't' => 'page',
  1033. 'info' => array(
  1034. 'parent' => $this->currentNode,
  1035. 'pageNum' => $this->numPages
  1036. )
  1037. );
  1038. if ( is_array( $options ) )
  1039. {
  1040. // then this must be a page insertion, array shoudl contain
  1041. // 'rid','pos'=[before|after]
  1042. $options['id'] = $id;
  1043. $this->o_pages( $this->currentNode, 'page', $options );
  1044. }
  1045. else
  1046. {
  1047. $this->o_pages( $this->currentNode, 'page', $id );
  1048. }
  1049. $this->currentPage = $id;
  1050. // make a contents object to go with this page
  1051. $this->numObj ++ ;
  1052. $this->o_contents( $this->numObj, 'new', $id );
  1053. $this->currentContents = $this->numObj;
  1054. $this->objects[$id]['info']['contents'] = array();
  1055. $this->objects[$id]['info']['contents'][ ] = $this->numObj;
  1056. $match = ($this->numPages % 2 ? 'odd' : 'even');
  1057. foreach ( $this->addLooseObjects as $oId => $target )
  1058. {
  1059. if ( $target == 'all' || $match == $target )
  1060. {
  1061. $this->objects[$id]['info']['contents'][ ] = $oId;
  1062. }
  1063. }
  1064. break;
  1065. case 'content' :
  1066. $o['info']['contents'][ ] = $options;
  1067. break;
  1068. case 'annot' :
  1069. // add an annotation to this page
  1070. if ( ! isset( $o['info']['annot'] ) )
  1071. {
  1072. $o['info']['annot'] = array();
  1073. }
  1074. // $options should contain the id of the annotation dictionary
  1075. $o['info']['annot'][ ] = $options;
  1076. break;
  1077. case 'out' :
  1078. $res = "\n" . $id . " 0 obj\n<< /Type /Page";
  1079. $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
  1080. if ( isset( $o['info']['annot'] ) )
  1081. {
  1082. $res .= "\n/Annots [";
  1083. foreach ( $o['info']['annot'] as $aId )
  1084. {
  1085. $res .= " " . $aId . " 0 R";
  1086. }
  1087. $res .= " ]";
  1088. }
  1089. $count = count( $o['info']['contents'] );
  1090. if ( $count == 1 )
  1091. {
  1092. $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
  1093. }
  1094. else if ( $count > 1 )
  1095. {
  1096. $res .= "\n/Contents [\n";
  1097. foreach ( $o['info']['contents'] as $cId )
  1098. {
  1099. $res .= $cId . " 0 R\n";
  1100. }
  1101. $res .= "]";
  1102. }
  1103. $res .= "\n>>\nendobj";
  1104. return $res;
  1105. break;
  1106. }
  1107. }
  1108. /**
  1109. * the contents objects hold all of the content which appears on pages
  1110. */
  1111. function o_contents($id, $action, $options = '')
  1112. {
  1113. if ( $action != 'new' )
  1114. {
  1115. $o = & $this->objects[$id];
  1116. }
  1117. switch ( $action )
  1118. {
  1119. case 'new' :
  1120. $this->objects[$id] = array(
  1121. 't' => 'contents',
  1122. 'c' => '',
  1123. 'info' => array()
  1124. );
  1125. if ( strlen( $options ) && intval( $options ) )
  1126. {
  1127. // then this contents is the primary for a page
  1128. $this->objects[$id]['onPage'] = $options;
  1129. }
  1130. else if ( $options == 'raw' )
  1131. {
  1132. // then this page contains some other type of system object
  1133. $this->objects[$id]['raw'] = 1;
  1134. }
  1135. break;
  1136. case 'add' :
  1137. // add more options to the decleration
  1138. foreach ( $options as $k => $v )
  1139. {
  1140. $o['info'][$k] = $v;
  1141. }
  1142. case 'out' :
  1143. $tmp = $o['c'];
  1144. $res = "\n" . $id . " 0 obj\n";
  1145. if ( isset( $this->objects[$id]['raw'] ) )
  1146. {
  1147. $res .= $tmp;
  1148. }
  1149. else
  1150. {
  1151. $res .= "<<";
  1152. if ( function_exists( 'gzcompress' ) && $this->options['compression'] )
  1153. {
  1154. // then implement ZLIB based compression on this content
  1155. // stream
  1156. $res .= " /Filter /FlateDecode";
  1157. $tmp = gzcompress( $tmp );
  1158. }
  1159. if ( $this->encrypted )
  1160. {
  1161. $this->encryptInit( $id );
  1162. $tmp = $this->ARC4( $tmp );
  1163. }
  1164. foreach ( $o['info'] as $k => $v )
  1165. {
  1166. $res .= "\n/" . $k . ' ' . $v;
  1167. }
  1168. $res .= "\n/Length " . strlen( $tmp ) . " >>\nstream\n" . $tmp . "\nendstream";
  1169. }
  1170. $res .= "\nendobj\n";
  1171. return $res;
  1172. break;
  1173. }
  1174. }
  1175. /**
  1176. * an image object, will be an XObject in the document, includes description
  1177. * and data
  1178. */
  1179. function o_image($id, $action, $options = '')
  1180. {
  1181. if ( $action != 'new' )
  1182. {
  1183. $o = & $this->objects[$id];
  1184. }
  1185. switch ( $action )
  1186. {
  1187. case 'new' :
  1188. // make the new object
  1189. $this->objects[$id] = array(
  1190. 't' => 'image',
  1191. 'data' => $options['data'],
  1192. 'info' => array()
  1193. );
  1194. $this->objects[$id]['info']['Type'] = '/XObject';
  1195. $this->objects[$id]['info']['Subtype'] = '/Image';
  1196. $this->objects[$id]['info']['Width'] = $options['iw'];
  1197. $this->objects[$id]['info']['Height'] = $options['ih'];
  1198. if ( ! isset( $options['type'] ) || $options['type'] == 'jpg' )
  1199. {
  1200. if ( ! isset( $options['channels'] ) )
  1201. {
  1202. $options['channels'] = 3;
  1203. }
  1204. switch ( $options['channels'] )
  1205. {
  1206. case 1 :
  1207. $this->objects[$id]['info']['ColorSpace'] = '/DeviceGray';
  1208. break;
  1209. default :
  1210. $this->objects[$id]['info']['ColorSpace'] = '/DeviceRGB';
  1211. break;
  1212. }
  1213. $this->objects[$id]['info']['Filter'] = '/DCTDecode';
  1214. $this->objects[$id]['info']['BitsPerComponent'] = 8;
  1215. }
  1216. else if ( $options['type'] == 'png' )
  1217. {
  1218. $this->objects[$id]['info']['Filter'] = '/FlateDecode';
  1219. $this->objects[$id]['info']['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
  1220. if ( strlen( $options['pdata'] ) )
  1221. {
  1222. $tmp = ' [ /Indexed /DeviceRGB ' . (strlen( $options['pdata'] ) / 3 - 1) . ' ';
  1223. $this->numObj ++ ;
  1224. $this->o_contents( $this->numObj, 'new' );
  1225. $this->objects[$this->numObj]['c'] = $options['pdata'];
  1226. $tmp .= $this->numObj . ' 0 R';
  1227. $tmp .= ' ]';
  1228. $this->objects[$id]['info']['ColorSpace'] = $tmp;
  1229. if ( isset( $options['transparency'] ) )
  1230. {
  1231. switch ( $options['transparency']['type'] )
  1232. {
  1233. case 'indexed' :
  1234. $tmp = ' [ ' . $options['transparency']['data'] . ' ' . $options['transparency']['data'] . '] ';
  1235. $this->objects[$id]['info']['Mask'] = $tmp;
  1236. break;
  1237. }
  1238. }
  1239. }
  1240. else
  1241. {
  1242. $this->objects[$id]['info']['ColorSpace'] = '/' . $options['color'];
  1243. }
  1244. $this->objects[$id]['info']['BitsPerComponent'] = $options['bitsPerComponent'];
  1245. }
  1246. // assign it a place in the named resource dictionary as an
  1247. // external object, according to
  1248. // the label passed in with it.
  1249. $this->o_pages( $this->currentNode, 'xObject', array(
  1250. 'label' => $options['label'],
  1251. 'objNum' => $id
  1252. ) );
  1253. // also make sure that we have the right procset object for it.
  1254. $this->o_procset( $this->procsetObjectId, 'add', 'ImageC' );
  1255. break;
  1256. case 'out' :
  1257. $tmp = $o['data'];
  1258. $res = "\n" . $id . " 0 obj\n<<";
  1259. foreach ( $o['info'] as $k => $v )
  1260. {
  1261. $res .= "\n/" . $k . ' ' . $v;
  1262. }
  1263. if ( $this->encrypted )
  1264. {
  1265. $this->encryptInit( $id );
  1266. $tmp = $this->ARC4( $tmp );
  1267. }
  1268. $res .= "\n/Length " . strlen( $tmp ) . " >>\nstream\n" . $tmp . "\nendstream\nendobj\n";
  1269. return $res;
  1270. break;
  1271. }
  1272. }
  1273. /**
  1274. * encryption object.
  1275. */
  1276. function o_encryption($id, $action, $options = '')
  1277. {
  1278. if ( $action != 'new' )
  1279. {
  1280. $o = & $this->objects[$id];
  1281. }
  1282. switch ( $action )
  1283. {
  1284. case 'new' :
  1285. // make the new object
  1286. $this->objects[$id] = array(
  1287. 't' => 'encryption',
  1288. 'info' => $options
  1289. );
  1290. $this->arc4_objnum = $id;
  1291. // figure out the additional paramaters required
  1292. $pad = chr( 0x28 ) . chr( 0xBF ) . chr( 0x4E ) . chr( 0x5E ) . chr( 0x4E ) . chr( 0x75 ) . chr( 0x8A ) . chr( 0x41 ) . chr( 0x64 ) . chr( 0x00 ) . chr( 0x4E ) . chr( 0x56 ) . chr( 0xFF ) . chr( 0xFA ) . chr( 0x01 ) . chr( 0x08 ) . chr( 0x2E ) . chr( 0x2E ) . chr( 0x00 ) . chr( 0xB6 ) . chr( 0xD0 ) . chr( 0x68 ) . chr( 0x3E ) . chr( 0x80 ) . chr( 0x2F ) . chr( 0x0C ) . chr( 0xA9 ) . chr( 0xFE ) . chr( 0x64 ) . chr( 0x53 ) . chr( 0x69 ) . chr( 0x7A );
  1293. $len = strlen( $options['owner'] );
  1294. if ( $len > 32 )
  1295. {
  1296. $owner = substr( $options['owner'], 0, 32 );
  1297. }
  1298. else if ( $len < 32 )
  1299. {
  1300. $owner = $options['owner'] . substr( $pad, 0, 32 - $len );
  1301. }
  1302. else
  1303. {
  1304. $owner = $options['owner'];
  1305. }
  1306. $len = strlen( $options['user'] );
  1307. if ( $len > 32 )
  1308. {
  1309. $user = substr( $options['user'], 0, 32 );
  1310. }
  1311. else if ( $len < 32 )
  1312. {
  1313. $user = $options['user'] . substr( $pad, 0, 32 - $len );
  1314. }
  1315. else
  1316. {
  1317. $user = $options['user'];
  1318. }
  1319. $tmp = $this->md5_16( $owner );
  1320. $okey = substr( $tmp, 0, 5 );
  1321. $this->ARC4_init( $okey );
  1322. $ovalue = $this->ARC4( $user );
  1323. $this->objects[$id]['info']['O'] = $ovalue;
  1324. // now make the u value, phew.
  1325. $tmp = $this->md5_16( $user . $ovalue . chr( $options['p'] ) . chr( 255 ) . chr( 255 ) . chr( 255 ) . $this->fileIdentifier );
  1326. $ukey = substr( $tmp, 0, 5 );
  1327. $this->ARC4_init( $ukey );
  1328. $this->encryptionKey = $ukey;
  1329. $this->encrypted = 1;
  1330. $uvalue = $this->ARC4( $pad );
  1331. $this->objects[$id]['info']['U'] = $uvalue;
  1332. $this->encryptionKey = $ukey;
  1333. // initialize the arc4 array
  1334. break;
  1335. case 'out' :
  1336. $res = "\n" . $id . " 0 obj\n<<";
  1337. $res .= "\n/Filter /Standard";
  1338. $res .= "\n/V 1";
  1339. $res .= "\n/R 2";
  1340. $res .= "\n/O (" . $this->filterText( $o['info']['O'] ) . ')';
  1341. $res .= "\n/U (" . $this->filterText( $o['info']['U'] ) . ')';
  1342. // and the p-value needs to be converted to account for the
  1343. // twos-complement approach
  1344. $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * - 1;
  1345. $res .= "\n/P " . ($o['info']['p']);
  1346. $res .= "\n>>\nendobj\n";
  1347. return $res;
  1348. break;
  1349. }
  1350. }
  1351. /**
  1352. * ARC4 functions
  1353. * A series of function to implement ARC4 encoding in PHP
  1354. */
  1355. /**
  1356. * calculate the 16 byte version of the 128 bit md5 digest of the string
  1357. */
  1358. function md5_16($string)
  1359. {
  1360. $tmp = md5( $string );
  1361. $out = '';
  1362. for ( $i = 0; $i <= 30; $i = $i + 2 )
  1363. {
  1364. $out .= chr( hexdec( substr( $tmp, $i, 2 ) ) );
  1365. }
  1366. return $out;
  1367. }
  1368. /**
  1369. * initialize the encryption for processing a particular object
  1370. */
  1371. function encryptInit($id)
  1372. {
  1373. $tmp = $this->encryptionKey;
  1374. $hex = dechex( $id );
  1375. if ( strlen( $hex ) < 6 )
  1376. {
  1377. $hex = substr( '000000', 0, 6 - strlen( $hex ) ) . $hex;
  1378. }
  1379. $tmp .= chr( hexdec( substr( $hex, 4, 2 ) ) ) . chr( hexdec( substr( $hex, 2, 2 ) ) ) . chr( hexdec( substr( $hex, 0, 2 ) ) ) . chr( 0 ) . chr( 0 );
  1380. $key = $this->md5_16( $tmp );
  1381. $this->ARC4_init( substr( $key, 0, 10 ) );
  1382. }
  1383. /**
  1384. * initialize the ARC4 encryption
  1385. */
  1386. function ARC4_init($key = '')
  1387. {
  1388. $this->arc4 = '';
  1389. // setup the control array
  1390. if ( strlen( $key ) == 0 )
  1391. {
  1392. return;
  1393. }
  1394. $k = '';
  1395. while ( strlen( $k ) < 256 )
  1396. {
  1397. $k .= $key;
  1398. }
  1399. $k = substr( $k, 0, 256 );
  1400. for ( $i = 0; $i < 256; $i ++ )
  1401. {
  1402. $this->arc4 .= chr( $i );
  1403. }
  1404. $j = 0;
  1405. for ( $i = 0; $i < 256; $i ++ )
  1406. {
  1407. $t = $this->arc4[$i];
  1408. $j = ($j + ord( $t ) + ord( $k[$i] )) % 256;
  1409. $this->arc4[$i] = $this->arc4[$j];
  1410. $this->arc4[$j] = $t;
  1411. }
  1412. }
  1413. /**
  1414. * ARC4 encrypt a text string
  1415. */
  1416. function ARC4($text)
  1417. {
  1418. $len = strlen( $text );
  1419. $a = 0;
  1420. $b = 0;
  1421. $c = $this->arc4;
  1422. $out = '';
  1423. for ( $i = 0; $i < $len; $i ++ )
  1424. {
  1425. $a = ($a + 1) % 256;
  1426. $t = $c[$a];
  1427. $b = ($b + ord( $t )) % 256;
  1428. $c[$a] = $c[$b];
  1429. $c[$b] = $t;
  1430. $k = ord( $c[(ord( $c[$a] ) + ord( $c[$b] )) % 256] );
  1431. $out .= chr( ord( $text[$i] ) ^ $k );
  1432. }
  1433. return $out;
  1434. }
  1435. /**
  1436. * functions which can be called to adjust or add to the document
  1437. */
  1438. /**
  1439. * add a link in the document to an external URL
  1440. */
  1441. function addLink($url, $x0, $y0, $x1, $y1)
  1442. {
  1443. $this->numObj ++ ;
  1444. $info = array(
  1445. 'type' => 'link',
  1446. 'url' => $url,
  1447. 'rect' => array(
  1448. $x0,
  1449. $y0,
  1450. $x1,
  1451. $y1
  1452. )
  1453. );
  1454. $this->o_annotation( $this->numObj, 'new', $info );
  1455. }
  1456. /**
  1457. * add a link in the document to an internal destination (ie.
  1458. * within the document)
  1459. */
  1460. function addInternalLink($label, $x0, $y0, $x1, $y1)
  1461. {
  1462. $this->numObj ++ ;
  1463. $info = array(
  1464. 'type' => 'ilink',
  1465. 'label' => $label,
  1466. 'rect' => array(
  1467. $x0,
  1468. $y0,
  1469. $x1,
  1470. $y1
  1471. )
  1472. );
  1473. $this->o_annotation( $this->numObj, 'new', $info );
  1474. }
  1475. /**
  1476. * set the encryption of the document
  1477. * can be used to turn it on and/or set the passwords which it will have.
  1478. * also the functions that the user will have are set here, such as print,
  1479. * modify, add
  1480. */
  1481. function setEncryption($userPass = '', $ownerPass = '', $pc = array())
  1482. {
  1483. $p = bindec( 11000000 );
  1484. $options = array(
  1485. 'print' => 4,
  1486. 'modify' => 8,
  1487. 'copy' => 16,
  1488. 'add' => 32
  1489. );
  1490. foreach ( $pc as $k => $v )
  1491. {
  1492. if ( $v && isset( $options[$k] ) )
  1493. {
  1494. $p += $options[$k];
  1495. }
  1496. else if ( isset( $options[$v] ) )
  1497. {
  1498. $p += $options[$v];
  1499. }
  1500. }
  1501. // implement encryption on the document
  1502. if ( $this->arc4_objnum == 0 )
  1503. {
  1504. // then the block does not exist already, add it.
  1505. $this->numObj ++ ;
  1506. if ( strlen( $ownerPass ) == 0 )
  1507. {
  1508. $ownerPass = $userPass;
  1509. }
  1510. $this->o_encryption( $this->numObj, 'new', array(
  1511. 'user' => $userPass,
  1512. 'owner' => $ownerPass,
  1513. 'p' => $p
  1514. ) );
  1515. }
  1516. }
  1517. /**
  1518. * should be used for internal checks, not implemented as yet
  1519. */
  1520. function checkAllHere()
  1521. {
  1522. }
  1523. /**
  1524. * return the pdf stream as a string returned from the function
  1525. */
  1526. function output($debug = 0)
  1527. {
  1528. if ( $debug )
  1529. {
  1530. // turn compression off
  1531. $this->options['compression'] = 0;
  1532. }
  1533. if ( $this->arc4_objnum )
  1534. {
  1535. $this->ARC4_init( $this->encryptionKey );
  1536. }
  1537. $this->checkAllHere( );
  1538. $xref = array();
  1539. $content = "%PDF-1.3\n%‚„œ”\n";
  1540. // $content="%PDF-1.3\n";
  1541. $pos = strlen( $content );
  1542. var_dump($this->objects);
  1543. die();
  1544. foreach ( $this->objects as $k => $v )
  1545. {
  1546. $tmp = 'o_' . $v['t'];
  1547. $cont = $this->$tmp( $k, 'out' );
  1548. $content .= $cont;
  1549. $xref[ ] = $pos;
  1550. $pos += strlen( $cont );
  1551. }
  1552. $content .= "\nxref\n0 " . (count( $xref ) + 1) . "\n0000000000 65535 f \n";
  1553. foreach ( $xref as $p )
  1554. {
  1555. $content .= substr( '0000000000', 0, 10 - strlen( $p ) ) . $p . " 00000 n \n";
  1556. }
  1557. $content .= "\ntrailer\n << /Size " . (count( $xref ) + 1) . "\n /Root 1 0 R\n /Info " . $this->infoObject . " 0 R\n";
  1558. // if encryption has been applied to this document then add the marker
  1559. // for this dictionary
  1560. if ( $this->arc4_objnum > 0 )
  1561. {
  1562. $content .= "/Encrypt " . $this->arc4_objnum . " 0 R\n";
  1563. }
  1564. if ( strlen( $this->fileIdentifier ) )
  1565. {
  1566. $content .= "/ID[<" . $this->fileIdentifier . "><" . $this->fileIdentifier . ">]\n";
  1567. }
  1568. $content .= " >>\nstartxref\n" . $pos . "\n%%EOF\n";
  1569. return $content;
  1570. }
  1571. /**
  1572. * intialize a new document
  1573. * if this is called on an existing document results may be unpredictable,
  1574. * but the existing document would be lost at minimum
  1575. * this function is called automatically by the constructor function
  1576. *
  1577. * @access private
  1578. *
  1579. */
  1580. function newDocument($pageSize = array(0,0,612,792))
  1581. {
  1582. $this->numObj = 0;
  1583. $this->objects = array();
  1584. $this->numObj ++ ;
  1585. $this->o_catalog( $this->numObj, 'new' );
  1586. $this->numObj ++ ;
  1587. $this->o_outlines( $this->numObj, 'new' );
  1588. $this->numObj ++ ;
  1589. $this->o_pages( $this->numObj, 'new' );
  1590. $this->o_pages( $this->numObj, 'mediaBox', $pageSize );
  1591. $this->currentNode = 3;
  1592. $this->numObj ++ ;
  1593. $this->o_procset( $this->numObj, 'new' );
  1594. $this->numObj ++ ;
  1595. $this->o_info( $this->numObj, 'new' );
  1596. $this->numObj ++ ;
  1597. $this->o_page( $this->numObj, 'new' );
  1598. // need to store the first page id as there is no way to get it to the
  1599. // user during
  1600. // startup
  1601. $this->firstPageId = $this->currentContents;
  1602. }
  1603. /**
  1604. * open the font file and return a php structure containing it.
  1605. * first check if this one has been done before and saved in a form more
  1606. * suited to php
  1607. * note that if a php serialized version does not exist it will try and make
  1608. * one, but will
  1609. * require write access to the directory to do it... it is MUCH faster to
  1610. * have these serialized
  1611. * files.
  1612. *
  1613. * @access private
  1614. *
  1615. */
  1616. function openFont($font)
  1617. {
  1618. // assume that $font contains both the path and perhaps the extension to
  1619. // the file, split them
  1620. $pos = strrpos( $font, '/' );
  1621. if ( $pos === false )
  1622. {
  1623. $dir = './';
  1624. $name = $font;
  1625. }
  1626. else
  1627. {
  1628. $dir = substr( $font, 0, $pos + 1 );
  1629. $name = substr( $font, $pos + 1 );
  1630. }
  1631. if ( substr( $name, - 4 ) == '.afm' )
  1632. {
  1633. $name = substr( $name, 0, strlen( $name ) - 4 );
  1634. }
  1635. $this->addMessage( 'openFont: ' . $font . ' - ' . $name );
  1636. if ( file_exists( $dir . 'php_' . $name . '.afm' ) )
  1637. {
  1638. $this->addMessage( 'openFont: php file exists ' . $dir . 'php_' . $name . '.afm' );
  1639. $tmp = file( $dir . 'php_' . $name . '.afm' );
  1640. $this->fonts[$font] = unserialize( $tmp[0] );
  1641. if ( ! isset( $this->fonts[$font]['_version_'] ) || $this->fonts[$font]['_version_'] < 1 )
  1642. {
  1643. // if the font file is old, then clear it out and prepare for
  1644. // re-creation
  1645. $this->addMessage( 'openFont: clear out, make way for new version.' );
  1646. unset( $this->fonts[$font] );
  1647. }
  1648. }
  1649. if ( ! isset( $this->fonts[$font] ) && file_exists( $dir . $name . '.afm' ) )
  1650. {
  1651. // then rebuild the php_<font>.afm file from the <font>.afm file
  1652. $this->addMessage( 'openFont: build php file from ' . $dir . $name . '.afm' );
  1653. $data = array();
  1654. $file = file( $dir . $name . '.afm' );
  1655. foreach ( $file as $rowA )
  1656. {
  1657. $row = trim( $rowA );
  1658. $pos = strpos( $row, ' ' );
  1659. if ( $pos )
  1660. {
  1661. // then there must be some keyword
  1662. $key = substr( $row, 0, $pos );
  1663. switch ( $key )
  1664. {
  1665. case 'FontName' :
  1666. case 'FullName' :
  1667. case 'FamilyName' :
  1668. case 'Weight' :
  1669. case 'ItalicAngle' :
  1670. case 'IsFixedPitch' :
  1671. case 'CharacterSet' :
  1672. case 'UnderlinePosition' :
  1673. case 'UnderlineThickness' :
  1674. case 'Version' :
  1675. case 'EncodingScheme' :
  1676. case 'CapHeight' :
  1677. case 'XHeight' :
  1678. case 'Ascender' :
  1679. case 'Descender' :
  1680. case 'StdHW' :
  1681. case 'StdVW' :
  1682. case 'StartCharMetrics' :
  1683. $data[$key] = trim( substr( $row, $pos ) );
  1684. break;
  1685. case 'FontBBox' :
  1686. $data[$key] = explode( ' ', trim( substr( $row, $pos ) ) );
  1687. break;
  1688. case 'C' :
  1689. // C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
  1690. $bits = explode( ';', trim( $row ) );
  1691. $dtmp = array();
  1692. foreach ( $bits as $bit )
  1693. {
  1694. $bits2 = explode( ' ', trim( $bit ) );
  1695. if ( strlen( $bits2[0] ) )
  1696. {
  1697. if ( count( $bits2 ) > 2 )
  1698. {
  1699. $dtmp[$bits2[0]] = array();
  1700. for ( $i = 1; $i < count( $bits2 ); $i ++ )
  1701. {
  1702. $dtmp[$bits2[0]][ ] = $bits2[$i];
  1703. }
  1704. }
  1705. else if ( count( $bits2 ) == 2 )
  1706. {
  1707. $dtmp[$bits2[0]] = $bits2[1];
  1708. }
  1709. }
  1710. }
  1711. if ( $dtmp['C'] >= 0 )
  1712. {
  1713. $data['C'][$dtmp['C']] = $dtmp;
  1714. $data['C'][$dtmp['N']] = $dtmp;
  1715. }
  1716. else
  1717. {
  1718. $data['C'][$dtmp['N']] = $dtmp;
  1719. }
  1720. break;
  1721. case 'KPX' :
  1722. // KPX Adieresis yacute -40
  1723. $bits = explode( ' ', trim( $row ) );
  1724. $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
  1725. break;
  1726. }
  1727. }
  1728. }
  1729. $data['_version_'] = 1;
  1730. $this->fonts[$font] = $data;
  1731. $fp = fopen( $dir . 'php_' . $name . '.afm', 'w' );
  1732. fwrite( $fp, serialize( $data ) );
  1733. fclose( $fp );
  1734. }
  1735. else if ( ! isset( $this->fonts[$font] ) )
  1736. {
  1737. $this->addMessage( 'openFont: no font file found' );
  1738. // echo 'Font not Found '.$font;
  1739. }
  1740. }
  1741. /**
  1742. * if the font is not loaded then load it and make the required object
  1743. * else just make it the current font
  1744. * the encoding array can contain 'encoding'=>
  1745. * 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
  1746. * note that encoding='none' will need to be used for symbolic fonts
  1747. * and 'differences' => an array of mappings between numbers 0->255 and
  1748. * character names.
  1749. */
  1750. function selectFont($fontName, $encoding = '', $set = 1)
  1751. {
  1752. if ( ! isset( $this->fonts[$fontName] ) )
  1753. {
  1754. // load the file
  1755. $this->openFont( $fontName );
  1756. if ( isset( $this->fonts[$fontName] ) )
  1757. {
  1758. $this->numObj ++ ;
  1759. $this->numFonts ++ ;
  1760. $pos = strrpos( $fontName, '/' );
  1761. // $dir=substr($fontName,0,$pos+1);
  1762. $name = substr( $fontName, $pos + 1 );
  1763. if ( substr( $name, - 4 ) == '.afm' )
  1764. {
  1765. $name = substr( $name, 0, strlen( $name ) - 4 );
  1766. }
  1767. $options = array(
  1768. 'name' => $name
  1769. );
  1770. if ( is_array( $encoding ) )
  1771. {
  1772. // then encoding and differences might be set
  1773. if ( isset( $encoding['encoding'] ) )
  1774. {
  1775. $options['encoding'] = $encoding['encoding'];
  1776. }
  1777. if ( isset( $encoding['differences'] ) )
  1778. {
  1779. $options['differences'] = $encoding['differences'];
  1780. }
  1781. }
  1782. else if ( strlen( $encoding ) )
  1783. {
  1784. // then perhaps only the encoding has been set
  1785. $options['encoding'] = $encoding;
  1786. }
  1787. $fontObj = $this->numObj;
  1788. $this->o_font( $this->numObj, 'new', $options );
  1789. $this->fonts[$fontName]['fontNum'] = $this->numFonts;
  1790. // if this is a '.afm' font, and there is a '.pfa' file to go
  1791. // with it ( as there
  1792. // should be for all non-basic fonts), then load it into an
  1793. // object and put the
  1794. // references into the font object
  1795. $basefile = substr( $fontName, 0, strlen( $fontName ) - 4 );
  1796. if ( file_exists( $basefile . '.pfb' ) )
  1797. {
  1798. $fbtype = 'pfb';
  1799. }
  1800. else if ( file_exists( $basefile . '.ttf' ) )
  1801. {
  1802. $fbtype = 'ttf';
  1803. }
  1804. else
  1805. {
  1806. $fbtype = '';
  1807. }
  1808. $fbfile = $basefile . '.' . $fbtype;
  1809. // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
  1810. // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
  1811. $this->addMessage( 'selectFont: checking for - ' . $fbfile );
  1812. if ( substr( $fontName, - 4 ) == '.afm' && strlen( $fbtype ) )
  1813. {
  1814. $adobeFontName = $this->fonts[$fontName]['FontName'];
  1815. // $fontObj = $this->numObj;
  1816. $this->addMessage( 'selectFont: adding font file - ' . $fbfile . ' - ' . $adobeFontName );
  1817. // find the array of fond widths, and put that into an
  1818. // object.
  1819. $firstChar = - 1;
  1820. $lastChar = 0;
  1821. $widths = array();
  1822. foreach ( $this->fonts[$fontName]['C'] as $num => $d )
  1823. {
  1824. if ( intval( $num ) > 0 || $num == '0' )
  1825. {
  1826. if ( $lastChar > 0 && $num > $lastChar + 1 )
  1827. {
  1828. for ( $i = $lastChar + 1; $i < $num; $i ++ )
  1829. {
  1830. $widths[ ] = 0;
  1831. }
  1832. }
  1833. $widths[ ] = $d['WX'];
  1834. if ( $firstChar == - 1 )
  1835. {
  1836. $firstChar = $num;
  1837. }
  1838. $lastChar = $num;
  1839. }
  1840. }
  1841. // also need to adjust the widths for the differences array
  1842. if ( isset( $options['differences'] ) )
  1843. {
  1844. foreach ( $options['differences'] as $charNum => $charName )
  1845. {
  1846. if ( $charNum > $lastChar )
  1847. {
  1848. for ( $i = $lastChar + 1; $i <= $charNum; $i ++ )
  1849. {
  1850. $widths[ ] = 0;
  1851. }
  1852. $lastChar = $charNum;
  1853. }
  1854. if ( isset( $this->fonts[$fontName]['C'][$charName] ) )
  1855. {
  1856. $widths[$charNum - $firstChar] = $this->fonts[$fontName]['C'][$charName]['WX'];
  1857. }
  1858. }
  1859. }
  1860. $this->addMessage( 'selectFont: FirstChar=' . $firstChar );
  1861. $this->addMessage( 'selectFont: LastChar=' . $lastChar );
  1862. $this->numObj ++ ;
  1863. $this->o_contents( $this->numObj, 'new', 'raw' );
  1864. $this->objects[$this->numObj]['c'] .= '[';
  1865. foreach ( $widths as $width )
  1866. {
  1867. $this->objects[$this->numObj]['c'] .= ' ' . $width;
  1868. }
  1869. $this->objects[$this->numObj]['c'] .= ' ]';
  1870. $widthid = $this->numObj;
  1871. // load the pfb file, and put that into an object too.
  1872. // note that pdf supports only binary format type 1 font
  1873. // files, though there is a
  1874. // simple utility to convert them from pfa to pfb.
  1875. $fp = fopen( $fbfile, 'rb' );
  1876. $tmp = get_magic_quotes_runtime( );
  1877. set_magic_quotes_runtime( 0 );
  1878. $data = fread( $fp, filesize( $fbfile ) );
  1879. set_magic_quotes_runtime( $tmp );
  1880. fclose( $fp );
  1881. // create the font descriptor
  1882. $this->numObj ++ ;
  1883. $fontDescriptorId = $this->numObj;
  1884. $this->numObj ++ ;
  1885. $pfbid = $this->numObj;
  1886. // determine flags (more than a little flakey, hopefully
  1887. // will not matter much)
  1888. $flags = 0;
  1889. if ( $this->fonts[$fontName]['ItalicAngle'] != 0 )
  1890. {
  1891. $flags += pow( 2, 6 );
  1892. }
  1893. if ( $this->fonts[$fontName]['IsFixedPitch'] == 'true' )
  1894. {
  1895. $flags += 1;
  1896. }
  1897. $flags += pow( 2, 5 ); // assume non-sybolic
  1898. $list = array(
  1899. 'Ascent' => 'Ascender',
  1900. 'CapHeight' => 'CapHeight',

Large files files are truncated, but you can click here to view the full file