PageRenderTime 62ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/build/PhpDocumentor/phpDocumentor/Converters/PDF/default/class.pdf.php

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