PageRenderTime 56ms CodeModel.GetById 18ms 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
  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',
  1901. 'Descent' => 'Descender',
  1902. 'FontBBox' => 'FontBBox',
  1903. 'ItalicAngle' => 'ItalicAngle'
  1904. );
  1905. $fdopt = array(
  1906. 'Flags' => $flags,
  1907. 'FontName' => $adobeFontName,
  1908. 'StemV' => 100 // don't know what the value for this should be!
  1909. );
  1910. foreach ( $list as $k => $v )
  1911. {
  1912. if ( isset( $this->fonts[$fontName][$v] ) )
  1913. {
  1914. $fdopt[$k] = $this->fonts[$fontName][$v];
  1915. }
  1916. }
  1917. if ( $fbtype == 'pfb' )
  1918. {
  1919. $fdopt['FontFile'] = $pfbid;
  1920. }
  1921. else if ( $fbtype == 'ttf' )
  1922. {
  1923. $fdopt['FontFile2'] = $pfbid;
  1924. }
  1925. $this->o_fontDescriptor( $fontDescriptorId, 'new', $fdopt );
  1926. // embed the font program
  1927. $this->o_contents( $this->numObj, 'new' );
  1928. $this->objects[$pfbid]['c'] .= $data;
  1929. // determine the cruicial lengths within this file
  1930. if ( $fbtype == 'pfb' )
  1931. {
  1932. $l1 = strpos( $data, 'eexec' ) + 6;
  1933. $l2 = strpos( $data, '00000000' ) - $l1;
  1934. $l3 = strlen( $data ) - $l2 - $l1;
  1935. $this->o_contents( $this->numObj, 'add', array(
  1936. 'Length1' => $l1,
  1937. 'Length2' => $l2,
  1938. 'Length3' => $l3
  1939. ) );
  1940. }
  1941. else if ( $fbtype == 'ttf' )
  1942. {
  1943. $l1 = strlen( $data );
  1944. $this->o_contents( $this->numObj, 'add', array(
  1945. 'Length1' => $l1
  1946. ) );
  1947. }
  1948. // tell the font object about all this new stuff
  1949. $tmp = array(
  1950. 'BaseFont' => $adobeFontName,
  1951. 'Widths' => $widthid,
  1952. 'FirstChar' => $firstChar,
  1953. 'LastChar' => $lastChar,
  1954. 'FontDescriptor' => $fontDescriptorId
  1955. );
  1956. if ( $fbtype == 'ttf' )
  1957. {
  1958. $tmp['SubType'] = 'TrueType';
  1959. }
  1960. $this->addMessage( 'adding extra info to font.(' . $fontObj . ')' );
  1961. foreach ( $tmp as $fk => $fv )
  1962. {
  1963. $this->addMessage( $fk . " : " . $fv );
  1964. }
  1965. $this->o_font( $fontObj, 'add', $tmp );
  1966. }
  1967. else
  1968. {
  1969. $this->addMessage( 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts' );
  1970. }
  1971. // also set the differences here, note that this means that
  1972. // these will take effect only the
  1973. // first time that a font is selected, else they are ignored
  1974. if ( isset( $options['differences'] ) )
  1975. {
  1976. $this->fonts[$fontName]['differences'] = $options['differences'];
  1977. }
  1978. }
  1979. }
  1980. if ( $set && isset( $this->fonts[$fontName] ) )
  1981. {
  1982. // so if for some reason the font was not set in the last one then
  1983. // it will not be selected
  1984. $this->currentBaseFont = $fontName;
  1985. // the next line means that if a new font is selected, then the
  1986. // current text state will be
  1987. // applied to it as well.
  1988. $this->setCurrentFont( );
  1989. }
  1990. return $this->currentFontNum;
  1991. }
  1992. /**
  1993. * sets up the current font, based on the font families, and the current
  1994. * text state
  1995. * note that this system is quite flexible, a <b><i> font can be completely
  1996. * different to a
  1997. * <i><b> font, and even <b><b> will have to be defined within the family to
  1998. * have meaning
  1999. * This function is to be called whenever the currentTextState is changed,
  2000. * it will update
  2001. * the currentFont setting to whatever the appropriatte family one is.
  2002. * If the user calls selectFont themselves then that will reset the
  2003. * currentBaseFont, and the currentFont
  2004. * This function will change the currentFont to whatever it should be, but
  2005. * will not change the
  2006. * currentBaseFont.
  2007. *
  2008. * @access private
  2009. *
  2010. */
  2011. function setCurrentFont()
  2012. {
  2013. if ( strlen( $this->currentBaseFont ) == 0 )
  2014. {
  2015. // then assume an initial font
  2016. $this->selectFont( './fonts/Helvetica.afm' );
  2017. }
  2018. $cf = substr( $this->currentBaseFont, strrpos( $this->currentBaseFont, '/' ) + 1 );
  2019. if ( strlen( $this->currentTextState ) && isset( $this->fontFamilies[$cf] ) && isset( $this->fontFamilies[$cf][$this->currentTextState] ) )
  2020. {
  2021. // then we are in some state or another
  2022. // and this font has a family, and the current setting exists within
  2023. // it
  2024. // select the font, then return it
  2025. $nf = substr( $this->currentBaseFont, 0, strrpos( $this->currentBaseFont, '/' ) + 1 ) . $this->fontFamilies[$cf][$this->currentTextState];
  2026. $this->selectFont( $nf, '', 0 );
  2027. $this->currentFont = $nf;
  2028. $this->currentFontNum = $this->fonts[$nf]['fontNum'];
  2029. }
  2030. else
  2031. {
  2032. // the this font must not have the right family member for the
  2033. // current state
  2034. // simply assume the base font
  2035. $this->currentFont = $this->currentBaseFont;
  2036. $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
  2037. }
  2038. }
  2039. /**
  2040. * function for the user to find out what the ID is of the first page that
  2041. * was created during
  2042. * startup - useful if they wish to add something to it later.
  2043. */
  2044. function getFirstPageId()
  2045. {
  2046. return $this->firstPageId;
  2047. }
  2048. /**
  2049. * add content to the currently active object
  2050. *
  2051. * @access private
  2052. *
  2053. */
  2054. function addContent($content)
  2055. {
  2056. $this->objects[$this->currentContents]['c'] .= $content;
  2057. }
  2058. /**
  2059. * sets the colour for fill operations
  2060. */
  2061. function setColor($r, $g, $b, $force = 0)
  2062. {
  2063. if ( $r >= 0 && ($force || $r != $this->currentColour['r'] || $g != $this->currentColour['g'] || $b != $this->currentColour['b']) )
  2064. {
  2065. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $r ) . ' ' . sprintf( '%.3f', $g ) . ' ' . sprintf( '%.3f', $b ) . ' rg';
  2066. $this->currentColour = array(
  2067. 'r' => $r,
  2068. 'g' => $g,
  2069. 'b' => $b
  2070. );
  2071. }
  2072. }
  2073. /**
  2074. * sets the colour for stroke operations
  2075. */
  2076. function setStrokeColor($r, $g, $b, $force = 0)
  2077. {
  2078. if ( $r >= 0 && ($force || $r != $this->currentStrokeColour['r'] || $g != $this->currentStrokeColour['g'] || $b != $this->currentStrokeColour['b']) )
  2079. {
  2080. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $r ) . ' ' . sprintf( '%.3f', $g ) . ' ' . sprintf( '%.3f', $b ) . ' RG';
  2081. $this->currentStrokeColour = array(
  2082. 'r' => $r,
  2083. 'g' => $g,
  2084. 'b' => $b
  2085. );
  2086. }
  2087. }
  2088. /**
  2089. * draw a line from one set of coordinates to another
  2090. */
  2091. function line($x1, $y1, $x2, $y2)
  2092. {
  2093. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $x1 ) . ' ' . sprintf( '%.3f', $y1 ) . ' m ' . sprintf( '%.3f', $x2 ) . ' ' . sprintf( '%.3f', $y2 ) . ' l S';
  2094. }
  2095. /**
  2096. * draw a bezier curve based on 4 control points
  2097. */
  2098. function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
  2099. {
  2100. // in the current line style, draw a bezier curve from (x0,y0) to
  2101. // (x3,y3) using the other two points
  2102. // as the control points for the curve.
  2103. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $x0 ) . ' ' . sprintf( '%.3f', $y0 ) . ' m ' . sprintf( '%.3f', $x1 ) . ' ' . sprintf( '%.3f', $y1 );
  2104. $this->objects[$this->currentContents]['c'] .= ' ' . sprintf( '%.3f', $x2 ) . ' ' . sprintf( '%.3f', $y2 ) . ' ' . sprintf( '%.3f', $x3 ) . ' ' . sprintf( '%.3f', $y3 ) . ' c S';
  2105. }
  2106. /**
  2107. * draw a part of an ellipse
  2108. */
  2109. function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
  2110. {
  2111. $this->ellipse( $x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, 0 );
  2112. }
  2113. /**
  2114. * draw a filled ellipse
  2115. */
  2116. function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
  2117. {
  2118. return $this->ellipse( $x0, $y0, $r1, $r2 = 0, $angle, $nSeg, $astart, $afinish, 1, 1 );
  2119. }
  2120. /**
  2121. * draw an ellipse
  2122. * note that the part and filled ellipse are just special cases of this
  2123. * function
  2124. *
  2125. * draws an ellipse in the current line style
  2126. * centered at $x0,$y0, radii $r1,$r2
  2127. * if $r2 is not set, then a circle is drawn
  2128. * nSeg is not allowed to be less than 2, as this will simply draw a line
  2129. * (and will even draw a
  2130. * pretty crappy shape at 2, as we are approximating with bezier curves.
  2131. */
  2132. function ellipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360, $close = 1, $fill = 0)
  2133. {
  2134. if ( $r1 == 0 )
  2135. {
  2136. return;
  2137. }
  2138. if ( $r2 == 0 )
  2139. {
  2140. $r2 = $r1;
  2141. }
  2142. if ( $nSeg < 2 )
  2143. {
  2144. $nSeg = 2;
  2145. }
  2146. $astart = deg2rad( ( float )$astart );
  2147. $afinish = deg2rad( ( float )$afinish );
  2148. $totalAngle = $afinish - $astart;
  2149. $dt = $totalAngle / $nSeg;
  2150. $dtm = $dt / 3;
  2151. if ( $angle != 0 )
  2152. {
  2153. $a = - 1 * deg2rad( ( float )$angle );
  2154. $tmp = "\n q ";
  2155. $tmp .= sprintf( '%.3f', cos( $a ) ) . ' ' . sprintf( '%.3f', (- 1.0 * sin( $a )) ) . ' ' . sprintf( '%.3f', sin( $a ) ) . ' ' . sprintf( '%.3f', cos( $a ) ) . ' ';
  2156. $tmp .= sprintf( '%.3f', $x0 ) . ' ' . sprintf( '%.3f', $y0 ) . ' cm';
  2157. $this->objects[$this->currentContents]['c'] .= $tmp;
  2158. $x0 = 0;
  2159. $y0 = 0;
  2160. }
  2161. $t1 = $astart;
  2162. $a0 = $x0 + $r1 * cos( $t1 );
  2163. $b0 = $y0 + $r2 * sin( $t1 );
  2164. $c0 = - $r1 * sin( $t1 );
  2165. $d0 = $r2 * cos( $t1 );
  2166. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $a0 ) . ' ' . sprintf( '%.3f', $b0 ) . ' m ';
  2167. for ( $i = 1; $i <= $nSeg; $i ++ )
  2168. {
  2169. // draw this bit of the total curve
  2170. $t1 = $i * $dt + $astart;
  2171. $a1 = $x0 + $r1 * cos( $t1 );
  2172. $b1 = $y0 + $r2 * sin( $t1 );
  2173. $c1 = - $r1 * sin( $t1 );
  2174. $d1 = $r2 * cos( $t1 );
  2175. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', ($a0 + $c0 * $dtm) ) . ' ' . sprintf( '%.3f', ($b0 + $d0 * $dtm) );
  2176. $this->objects[$this->currentContents]['c'] .= ' ' . sprintf( '%.3f', ($a1 - $c1 * $dtm) ) . ' ' . sprintf( '%.3f', ($b1 - $d1 * $dtm) ) . ' ' . sprintf( '%.3f', $a1 ) . ' ' . sprintf( '%.3f', $b1 ) . ' c';
  2177. $a0 = $a1;
  2178. $b0 = $b1;
  2179. $c0 = $c1;
  2180. $d0 = $d1;
  2181. }
  2182. if ( $fill )
  2183. {
  2184. $this->objects[$this->currentContents]['c'] .= ' f';
  2185. }
  2186. else
  2187. {
  2188. if ( $close )
  2189. {
  2190. $this->objects[$this->currentContents]['c'] .= ' s'; // small 's'
  2191. // signifies closing the
  2192. // path as well
  2193. }
  2194. else
  2195. {
  2196. $this->objects[$this->currentContents]['c'] .= ' S';
  2197. }
  2198. }
  2199. if ( $angle != 0 )
  2200. {
  2201. $this->objects[$this->currentContents]['c'] .= ' Q';
  2202. }
  2203. }
  2204. /**
  2205. * this sets the line drawing style.
  2206. * width, is the thickness of the line in user units
  2207. * cap is the type of cap to put on the line, values can be
  2208. * 'butt','round','square'
  2209. * where the diffference between 'square' and 'butt' is that 'square'
  2210. * projects a flat end past the
  2211. * end of the line.
  2212. * join can be 'miter', 'round', 'bevel'
  2213. * dash is an array which sets the dash pattern, is a series of length
  2214. * values, which are the lengths of the
  2215. * on and off dashes.
  2216. * (2) represents 2 on, 2 off, 2 on , 2 off ...
  2217. * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
  2218. * phase is a modifier on the dash pattern which is used to shift the point
  2219. * at which the pattern starts.
  2220. */
  2221. function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
  2222. {
  2223. // this is quite inefficient in that it sets all the parameters whenever
  2224. // 1 is changed, but will fix another day
  2225. $string = '';
  2226. if ( $width > 0 )
  2227. {
  2228. $string .= $width . ' w';
  2229. }
  2230. $ca = array(
  2231. 'butt' => 0,
  2232. 'round' => 1,
  2233. 'square' => 2
  2234. );
  2235. if ( isset( $ca[$cap] ) )
  2236. {
  2237. $string .= ' ' . $ca[$cap] . ' J';
  2238. }
  2239. $ja = array(
  2240. 'miter' => 0,
  2241. 'round' => 1,
  2242. 'bevel' => 2
  2243. );
  2244. if ( isset( $ja[$join] ) )
  2245. {
  2246. $string .= ' ' . $ja[$join] . ' j';
  2247. }
  2248. if ( is_array( $dash ) )
  2249. {
  2250. $string .= ' [';
  2251. foreach ( $dash as $len )
  2252. {
  2253. $string .= ' ' . $len;
  2254. }
  2255. $string .= ' ] ' . $phase . ' d';
  2256. }
  2257. $this->currentLineStyle = $string;
  2258. $this->objects[$this->currentContents]['c'] .= "\n" . $string;
  2259. }
  2260. /**
  2261. * draw a polygon, the syntax for this is similar to the GD polygon command
  2262. */
  2263. function polygon($p, $np, $f = 0)
  2264. {
  2265. $this->objects[$this->currentContents]['c'] .= "\n";
  2266. $this->objects[$this->currentContents]['c'] .= sprintf( '%.3f', $p[0] ) . ' ' . sprintf( '%.3f', $p[1] ) . ' m ';
  2267. for ( $i = 2; $i < $np * 2; $i = $i + 2 )
  2268. {
  2269. $this->objects[$this->currentContents]['c'] .= sprintf( '%.3f', $p[$i] ) . ' ' . sprintf( '%.3f', $p[$i + 1] ) . ' l ';
  2270. }
  2271. if ( $f == 1 )
  2272. {
  2273. $this->objects[$this->currentContents]['c'] .= ' f';
  2274. }
  2275. else
  2276. {
  2277. $this->objects[$this->currentContents]['c'] .= ' S';
  2278. }
  2279. }
  2280. /**
  2281. * a filled rectangle, note that it is the width and height of the rectangle
  2282. * which are the secondary paramaters, not
  2283. * the coordinates of the upper-right corner
  2284. */
  2285. function filledRectangle($x1, $y1, $width, $height)
  2286. {
  2287. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $x1 ) . ' ' . sprintf( '%.3f', $y1 ) . ' ' . sprintf( '%.3f', $width ) . ' ' . sprintf( '%.3f', $height ) . ' re f';
  2288. }
  2289. /**
  2290. * draw a rectangle, note that it is the width and height of the rectangle
  2291. * which are the secondary paramaters, not
  2292. * the coordinates of the upper-right corner
  2293. */
  2294. function rectangle($x1, $y1, $width, $height)
  2295. {
  2296. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $x1 ) . ' ' . sprintf( '%.3f', $y1 ) . ' ' . sprintf( '%.3f', $width ) . ' ' . sprintf( '%.3f', $height ) . ' re S';
  2297. }
  2298. /**
  2299. * add a new page to the document
  2300. * this also makes the new page the current active object
  2301. */
  2302. function newPage($insert = 0, $id = 0, $pos = 'after')
  2303. {
  2304. // if there is a state saved, then go up the stack closing them
  2305. // then on the new page, re-open them with the right setings
  2306. if ( $this->nStateStack )
  2307. {
  2308. for ( $i = $this->nStateStack; $i >= 1; $i -- )
  2309. {
  2310. $this->restoreState( $i );
  2311. }
  2312. }
  2313. $this->numObj ++ ;
  2314. if ( $insert )
  2315. {
  2316. // the id from the ezPdf class is the od of the contents of the
  2317. // page, not the page object itself
  2318. // query that object to find the parent
  2319. $rid = $this->objects[$id]['onPage'];
  2320. $opt = array(
  2321. 'rid' => $rid,
  2322. 'pos' => $pos
  2323. );
  2324. $this->o_page( $this->numObj, 'new', $opt );
  2325. }
  2326. else
  2327. {
  2328. $this->o_page( $this->numObj, 'new' );
  2329. }
  2330. // if there is a stack saved, then put that onto the page
  2331. if ( $this->nStateStack )
  2332. {
  2333. for ( $i = 1; $i <= $this->nStateStack; $i ++ )
  2334. {
  2335. $this->saveState( $i );
  2336. }
  2337. }
  2338. // and if there has been a stroke or fill colour set, then transfer them
  2339. if ( $this->currentColour['r'] >= 0 )
  2340. {
  2341. $this->setColor( $this->currentColour['r'], $this->currentColour['g'], $this->currentColour['b'], 1 );
  2342. }
  2343. if ( $this->currentStrokeColour['r'] >= 0 )
  2344. {
  2345. $this->setStrokeColor( $this->currentStrokeColour['r'], $this->currentStrokeColour['g'], $this->currentStrokeColour['b'], 1 );
  2346. }
  2347. // if there is a line style set, then put this in too
  2348. if ( strlen( $this->currentLineStyle ) )
  2349. {
  2350. $this->objects[$this->currentContents]['c'] .= "\n" . $this->currentLineStyle;
  2351. }
  2352. // the call to the o_page object set currentContents to the present
  2353. // page, so this can be returned as the page id
  2354. return $this->currentContents;
  2355. }
  2356. /**
  2357. * output the pdf code, streaming it to the browser
  2358. * the relevant headers are set so that hopefully the browser will recognise
  2359. * it
  2360. */
  2361. function stream($options = '')
  2362. {
  2363. // setting the options allows the adjustment of the headers
  2364. // values at the moment are:
  2365. // 'Content-Disposition'=>'filename' - sets the filename, though not too
  2366. // sure how well this will
  2367. // work as in my trial the browser seems to use the filename of the php
  2368. // file with .pdf on the end
  2369. // 'Accept-Ranges'=>1 or 0 - if this is not set to 1, then this header
  2370. // is not included, off by default
  2371. // this header seems to have caused some problems despite tha fact that
  2372. // it is supposed to solve
  2373. // them, so I am leaving it off by default.
  2374. // 'compress'=> 1 or 0 - apply content stream compression, this is on
  2375. // (1) by default
  2376. if ( ! is_array( $options ) )
  2377. {
  2378. $options = array();
  2379. }
  2380. if ( isset( $options['compress'] ) && $options['compress'] == 0 )
  2381. {
  2382. $tmp = $this->output( 1 );
  2383. }
  2384. else
  2385. {
  2386. $tmp = $this->output( );
  2387. }
  2388. header( "Content-type: application/pdf" );
  2389. header( "Content-Length: " . strlen( ltrim( $tmp ) ) );
  2390. $fileName = (isset( $options['Content-Disposition'] ) ? $options['Content-Disposition'] : 'file.pdf');
  2391. header( "Content-Disposition: inline; filename=" . $fileName );
  2392. if ( isset( $options['Accept-Ranges'] ) && $options['Accept-Ranges'] == 1 )
  2393. {
  2394. header( "Accept-Ranges: " . strlen( ltrim( $tmp ) ) );
  2395. }
  2396. echo ltrim( $tmp );
  2397. }
  2398. /**
  2399. * return the height in units of the current font in the given size
  2400. */
  2401. function getFontHeight($size)
  2402. {
  2403. if ( ! $this->numFonts )
  2404. {
  2405. $this->selectFont( './fonts/Helvetica' );
  2406. }
  2407. // for the current font, and the given size, what is the height of the
  2408. // font in user units
  2409. $h = $this->fonts[$this->currentFont]['FontBBox'][3] - $this->fonts[$this->currentFont]['FontBBox'][1];
  2410. return $size * $h / 1000;
  2411. }
  2412. /**
  2413. * return the font decender, this will normally return a negative number
  2414. * if you add this number to the baseline, you get the level of the bottom
  2415. * of the font
  2416. * it is in the pdf user units
  2417. */
  2418. function getFontDecender($size)
  2419. {
  2420. // note that this will most likely return a negative value
  2421. if ( ! $this->numFonts )
  2422. {
  2423. $this->selectFont( './fonts/Helvetica' );
  2424. }
  2425. $h = $this->fonts[$this->currentFont]['FontBBox'][1];
  2426. return $size * $h / 1000;
  2427. }
  2428. /**
  2429. * filter the text, this is applied to all text just before being inserted
  2430. * into the pdf document
  2431. * it escapes the various things that need to be escaped, and so on
  2432. *
  2433. * @access private
  2434. *
  2435. */
  2436. function filterText($text)
  2437. {
  2438. $text = str_replace( '\\', '\\\\', $text );
  2439. $text = str_replace( '(', '\(', $text );
  2440. $text = str_replace( ')', '\)', $text );
  2441. $text = str_replace( '&lt;', '<', $text );
  2442. $text = str_replace( '&gt;', '>', $text );
  2443. $text = str_replace( '&#039;', '\'', $text );
  2444. $text = str_replace( '&quot;', '"', $text );
  2445. $text = str_replace( '&amp;', '&', $text );
  2446. return $text;
  2447. }
  2448. /**
  2449. * given a start position and information about how text is to be laid out,
  2450. * calculate where
  2451. * on the page the text will end
  2452. *
  2453. * @access private
  2454. *
  2455. */
  2456. function PRVTgetTextPosition($x, $y, $angle, $size, $wa, $text)
  2457. {
  2458. // given this information return an array containing x and y for the end
  2459. // position as elements 0 and 1
  2460. $w = $this->getTextWidth( $size, $text );
  2461. // need to adjust for the number of spaces in this text
  2462. $words = explode( ' ', $text );
  2463. $nspaces = count( $words ) - 1;
  2464. $w += $wa * $nspaces;
  2465. $a = deg2rad( ( float )$angle );
  2466. return array(
  2467. cos( $a ) * $w + $x,
  2468. - sin( $a ) * $w + $y
  2469. );
  2470. }
  2471. /**
  2472. * wrapper function for PRVTcheckTextDirective1
  2473. *
  2474. * @access private
  2475. *
  2476. */
  2477. function PRVTcheckTextDirective(&$text, $i, &$f)
  2478. {
  2479. $x = 0;
  2480. $y = 0;
  2481. return $this->PRVTcheckTextDirective1( $text, $i, $f, 0, $x, $y );
  2482. }
  2483. /**
  2484. * checks if the text stream contains a control directive
  2485. * if so then makes some changes and returns the number of characters
  2486. * involved in the directive
  2487. * this has been re-worked to include everything neccesary to fins the
  2488. * current writing point, so that
  2489. * the location can be sent to the callback function if required
  2490. * if the directive does not require a font change, then $f should be set to
  2491. * 0
  2492. *
  2493. * @access private
  2494. *
  2495. */
  2496. function PRVTcheckTextDirective1(&$text, $i, &$f, $final, &$x, &$y, $size = 0, $angle = 0, $wordSpaceAdjust = 0)
  2497. {
  2498. $directive = 0;
  2499. $j = $i;
  2500. if ( $text[$j] == '<' )
  2501. {
  2502. $j ++ ;
  2503. switch ( $text[$j] )
  2504. {
  2505. case '/' :
  2506. $j ++ ;
  2507. if ( strlen( $text ) <= $j )
  2508. {
  2509. return $directive;
  2510. }
  2511. switch ( $text[$j] )
  2512. {
  2513. case 'b' :
  2514. case 'i' :
  2515. $j ++ ;
  2516. if ( $text[$j] == '>' )
  2517. {
  2518. $p = strrpos( $this->currentTextState, $text[$j - 1] );
  2519. if ( $p !== false )
  2520. {
  2521. // then there is one to remove
  2522. $this->currentTextState = substr( $this->currentTextState, 0, $p ) . substr( $this->currentTextState, $p + 1 );
  2523. }
  2524. $directive = $j - $i + 1;
  2525. }
  2526. break;
  2527. case 'c' :
  2528. // this this might be a callback function
  2529. $j ++ ;
  2530. $k = strpos( $text, '>', $j );
  2531. if ( $k !== false && $text[$j] == ':' )
  2532. {
  2533. // then this will be treated as a callback
  2534. // directive
  2535. $directive = $k - $i + 1;
  2536. $f = 0;
  2537. // split the remainder on colons to get the
  2538. // function name and the paramater
  2539. $tmp = substr( $text, $j + 1, $k - $j - 1 );
  2540. $b1 = strpos( $tmp, ':' );
  2541. if ( $b1 !== false )
  2542. {
  2543. $func = substr( $tmp, 0, $b1 );
  2544. $parm = substr( $tmp, $b1 + 1 );
  2545. }
  2546. else
  2547. {
  2548. $func = $tmp;
  2549. $parm = '';
  2550. }
  2551. if ( ! isset( $func ) || ! strlen( trim( $func ) ) )
  2552. {
  2553. $directive = 0;
  2554. }
  2555. else
  2556. {
  2557. // only call the function if this is the
  2558. // final call
  2559. if ( $final )
  2560. {
  2561. // need to assess the text position,
  2562. // calculate the text width to this
  2563. // point
  2564. // can use getTextWidth to find the text
  2565. // width I think
  2566. $tmp = $this->PRVTgetTextPosition( $x, $y, $angle, $size, $wordSpaceAdjust, substr( $text, 0, $i ) );
  2567. $info = array(
  2568. 'x' => $tmp[0],
  2569. 'y' => $tmp[1],
  2570. 'angle' => $angle,
  2571. 'status' => 'end',
  2572. 'p' => $parm,
  2573. 'nCallback' => $this->nCallback
  2574. );
  2575. $x = $tmp[0];
  2576. $y = $tmp[1];
  2577. $ret = $this->$func( $info );
  2578. if ( is_array( $ret ) )
  2579. {
  2580. // then the return from the callback
  2581. // function could set the position,
  2582. // to start with, later will do font
  2583. // colour, and font
  2584. foreach ( $ret as $rk => $rv )
  2585. {
  2586. switch ( $rk )
  2587. {
  2588. case 'x' :
  2589. case 'y' :
  2590. $$rk = $rv;
  2591. break;
  2592. }
  2593. }
  2594. }
  2595. // also remove from to the stack
  2596. // for simplicity, just take from the
  2597. // end, fix this another day
  2598. $this->nCallback -- ;
  2599. if ( $this->nCallback < 0 )
  2600. {
  2601. $this->nCallBack = 0;
  2602. }
  2603. }
  2604. }
  2605. }
  2606. break;
  2607. }
  2608. break;
  2609. case 'b' :
  2610. case 'i' :
  2611. $j ++ ;
  2612. if ( $text[$j] == '>' )
  2613. {
  2614. $this->currentTextState .= $text[$j - 1];
  2615. $directive = $j - $i + 1;
  2616. }
  2617. break;
  2618. case 'C' :
  2619. $noClose = 1;
  2620. case 'c' :
  2621. // this this might be a callback function
  2622. $j ++ ;
  2623. $k = strpos( $text, '>', $j );
  2624. if ( $k !== false && $text[$j] == ':' )
  2625. {
  2626. // then this will be treated as a callback directive
  2627. $directive = $k - $i + 1;
  2628. $f = 0;
  2629. // split the remainder on colons to get the function
  2630. // name and the paramater
  2631. // $bits = explode(':',substr($text,$j+1,$k-$j-1));
  2632. $tmp = substr( $text, $j + 1, $k - $j - 1 );
  2633. $b1 = strpos( $tmp, ':' );
  2634. if ( $b1 !== false )
  2635. {
  2636. $func = substr( $tmp, 0, $b1 );
  2637. $parm = substr( $tmp, $b1 + 1 );
  2638. }
  2639. else
  2640. {
  2641. $func = $tmp;
  2642. $parm = '';
  2643. }
  2644. if ( ! isset( $func ) || ! strlen( trim( $func ) ) )
  2645. {
  2646. $directive = 0;
  2647. }
  2648. else
  2649. {
  2650. // only call the function if this is the final call,
  2651. // ie, the one actually doing printing, not
  2652. // measurement
  2653. if ( $final )
  2654. {
  2655. // need to assess the text position, calculate
  2656. // the text width to this point
  2657. // can use getTextWidth to find the text width I
  2658. // think
  2659. // also add the text height and decender
  2660. $tmp = $this->PRVTgetTextPosition( $x, $y, $angle, $size, $wordSpaceAdjust, substr( $text, 0, $i ) );
  2661. $info = array(
  2662. 'x' => $tmp[0],
  2663. 'y' => $tmp[1],
  2664. 'angle' => $angle,
  2665. 'status' => 'start',
  2666. 'p' => $parm,
  2667. 'f' => $func,
  2668. 'height' => $this->getFontHeight( $size ),
  2669. 'decender' => $this->getFontDecender( $size )
  2670. );
  2671. $x = $tmp[0];
  2672. $y = $tmp[1];
  2673. if ( ! isset( $noClose ) || ! $noClose )
  2674. {
  2675. // only add to the stack if this is a small
  2676. // 'c', therefore is a start-stop pair
  2677. $this->nCallback ++ ;
  2678. $info['nCallback'] = $this->nCallback;
  2679. $this->callback[$this->nCallback] = $info;
  2680. }
  2681. $ret = $this->$func( $info );
  2682. if ( is_array( $ret ) )
  2683. {
  2684. // then the return from the callback
  2685. // function could set the position, to start
  2686. // with, later will do font colour, and font
  2687. foreach ( $ret as $rk => $rv )
  2688. {
  2689. switch ( $rk )
  2690. {
  2691. case 'x' :
  2692. case 'y' :
  2693. $$rk = $rv;
  2694. break;
  2695. }
  2696. }
  2697. }
  2698. }
  2699. }
  2700. }
  2701. break;
  2702. }
  2703. }
  2704. return $directive;
  2705. }
  2706. /**
  2707. * add text to the document, at a specified location, size and angle on the
  2708. * page
  2709. */
  2710. function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0)
  2711. {
  2712. if ( ! $this->numFonts )
  2713. {
  2714. $this->selectFont( './fonts/Helvetica' );
  2715. }
  2716. // if there are any open callbacks, then they should be called, to show
  2717. // the start of the line
  2718. if ( $this->nCallback > 0 )
  2719. {
  2720. for ( $i = $this->nCallback; $i > 0; $i -- )
  2721. {
  2722. // call each function
  2723. $info = array(
  2724. 'x' => $x,
  2725. 'y' => $y,
  2726. 'angle' => $angle,
  2727. 'status' => 'sol',
  2728. 'p' => $this->callback[$i]['p'],
  2729. 'nCallback' => $this->callback[$i]['nCallback'],
  2730. 'height' => $this->callback[$i]['height'],
  2731. 'decender' => $this->callback[$i]['decender']
  2732. );
  2733. $func = $this->callback[$i]['f'];
  2734. $this->$func( $info );
  2735. }
  2736. }
  2737. if ( $angle == 0 )
  2738. {
  2739. $this->objects[$this->currentContents]['c'] .= "\n" . 'BT ' . sprintf( '%.3f', $x ) . ' ' . sprintf( '%.3f', $y ) . ' Td';
  2740. }
  2741. else
  2742. {
  2743. $a = deg2rad( ( float )$angle );
  2744. $tmp = "\n" . 'BT ';
  2745. $tmp .= sprintf( '%.3f', cos( $a ) ) . ' ' . sprintf( '%.3f', (- 1.0 * sin( $a )) ) . ' ' . sprintf( '%.3f', sin( $a ) ) . ' ' . sprintf( '%.3f', cos( $a ) ) . ' ';
  2746. $tmp .= sprintf( '%.3f', $x ) . ' ' . sprintf( '%.3f', $y ) . ' Tm';
  2747. $this->objects[$this->currentContents]['c'] .= $tmp;
  2748. }
  2749. if ( $wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust )
  2750. {
  2751. $this->wordSpaceAdjust = $wordSpaceAdjust;
  2752. $this->objects[$this->currentContents]['c'] .= ' ' . sprintf( '%.3f', $wordSpaceAdjust ) . ' Tw';
  2753. }
  2754. $len = strlen( $text );
  2755. $start = 0;
  2756. for ( $i = 0; $i < $len; $i ++ )
  2757. {
  2758. $f = 1;
  2759. $directive = $this->PRVTcheckTextDirective( $text, $i, $f );
  2760. if ( $directive )
  2761. {
  2762. // then we should write what we need to
  2763. if ( $i > $start )
  2764. {
  2765. $part = substr( $text, $start, $i - $start );
  2766. $this->objects[$this->currentContents]['c'] .= ' /F' . $this->currentFontNum . ' ' . sprintf( '%.1f', $size ) . ' Tf ';
  2767. $this->objects[$this->currentContents]['c'] .= ' (' . $this->filterText( $part ) . ') Tj';
  2768. }
  2769. if ( $f )
  2770. {
  2771. // then there was nothing drastic done here, restore the
  2772. // contents
  2773. $this->setCurrentFont( );
  2774. }
  2775. else
  2776. {
  2777. $this->objects[$this->currentContents]['c'] .= ' ET';
  2778. $f = 1;
  2779. $xp = $x;
  2780. $yp = $y;
  2781. $directive = $this->PRVTcheckTextDirective1( $text, $i, $f, 1, $xp, $yp, $size, $angle, $wordSpaceAdjust );
  2782. // restart the text object
  2783. if ( $angle == 0 )
  2784. {
  2785. $this->objects[$this->currentContents]['c'] .= "\n" . 'BT ' . sprintf( '%.3f', $xp ) . ' ' . sprintf( '%.3f', $yp ) . ' Td';
  2786. }
  2787. else
  2788. {
  2789. $a = deg2rad( ( float )$angle );
  2790. $tmp = "\n" . 'BT ';
  2791. $tmp .= sprintf( '%.3f', cos( $a ) ) . ' ' . sprintf( '%.3f', (- 1.0 * sin( $a )) ) . ' ' . sprintf( '%.3f', sin( $a ) ) . ' ' . sprintf( '%.3f', cos( $a ) ) . ' ';
  2792. $tmp .= sprintf( '%.3f', $xp ) . ' ' . sprintf( '%.3f', $yp ) . ' Tm';
  2793. $this->objects[$this->currentContents]['c'] .= $tmp;
  2794. }
  2795. if ( $wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust )
  2796. {
  2797. $this->wordSpaceAdjust = $wordSpaceAdjust;
  2798. $this->objects[$this->currentContents]['c'] .= ' ' . sprintf( '%.3f', $wordSpaceAdjust ) . ' Tw';
  2799. }
  2800. }
  2801. // and move the writing point to the next piece of text
  2802. $i = $i + $directive - 1;
  2803. $start = $i + 1;
  2804. }
  2805. }
  2806. if ( $start < $len )
  2807. {
  2808. $part = substr( $text, $start );
  2809. $this->objects[$this->currentContents]['c'] .= ' /F' . $this->currentFontNum . ' ' . sprintf( '%.1f', $size ) . ' Tf ';
  2810. $this->objects[$this->currentContents]['c'] .= ' (' . $this->filterText( $part ) . ') Tj';
  2811. }
  2812. $this->objects[$this->currentContents]['c'] .= ' ET';
  2813. // if there are any open callbacks, then they should be called, to show
  2814. // the end of the line
  2815. if ( $this->nCallback > 0 )
  2816. {
  2817. for ( $i = $this->nCallback; $i > 0; $i -- )
  2818. {
  2819. // call each function
  2820. $tmp = $this->PRVTgetTextPosition( $x, $y, $angle, $size, $wordSpaceAdjust, $text );
  2821. $info = array(
  2822. 'x' => $tmp[0],
  2823. 'y' => $tmp[1],
  2824. 'angle' => $angle,
  2825. 'status' => 'eol',
  2826. 'p' => $this->callback[$i]['p'],
  2827. 'nCallback' => $this->callback[$i]['nCallback'],
  2828. 'height' => $this->callback[$i]['height'],
  2829. 'decender' => $this->callback[$i]['decender']
  2830. );
  2831. $func = $this->callback[$i]['f'];
  2832. $this->$func( $info );
  2833. }
  2834. }
  2835. }
  2836. /**
  2837. * calculate how wide a given text string will be on a page, at a given
  2838. * size.
  2839. * this can be called externally, but is alse used by the other class
  2840. * functions
  2841. */
  2842. function getTextWidth($size, $text)
  2843. {
  2844. // this function should not change any of the settings, though it will
  2845. // need to
  2846. // track any directives which change during calculation, so copy them at
  2847. // the start
  2848. // and put them back at the end.
  2849. $store_currentTextState = $this->currentTextState;
  2850. if ( ! $this->numFonts )
  2851. {
  2852. $this->selectFont( './fonts/Helvetica' );
  2853. }
  2854. // converts a number or a float to a string so it can get the width
  2855. $text = "$text";
  2856. // hmm, this is where it all starts to get tricky - use the font
  2857. // information to
  2858. // calculate the width of each character, add them up and convert to
  2859. // user units
  2860. $w = 0;
  2861. $len = strlen( $text );
  2862. $cf = $this->currentFont;
  2863. for ( $i = 0; $i < $len; $i ++ )
  2864. {
  2865. $f = 1;
  2866. $directive = $this->PRVTcheckTextDirective( $text, $i, $f );
  2867. if ( $directive )
  2868. {
  2869. if ( $f )
  2870. {
  2871. $this->setCurrentFont( );
  2872. $cf = $this->currentFont;
  2873. }
  2874. $i = $i + $directive - 1;
  2875. }
  2876. else
  2877. {
  2878. $char = ord( $text[$i] );
  2879. if ( isset( $this->fonts[$cf]['differences'][$char] ) )
  2880. {
  2881. // then this character is being replaced by another
  2882. $name = $this->fonts[$cf]['differences'][$char];
  2883. if ( isset( $this->fonts[$cf]['C'][$name]['WX'] ) )
  2884. {
  2885. $w += $this->fonts[$cf]['C'][$name]['WX'];
  2886. }
  2887. }
  2888. else if ( isset( $this->fonts[$cf]['C'][$char]['WX'] ) )
  2889. {
  2890. $w += $this->fonts[$cf]['C'][$char]['WX'];
  2891. }
  2892. }
  2893. }
  2894. $this->currentTextState = $store_currentTextState;
  2895. $this->setCurrentFont( );
  2896. return $w * $size / 1000;
  2897. }
  2898. /**
  2899. * do a part of the calculation for sorting out the justification of the
  2900. * text
  2901. *
  2902. * @access private
  2903. *
  2904. */
  2905. function PRVTadjustWrapText($text, $actual, $width, &$x, &$adjust, $justification)
  2906. {
  2907. switch ( $justification )
  2908. {
  2909. case 'left' :
  2910. return;
  2911. break;
  2912. case 'right' :
  2913. $x += $width - $actual;
  2914. break;
  2915. case 'center' :
  2916. case 'centre' :
  2917. $x += ($width - $actual) / 2;
  2918. break;
  2919. case 'full' :
  2920. // count the number of words
  2921. $words = explode( ' ', $text );
  2922. $nspaces = count( $words ) - 1;
  2923. if ( $nspaces > 0 )
  2924. {
  2925. $adjust = ($width - $actual) / $nspaces;
  2926. }
  2927. else
  2928. {
  2929. $adjust = 0;
  2930. }
  2931. break;
  2932. }
  2933. }
  2934. /**
  2935. * add text to the page, but ensure that it fits within a certain width
  2936. * if it does not fit then put in as much as possible, splitting at word
  2937. * boundaries
  2938. * and return the remainder.
  2939. * justification and angle can also be specified for the text
  2940. */
  2941. function addTextWrap($x, $y, $width, $size, $text, $justification = 'left', $angle = 0, $test = 0)
  2942. {
  2943. // this will display the text, and if it goes beyond the width $width,
  2944. // will backtrack to the
  2945. // previous space or hyphen, and return the remainder of the text.
  2946. // $justification can be set to 'left','right','center','centre','full'
  2947. // need to store the initial text state, as this will change during the
  2948. // width calculation
  2949. // but will need to be re-set before printing, so that the chars work
  2950. // out right
  2951. $store_currentTextState = $this->currentTextState;
  2952. if ( ! $this->numFonts )
  2953. {
  2954. $this->selectFont( './fonts/Helvetica' );
  2955. }
  2956. if ( $width <= 0 )
  2957. {
  2958. // error, pretend it printed ok, otherwise risking a loop
  2959. return '';
  2960. }
  2961. $w = 0;
  2962. $break = 0;
  2963. $breakWidth = 0;
  2964. $len = strlen( $text );
  2965. $cf = $this->currentFont;
  2966. $tw = $width / $size * 1000;
  2967. for ( $i = 0; $i < $len; $i ++ )
  2968. {
  2969. $f = 1;
  2970. $directive = $this->PRVTcheckTextDirective( $text, $i, $f );
  2971. if ( $directive )
  2972. {
  2973. if ( $f )
  2974. {
  2975. $this->setCurrentFont( );
  2976. $cf = $this->currentFont;
  2977. }
  2978. $i = $i + $directive - 1;
  2979. }
  2980. else
  2981. {
  2982. $cOrd = ord( $text[$i] );
  2983. if ( isset( $this->fonts[$cf]['differences'][$cOrd] ) )
  2984. {
  2985. // then this character is being replaced by another
  2986. $cOrd2 = $this->fonts[$cf]['differences'][$cOrd];
  2987. }
  2988. else
  2989. {
  2990. $cOrd2 = $cOrd;
  2991. }
  2992. if ( isset( $this->fonts[$cf]['C'][$cOrd2]['WX'] ) )
  2993. {
  2994. $w += $this->fonts[$cf]['C'][$cOrd2]['WX'];
  2995. }
  2996. if ( $w > $tw )
  2997. {
  2998. // then we need to truncate this line
  2999. if ( $break > 0 )
  3000. {
  3001. // then we have somewhere that we can split :)
  3002. if ( $text[$break] == ' ' )
  3003. {
  3004. $tmp = substr( $text, 0, $break );
  3005. }
  3006. else
  3007. {
  3008. $tmp = substr( $text, 0, $break + 1 );
  3009. }
  3010. $adjust = 0;
  3011. $this->PRVTadjustWrapText( $tmp, $breakWidth, $width, $x, $adjust, $justification );
  3012. // reset the text state
  3013. $this->currentTextState = $store_currentTextState;
  3014. $this->setCurrentFont( );
  3015. if ( ! $test )
  3016. {
  3017. $this->addText( $x, $y, $size, $tmp, $angle, $adjust );
  3018. }
  3019. return substr( $text, $break + 1 );
  3020. }
  3021. else
  3022. {
  3023. // just split before the current character
  3024. $tmp = substr( $text, 0, $i );
  3025. $adjust = 0;
  3026. $ctmp = ord( $text[$i] );
  3027. if ( isset( $this->fonts[$cf]['differences'][$ctmp] ) )
  3028. {
  3029. $ctmp = $this->fonts[$cf]['differences'][$ctmp];
  3030. }
  3031. $tmpw = ($w - $this->fonts[$cf]['C'][$ctmp]['WX']) * $size / 1000;
  3032. $this->PRVTadjustWrapText( $tmp, $tmpw, $width, $x, $adjust, $justification );
  3033. // reset the text state
  3034. $this->currentTextState = $store_currentTextState;
  3035. $this->setCurrentFont( );
  3036. if ( ! $test )
  3037. {
  3038. $this->addText( $x, $y, $size, $tmp, $angle, $adjust );
  3039. }
  3040. return substr( $text, $i );
  3041. }
  3042. }
  3043. if ( $text[$i] == '-' )
  3044. {
  3045. $break = $i;
  3046. $breakWidth = $w * $size / 1000;
  3047. }
  3048. if ( $text[$i] == ' ' )
  3049. {
  3050. $break = $i;
  3051. $ctmp = ord( $text[$i] );
  3052. if ( isset( $this->fonts[$cf]['differences'][$ctmp] ) )
  3053. {
  3054. $ctmp = $this->fonts[$cf]['differences'][$ctmp];
  3055. }
  3056. $breakWidth = ($w - $this->fonts[$cf]['C'][$ctmp]['WX']) * $size / 1000;
  3057. }
  3058. }
  3059. }
  3060. // then there was no need to break this line
  3061. if ( $justification == 'full' )
  3062. {
  3063. $justification = 'left';
  3064. }
  3065. $adjust = 0;
  3066. $tmpw = $w * $size / 1000;
  3067. $this->PRVTadjustWrapText( $text, $tmpw, $width, $x, $adjust, $justification );
  3068. // reset the text state
  3069. $this->currentTextState = $store_currentTextState;
  3070. $this->setCurrentFont( );
  3071. if ( ! $test )
  3072. {
  3073. $this->addText( $x, $y, $size, $text, $angle, $adjust, $angle );
  3074. }
  3075. return '';
  3076. }
  3077. /**
  3078. * this will be called at a new page to return the state to what it was on
  3079. * the
  3080. * end of the previous page, before the stack was closed down
  3081. * This is to get around not being able to have open 'q' across pages
  3082. */
  3083. function saveState($pageEnd = 0)
  3084. {
  3085. if ( $pageEnd )
  3086. {
  3087. // this will be called at a new page to return the state to what it
  3088. // was on the
  3089. // end of the previous page, before the stack was closed down
  3090. // This is to get around not being able to have open 'q' across
  3091. // pages
  3092. $opt = $this->stateStack[$pageEnd]; // ok to use this as stack starts
  3093. // numbering at 1
  3094. $this->setColor( $opt['col']['r'], $opt['col']['g'], $opt['col']['b'], 1 );
  3095. $this->setStrokeColor( $opt['str']['r'], $opt['str']['g'], $opt['str']['b'], 1 );
  3096. $this->objects[$this->currentContents]['c'] .= "\n" . $opt['lin'];
  3097. // $this->currentLineStyle = $opt['lin'];
  3098. }
  3099. else
  3100. {
  3101. $this->nStateStack ++ ;
  3102. $this->stateStack[$this->nStateStack] = array(
  3103. 'col' => $this->currentColour,
  3104. 'str' => $this->currentStrokeColour,
  3105. 'lin' => $this->currentLineStyle
  3106. );
  3107. }
  3108. $this->objects[$this->currentContents]['c'] .= "\nq";
  3109. }
  3110. /**
  3111. * restore a previously saved state
  3112. */
  3113. function restoreState($pageEnd = 0)
  3114. {
  3115. if ( ! $pageEnd )
  3116. {
  3117. $n = $this->nStateStack;
  3118. $this->currentColour = $this->stateStack[$n]['col'];
  3119. $this->currentStrokeColour = $this->stateStack[$n]['str'];
  3120. $this->objects[$this->currentContents]['c'] .= "\n" . $this->stateStack[$n]['lin'];
  3121. $this->currentLineStyle = $this->stateStack[$n]['lin'];
  3122. unset( $this->stateStack[$n] );
  3123. $this->nStateStack -- ;
  3124. }
  3125. $this->objects[$this->currentContents]['c'] .= "\nQ";
  3126. }
  3127. /**
  3128. * make a loose object, the output will go into this object, until it is
  3129. * closed, then will revert to
  3130. * the current one.
  3131. * this object will not appear until it is included within a page.
  3132. * the function will return the object number
  3133. */
  3134. function openObject()
  3135. {
  3136. $this->nStack ++ ;
  3137. $this->stack[$this->nStack] = array(
  3138. 'c' => $this->currentContents,
  3139. 'p' => $this->currentPage
  3140. );
  3141. // add a new object of the content type, to hold the data flow
  3142. $this->numObj ++ ;
  3143. $this->o_contents( $this->numObj, 'new' );
  3144. $this->currentContents = $this->numObj;
  3145. $this->looseObjects[$this->numObj] = 1;
  3146. return $this->numObj;
  3147. }
  3148. /**
  3149. * open an existing object for editing
  3150. */
  3151. function reopenObject($id)
  3152. {
  3153. $this->nStack ++ ;
  3154. $this->stack[$this->nStack] = array(
  3155. 'c' => $this->currentContents,
  3156. 'p' => $this->currentPage
  3157. );
  3158. $this->currentContents = $id;
  3159. // also if this object is the primary contents for a page, then set the
  3160. // current page to its parent
  3161. if ( isset( $this->objects[$id]['onPage'] ) )
  3162. {
  3163. $this->currentPage = $this->objects[$id]['onPage'];
  3164. }
  3165. }
  3166. /**
  3167. * close an object
  3168. */
  3169. function closeObject()
  3170. {
  3171. // close the object, as long as there was one open in the first place,
  3172. // which will be indicated by
  3173. // an objectId on the stack.
  3174. if ( $this->nStack > 0 )
  3175. {
  3176. $this->currentContents = $this->stack[$this->nStack]['c'];
  3177. $this->currentPage = $this->stack[$this->nStack]['p'];
  3178. $this->nStack -- ;
  3179. // easier to probably not worry about removing the old entries, they
  3180. // will be overwritten
  3181. // if there are new ones.
  3182. }
  3183. }
  3184. /**
  3185. * stop an object from appearing on pages from this point on
  3186. */
  3187. function stopObject($id)
  3188. {
  3189. // if an object has been appearing on pages up to now, then stop it,
  3190. // this page will
  3191. // be the last one that could contian it.
  3192. if ( isset( $this->addLooseObjects[$id] ) )
  3193. {
  3194. $this->addLooseObjects[$id] = '';
  3195. }
  3196. }
  3197. /**
  3198. * after an object has been created, it wil only show if it has been added,
  3199. * using this function.
  3200. */
  3201. function addObject($id, $options = 'add')
  3202. {
  3203. // add the specified object to the page
  3204. if ( isset( $this->looseObjects[$id] ) && $this->currentContents != $id )
  3205. {
  3206. // then it is a valid object, and it is not being added to itself
  3207. switch ( $options )
  3208. {
  3209. case 'all' :
  3210. // then this object is to be added to this page (done in the
  3211. // next block) and
  3212. // all future new pages.
  3213. $this->addLooseObjects[$id] = 'all';
  3214. case 'add' :
  3215. if ( isset( $this->objects[$this->currentContents]['onPage'] ) )
  3216. {
  3217. // then the destination contents is the primary for the
  3218. // page
  3219. // (though this object is actually added to that page)
  3220. $this->o_page( $this->objects[$this->currentContents]['onPage'], 'content', $id );
  3221. }
  3222. break;
  3223. case 'even' :
  3224. $this->addLooseObjects[$id] = 'even';
  3225. $pageObjectId = $this->objects[$this->currentContents]['onPage'];
  3226. if ( $this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0 )
  3227. {
  3228. $this->addObject( $id ); // hacky huh :)
  3229. }
  3230. break;
  3231. case 'odd' :
  3232. $this->addLooseObjects[$id] = 'odd';
  3233. $pageObjectId = $this->objects[$this->currentContents]['onPage'];
  3234. if ( $this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1 )
  3235. {
  3236. $this->addObject( $id ); // hacky huh :)
  3237. }
  3238. break;
  3239. case 'next' :
  3240. $this->addLooseObjects[$id] = 'all';
  3241. break;
  3242. case 'nexteven' :
  3243. $this->addLooseObjects[$id] = 'even';
  3244. break;
  3245. case 'nextodd' :
  3246. $this->addLooseObjects[$id] = 'odd';
  3247. break;
  3248. }
  3249. }
  3250. }
  3251. /**
  3252. * add content to the documents info object
  3253. */
  3254. function addInfo($label, $value = 0)
  3255. {
  3256. // this will only work if the label is one of the valid ones.
  3257. // modify this so that arrays can be passed as well.
  3258. // if $label is an array then assume that it is key=>value pairs
  3259. // else assume that they are both scalar, anything else will probably
  3260. // error
  3261. if ( is_array( $label ) )
  3262. {
  3263. foreach ( $label as $l => $v )
  3264. {
  3265. $this->o_info( $this->infoObject, $l, $v );
  3266. }
  3267. }
  3268. else
  3269. {
  3270. $this->o_info( $this->infoObject, $label, $value );
  3271. }
  3272. }
  3273. /**
  3274. * set the viewer preferences of the document, it is up to the browser to
  3275. * obey these.
  3276. */
  3277. function setPreferences($label, $value = 0)
  3278. {
  3279. // this will only work if the label is one of the valid ones.
  3280. if ( is_array( $label ) )
  3281. {
  3282. foreach ( $label as $l => $v )
  3283. {
  3284. $this->o_catalog( $this->catalogId, 'viewerPreferences', array(
  3285. $l => $v
  3286. ) );
  3287. }
  3288. }
  3289. else
  3290. {
  3291. $this->o_catalog( $this->catalogId, 'viewerPreferences', array(
  3292. $label => $value
  3293. ) );
  3294. }
  3295. }
  3296. /**
  3297. * extract an integer from a position in a byte stream
  3298. *
  3299. * @access private
  3300. *
  3301. */
  3302. function PRVT_getBytes(&$data, $pos, $num)
  3303. {
  3304. // return the integer represented by $num bytes from $pos within $data
  3305. $ret = 0;
  3306. for ( $i = 0; $i < $num; $i ++ )
  3307. {
  3308. $ret = $ret * 256;
  3309. $ret += ord( $data[$pos + $i] );
  3310. }
  3311. return $ret;
  3312. }
  3313. /**
  3314. * add a PNG image into the document, from a file
  3315. * this should work with remote files
  3316. */
  3317. function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
  3318. {
  3319. // read in a png file, interpret it, then add to the system
  3320. $error = 0;
  3321. $tmp = get_magic_quotes_runtime( );
  3322. set_magic_quotes_runtime( 0 );
  3323. $fp = @fopen( $file, 'rb' );
  3324. if ( $fp )
  3325. {
  3326. $data = '';
  3327. while ( ! feof( $fp ) )
  3328. {
  3329. $data .= fread( $fp, 1024 );
  3330. }
  3331. fclose( $fp );
  3332. }
  3333. else
  3334. {
  3335. $error = 1;
  3336. $errormsg = 'trouble opening file: ' . $file;
  3337. }
  3338. set_magic_quotes_runtime( $tmp );
  3339. if ( ! $error )
  3340. {
  3341. $header = chr( 137 ) . chr( 80 ) . chr( 78 ) . chr( 71 ) . chr( 13 ) . chr( 10 ) . chr( 26 ) . chr( 10 );
  3342. if ( substr( $data, 0, 8 ) != $header )
  3343. {
  3344. $error = 1;
  3345. $errormsg = 'this file does not have a valid header';
  3346. }
  3347. }
  3348. if ( ! $error )
  3349. {
  3350. // set pointer
  3351. $p = 8;
  3352. $len = strlen( $data );
  3353. // cycle through the file, identifying chunks
  3354. $haveHeader = 0;
  3355. $info = array();
  3356. $idata = '';
  3357. $pdata = '';
  3358. while ( $p < $len )
  3359. {
  3360. $chunkLen = $this->PRVT_getBytes( $data, $p, 4 );
  3361. $chunkType = substr( $data, $p + 4, 4 );
  3362. // echo $chunkType.' - '.$chunkLen.'<br>';
  3363. switch ( $chunkType )
  3364. {
  3365. case 'IHDR' :
  3366. // this is where all the file information comes from
  3367. $info['width'] = $this->PRVT_getBytes( $data, $p + 8, 4 );
  3368. $info['height'] = $this->PRVT_getBytes( $data, $p + 12, 4 );
  3369. $info['bitDepth'] = ord( $data[$p + 16] );
  3370. $info['colorType'] = ord( $data[$p + 17] );
  3371. $info['compressionMethod'] = ord( $data[$p + 18] );
  3372. $info['filterMethod'] = ord( $data[$p + 19] );
  3373. $info['interlaceMethod'] = ord( $data[$p + 20] );
  3374. // print_r($info);
  3375. $haveHeader = 1;
  3376. if ( $info['compressionMethod'] != 0 )
  3377. {
  3378. $error = 1;
  3379. $errormsg = 'unsupported compression method';
  3380. }
  3381. if ( $info['filterMethod'] != 0 )
  3382. {
  3383. $error = 1;
  3384. $errormsg = 'unsupported filter method';
  3385. }
  3386. break;
  3387. case 'PLTE' :
  3388. $pdata .= substr( $data, $p + 8, $chunkLen );
  3389. break;
  3390. case 'IDAT' :
  3391. $idata .= substr( $data, $p + 8, $chunkLen );
  3392. break;
  3393. case 'tRNS' :
  3394. // this chunk can only occur once and it must occur
  3395. // after the PLTE chunk and before IDAT chunk
  3396. // print "tRNS found, color type =
  3397. // ".$info['colorType']."<BR>";
  3398. $transparency = array();
  3399. if ( $info['colorType'] == 3 )
  3400. { // indexed color, rbg
  3401. /*
  3402. * corresponding to entries in the plte chunk Alpha
  3403. * for palette index 0: 1 byte Alpha for palette
  3404. * index 1: 1 byte ...etc...
  3405. */
  3406. // there will be one entry for each palette entry.
  3407. // up until the last non-opaque entry.
  3408. // set up an array, stretching over all palette
  3409. // entries which will be o (opaque) or 1
  3410. // (transparent)
  3411. $transparency['type'] = 'indexed';
  3412. $numPalette = strlen( $pdata ) / 3;
  3413. $trans = 0;
  3414. for ( $i = $chunkLen; $i >= 0; $i -- )
  3415. {
  3416. if ( ord( $data[$p + 8 + $i] ) == 0 )
  3417. {
  3418. $trans = $i;
  3419. }
  3420. }
  3421. $transparency['data'] = $trans;
  3422. }
  3423. elseif ( $info['colorType'] == 0 )
  3424. { // grayscale
  3425. /*
  3426. * corresponding to entries in the plte chunk Gray:
  3427. * 2 bytes, range 0 .. (2^bitdepth)-1
  3428. */
  3429. // $transparency['grayscale']=$this->PRVT_getBytes($data,$p+8,2);
  3430. // // g = grayscale
  3431. $transparency['type'] = 'indexed';
  3432. $transparency['data'] = ord( $data[$p + 8 + 1] );
  3433. }
  3434. elseif ( $info['colorType'] == 2 )
  3435. { // truecolor
  3436. /*
  3437. * corresponding to entries in the plte chunk Red: 2
  3438. * bytes, range 0 .. (2^bitdepth)-1 Green: 2 bytes,
  3439. * range 0 .. (2^bitdepth)-1 Blue: 2 bytes, range 0
  3440. * .. (2^bitdepth)-1
  3441. */
  3442. $transparency['r'] = $this->PRVT_getBytes( $data, $p + 8, 2 ); // r
  3443. // from truecolor
  3444. $transparency['g'] = $this->PRVT_getBytes( $data, $p + 10, 2 ); // g
  3445. // from truecolor
  3446. $transparency['b'] = $this->PRVT_getBytes( $data, $p + 12, 2 ); // b
  3447. // from truecolor
  3448. }
  3449. else
  3450. {
  3451. // unsupported transparency type
  3452. }
  3453. // KS End new code
  3454. break;
  3455. default :
  3456. break;
  3457. }
  3458. $p += $chunkLen + 12;
  3459. }
  3460. if ( ! $haveHeader )
  3461. {
  3462. $error = 1;
  3463. $errormsg = 'information header is missing';
  3464. }
  3465. if ( isset( $info['interlaceMethod'] ) && $info['interlaceMethod'] )
  3466. {
  3467. $error = 1;
  3468. $errormsg = 'There appears to be no support for interlaced images in pdf.';
  3469. }
  3470. }
  3471. if ( ! $error && $info['bitDepth'] > 8 )
  3472. {
  3473. $error = 1;
  3474. $errormsg = 'only bit depth of 8 or less is supported';
  3475. }
  3476. if ( ! $error )
  3477. {
  3478. if ( $info['colorType'] != 2 && $info['colorType'] != 0 && $info['colorType'] != 3 )
  3479. {
  3480. $error = 1;
  3481. $errormsg = 'transparancey alpha channel not supported, transparency only supported for palette images.';
  3482. }
  3483. else
  3484. {
  3485. switch ( $info['colorType'] )
  3486. {
  3487. case 3 :
  3488. $color = 'DeviceRGB';
  3489. $ncolor = 1;
  3490. break;
  3491. case 2 :
  3492. $color = 'DeviceRGB';
  3493. $ncolor = 3;
  3494. break;
  3495. case 0 :
  3496. $color = 'DeviceGray';
  3497. $ncolor = 1;
  3498. break;
  3499. }
  3500. }
  3501. }
  3502. if ( $error )
  3503. {
  3504. $this->addMessage( 'PNG error - (' . $file . ') ' . $errormsg );
  3505. return;
  3506. }
  3507. if ( $w == 0 )
  3508. {
  3509. $w = $h / $info['height'] * $info['width'];
  3510. }
  3511. if ( $h == 0 )
  3512. {
  3513. $h = $w * $info['height'] / $info['width'];
  3514. }
  3515. // print_r($info);
  3516. // so this image is ok... add it in.
  3517. $this->numImages ++ ;
  3518. $im = $this->numImages;
  3519. $label = 'I' . $im;
  3520. $this->numObj ++ ;
  3521. // $this->o_image($this->numObj,'new',array('label'=>$label,'data'=>$idata,'iw'=>$w,'ih'=>$h,'type'=>'png','ic'=>$info['width']));
  3522. $options = array(
  3523. 'label' => $label,
  3524. 'data' => $idata,
  3525. 'bitsPerComponent' => $info['bitDepth'],
  3526. 'pdata' => $pdata,
  3527. 'iw' => $info['width'],
  3528. 'ih' => $info['height'],
  3529. 'type' => 'png',
  3530. 'color' => $color,
  3531. 'ncolor' => $ncolor
  3532. );
  3533. if ( isset( $transparency ) )
  3534. {
  3535. $options['transparency'] = $transparency;
  3536. }
  3537. $this->o_image( $this->numObj, 'new', $options );
  3538. $this->objects[$this->currentContents]['c'] .= "\nq";
  3539. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $w ) . " 0 0 " . sprintf( '%.3f', $h ) . " " . sprintf( '%.3f', $x ) . " " . sprintf( '%.3f', $y ) . " cm";
  3540. $this->objects[$this->currentContents]['c'] .= "\n/" . $label . ' Do';
  3541. $this->objects[$this->currentContents]['c'] .= "\nQ";
  3542. }
  3543. /**
  3544. * add a JPEG image into the document, from a file
  3545. */
  3546. function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
  3547. {
  3548. // attempt to add a jpeg image straight from a file, using no GD
  3549. // commands
  3550. // note that this function is unable to operate on a remote file.
  3551. if ( ! file_exists( $img ) )
  3552. {
  3553. return;
  3554. }
  3555. $tmp = getimagesize( $img );
  3556. $imageWidth = $tmp[0];
  3557. $imageHeight = $tmp[1];
  3558. if ( isset( $tmp['channels'] ) )
  3559. {
  3560. $channels = $tmp['channels'];
  3561. }
  3562. else
  3563. {
  3564. $channels = 3;
  3565. }
  3566. if ( $w <= 0 && $h <= 0 )
  3567. {
  3568. $w = $imageWidth;
  3569. }
  3570. if ( $w == 0 )
  3571. {
  3572. $w = $h / $imageHeight * $imageWidth;
  3573. }
  3574. if ( $h == 0 )
  3575. {
  3576. $h = $w * $imageHeight / $imageWidth;
  3577. }
  3578. $fp = fopen( $img, 'rb' );
  3579. $tmp = get_magic_quotes_runtime( );
  3580. set_magic_quotes_runtime( 0 );
  3581. $data = fread( $fp, filesize( $img ) );
  3582. set_magic_quotes_runtime( $tmp );
  3583. fclose( $fp );
  3584. $this->addJpegImage_common( $data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels );
  3585. }
  3586. /**
  3587. * add an image into the document, from a GD object
  3588. * this function is not all that reliable, and I would probably encourage
  3589. * people to use
  3590. * the file based functions
  3591. */
  3592. function addImage(&$img, $x, $y, $w = 0, $h = 0, $quality = 75)
  3593. {
  3594. // add a new image into the current location, as an external object
  3595. // add the image at $x,$y, and with width and height as defined by $w &
  3596. // $h
  3597. // note that this will only work with full colour images and makes them
  3598. // jpg images for display
  3599. // later versions could present lossless image formats if there is
  3600. // interest.
  3601. // there seems to be some problem here in that images that have quality
  3602. // set above 75 do not appear
  3603. // not too sure why this is, but in the meantime I have restricted this
  3604. // to 75.
  3605. if ( $quality > 75 )
  3606. {
  3607. $quality = 75;
  3608. }
  3609. // if the width or height are set to zero, then set the other one based
  3610. // on keeping the image
  3611. // height/width ratio the same, if they are both zero, then give up :)
  3612. $imageWidth = imagesx( $img );
  3613. $imageHeight = imagesy( $img );
  3614. if ( $w <= 0 && $h <= 0 )
  3615. {
  3616. return;
  3617. }
  3618. if ( $w == 0 )
  3619. {
  3620. $w = $h / $imageHeight * $imageWidth;
  3621. }
  3622. if ( $h == 0 )
  3623. {
  3624. $h = $w * $imageHeight / $imageWidth;
  3625. }
  3626. // gotta get the data out of the img..
  3627. // so I write to a temp file, and then read it back.. soo ugly, my
  3628. // apologies.
  3629. $tmpDir = '/tmp';
  3630. $tmpName = tempnam( $tmpDir, 'img' );
  3631. imagejpeg( $img, $tmpName, $quality );
  3632. $fp = fopen( $tmpName, 'rb' );
  3633. $tmp = get_magic_quotes_runtime( );
  3634. set_magic_quotes_runtime( 0 );
  3635. $fp = @fopen( $tmpName, 'rb' );
  3636. if ( $fp )
  3637. {
  3638. $data = '';
  3639. while ( ! feof( $fp ) )
  3640. {
  3641. $data .= fread( $fp, 1024 );
  3642. }
  3643. fclose( $fp );
  3644. }
  3645. else
  3646. {
  3647. $error = 1;
  3648. $errormsg = 'trouble opening file';
  3649. }
  3650. // $data = fread($fp,filesize($tmpName));
  3651. set_magic_quotes_runtime( $tmp );
  3652. // fclose($fp);
  3653. unlink( $tmpName );
  3654. $this->addJpegImage_common( $data, $x, $y, $w, $h, $imageWidth, $imageHeight );
  3655. }
  3656. /**
  3657. * common code used by the two JPEG adding functions
  3658. *
  3659. * @access private
  3660. *
  3661. */
  3662. function addJpegImage_common(&$data, $x, $y, $w = 0, $h = 0, $imageWidth, $imageHeight, $channels = 3)
  3663. {
  3664. // note that this function is not to be called externally
  3665. // it is just the common code between the GD and the file options
  3666. $this->numImages ++ ;
  3667. $im = $this->numImages;
  3668. $label = 'I' . $im;
  3669. $this->numObj ++ ;
  3670. $this->o_image( $this->numObj, 'new', array(
  3671. 'label' => $label,
  3672. 'data' => $data,
  3673. 'iw' => $imageWidth,
  3674. 'ih' => $imageHeight,
  3675. 'channels' => $channels
  3676. ) );
  3677. $this->objects[$this->currentContents]['c'] .= "\nq";
  3678. $this->objects[$this->currentContents]['c'] .= "\n" . sprintf( '%.3f', $w ) . " 0 0 " . sprintf( '%.3f', $h ) . " " . sprintf( '%.3f', $x ) . " " . sprintf( '%.3f', $y ) . " cm";
  3679. $this->objects[$this->currentContents]['c'] .= "\n/" . $label . ' Do';
  3680. $this->objects[$this->currentContents]['c'] .= "\nQ";
  3681. }
  3682. /**
  3683. * specify where the document should open when it first starts
  3684. */
  3685. function openHere($style, $a = 0, $b = 0, $c = 0)
  3686. {
  3687. // this function will open the document at a specified page, in a
  3688. // specified style
  3689. // the values for style, and the required paramters are:
  3690. // 'XYZ' left, top, zoom
  3691. // 'Fit'
  3692. // 'FitH' top
  3693. // 'FitV' left
  3694. // 'FitR' left,bottom,right
  3695. // 'FitB'
  3696. // 'FitBH' top
  3697. // 'FitBV' left
  3698. $this->numObj ++ ;
  3699. $this->o_destination( $this->numObj, 'new', array(
  3700. 'page' => $this->currentPage,
  3701. 'type' => $style,
  3702. 'p1' => $a,
  3703. 'p2' => $b,
  3704. 'p3' => $c
  3705. ) );
  3706. $id = $this->catalogId;
  3707. $this->o_catalog( $id, 'openHere', $this->numObj );
  3708. }
  3709. /**
  3710. * create a labelled destination within the document
  3711. */
  3712. function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
  3713. {
  3714. // associates the given label with the destination, it is done this way
  3715. // so that a destination can be specified after
  3716. // it has been linked to
  3717. // styles are the same as the 'openHere' function
  3718. $this->numObj ++ ;
  3719. $this->o_destination( $this->numObj, 'new', array(
  3720. 'page' => $this->currentPage,
  3721. 'type' => $style,
  3722. 'p1' => $a,
  3723. 'p2' => $b,
  3724. 'p3' => $c
  3725. ) );
  3726. $id = $this->numObj;
  3727. // store the label->idf relationship, note that this means that labels
  3728. // can be used only once
  3729. $this->destinations["$label"] = $id;
  3730. }
  3731. /**
  3732. * define font families, this is used to initialize the font families for
  3733. * the default fonts
  3734. * and for the user to add new ones for their fonts.
  3735. * The default bahavious can be overridden should
  3736. * that be desired.
  3737. */
  3738. function setFontFamily($family, $options = '')
  3739. {
  3740. if ( ! is_array( $options ) )
  3741. {
  3742. if ( $family == 'init' )
  3743. {
  3744. // set the known family groups
  3745. // these font families will be used to enable bold and italic
  3746. // markers to be included
  3747. // within text streams. html forms will be used... <b></b>
  3748. // <i></i>
  3749. $this->fontFamilies['Helvetica.afm'] = array(
  3750. 'b' => 'Helvetica-Bold.afm',
  3751. 'i' => 'Helvetica-Oblique.afm',
  3752. 'bi' => 'Helvetica-BoldOblique.afm',
  3753. 'ib' => 'Helvetica-BoldOblique.afm'
  3754. );
  3755. $this->fontFamilies['Courier.afm'] = array(
  3756. 'b' => 'Courier-Bold.afm',
  3757. 'i' => 'Courier-Oblique.afm',
  3758. 'bi' => 'Courier-BoldOblique.afm',
  3759. 'ib' => 'Courier-BoldOblique.afm'
  3760. );
  3761. $this->fontFamilies['Times-Roman.afm'] = array(
  3762. 'b' => 'Times-Bold.afm',
  3763. 'i' => 'Times-Italic.afm',
  3764. 'bi' => 'Times-BoldItalic.afm',
  3765. 'ib' => 'Times-BoldItalic.afm'
  3766. );
  3767. }
  3768. }
  3769. else
  3770. {
  3771. // the user is trying to set a font family
  3772. // note that this can also be used to set the base ones to something
  3773. // else
  3774. if ( strlen( $family ) )
  3775. {
  3776. $this->fontFamilies[$family] = $options;
  3777. }
  3778. }
  3779. }
  3780. /**
  3781. * used to add messages for use in debugging
  3782. */
  3783. function addMessage($message)
  3784. {
  3785. $this->messages .= $message . "\n";
  3786. }
  3787. /**
  3788. * a few functions which should allow the document to be treated
  3789. * transactionally.
  3790. */
  3791. function transaction($action)
  3792. {
  3793. switch ( $action )
  3794. {
  3795. case 'start' :
  3796. // store all the data away into the checkpoint variable
  3797. $data = get_object_vars( $this );
  3798. $this->checkpoint = $data;
  3799. unset( $data );
  3800. break;
  3801. case 'commit' :
  3802. if ( is_array( $this->checkpoint ) && isset( $this->checkpoint['checkpoint'] ) )
  3803. {
  3804. $tmp = $this->checkpoint['checkpoint'];
  3805. $this->checkpoint = $tmp;
  3806. unset( $tmp );
  3807. }
  3808. else
  3809. {
  3810. $this->checkpoint = '';
  3811. }
  3812. break;
  3813. case 'rewind' :
  3814. // do not destroy the current checkpoint, but move us back to
  3815. // the state then, so that we can try again
  3816. if ( is_array( $this->checkpoint ) )
  3817. {
  3818. // can only abort if were inside a checkpoint
  3819. $tmp = $this->checkpoint;
  3820. foreach ( $tmp as $k => $v )
  3821. {
  3822. if ( $k != 'checkpoint' )
  3823. {
  3824. $this->$k = $v;
  3825. }
  3826. }
  3827. unset( $tmp );
  3828. }
  3829. break;
  3830. case 'abort' :
  3831. if ( is_array( $this->checkpoint ) )
  3832. {
  3833. // can only abort if were inside a checkpoint
  3834. $tmp = $this->checkpoint;
  3835. foreach ( $tmp as $k => $v )
  3836. {
  3837. $this->$k = $v;
  3838. }
  3839. unset( $tmp );
  3840. }
  3841. break;
  3842. }
  3843. }
  3844. } // end of class
  3845. ?>