home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 April / CMCD0404.ISO / Software / Freeware / Programare / dotproject / lib / ezpdf / class.pdf.php < prev   
PHP Script  |  2003-05-28  |  103KB  |  3,075 lines

  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. * LICENCE
  16. * This code has been placed in the Public Domain for all to enjoy.
  17. *
  18. * @author        Wayne Munro <pdf@ros.co.nz>
  19. * @version     009
  20. * @package    Cpdf
  21. */
  22. class Cpdf {
  23.  
  24. /**
  25. * the current number of pdf objects in the document
  26. */
  27. var $numObj=0;
  28. /**
  29. * this array contains all of the pdf objects, ready for final assembly
  30. */
  31. var $objects = array();
  32. /**
  33. * the objectId (number within the objects array) of the document catalog
  34. */
  35. var $catalogId;
  36. /**
  37. * array carrying information about the fonts that the system currently knows about
  38. * used to ensure that a font is not loaded twice, among other things
  39. */
  40. var $fonts=array(); 
  41. /**
  42. * a record of the current font
  43. */
  44. var $currentFont='';
  45. /**
  46. * the current base font
  47. */
  48. var $currentBaseFont='';
  49. /**
  50. * the number of the current font within the font array
  51. */
  52. var $currentFontNum=0;
  53. /**
  54. */
  55. var $currentNode;
  56. /**
  57. * object number of the current page
  58. */
  59. var $currentPage;
  60. /**
  61. * object number of the currently active contents block
  62. */
  63. var $currentContents;
  64. /**
  65. * number of fonts within the system
  66. */
  67. var $numFonts=0;
  68. /**
  69. * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
  70. */
  71. var $currentColour=array('r'=>-1,'g'=>-1,'b'=>-1);
  72. /**
  73. * current colour for stroke operations (lines etc.)
  74. */
  75. var $currentStrokeColour=array('r'=>-1,'g'=>-1,'b'=>-1);
  76. /**
  77. * current style that lines are drawn in
  78. */
  79. var $currentLineStyle='';
  80. /**
  81. * an array which is used to save the state of the document, mainly the colours and styles
  82. * it is used to temporarily change to another state, the change back to what it was before
  83. */
  84. var $stateStack = array();
  85. /**
  86. * number of elements within the state stack
  87. */
  88. var $nStateStack = 0;
  89. /**
  90. * number of page objects within the document
  91. */
  92. var $numPages=0;
  93. /**
  94. * object Id storage stack
  95. */
  96. var $stack=array();
  97. /**
  98. * number of elements within the object Id storage stack
  99. */
  100. var $nStack=0;
  101. /**
  102. * an array which contains information about the objects which are not firmly attached to pages
  103. * these have been added with the addObject function
  104. */
  105. var $looseObjects=array();
  106. /**
  107. * array contains infomation about how the loose objects are to be added to the document
  108. */
  109. var $addLooseObjects=array();
  110. /**
  111. * the objectId of the information object for the document
  112. * this contains authorship, title etc.
  113. */
  114. var $infoObject=0;
  115. /**
  116. * number of images being tracked within the document
  117. */
  118. var $numImages=0;
  119. /**
  120. * an array containing options about the document
  121. * it defaults to turning on the compression of the objects
  122. */
  123. var $options=array('compression'=>1);
  124. /**
  125. * the objectId of the first page of the document
  126. */
  127. var $firstPageId;
  128. /**
  129. * used to track the last used value of the inter-word spacing, this is so that it is known
  130. * when the spacing is changed.
  131. */
  132. var $wordSpaceAdjust=0;
  133. /**
  134. * the object Id of the procset object
  135. */
  136. var $procsetObjectId;
  137. /**
  138. * store the information about the relationship between font families
  139. * this used so that the code knows which font is the bold version of another font, etc.
  140. * the value of this array is initialised in the constuctor function.
  141. */
  142. var $fontFamilies = array();
  143. /**
  144. * track if the current font is bolded or italicised
  145. */
  146. var $currentTextState = ''; 
  147. /**
  148. * messages are stored here during processing, these can be selected afterwards to give some useful debug information
  149. */
  150. var $messages='';
  151. /**
  152. * the ancryption array for the document encryption is stored here
  153. */
  154. var $arc4='';
  155. /**
  156. * the object Id of the encryption information
  157. */
  158. var $arc4_objnum=0;
  159. /**
  160. * the file identifier, used to uniquely identify a pdf document
  161. */
  162. var $fileIdentifier='';
  163. /**
  164. * a flag to say if a document is to be encrypted or not
  165. */
  166. var $encrypted=0;
  167. /**
  168. * the ancryption key for the encryption of all the document content (structure is not encrypted)
  169. */
  170. var $encryptionKey='';
  171. /**
  172. * array which forms a stack to keep track of nested callback functions
  173. */
  174. var $callback = array();
  175. /**
  176. * the number of callback functions in the callback array
  177. */
  178. var $nCallback = 0;
  179. /**
  180. * store label->id pairs for named destinations, these will be used to replace internal links
  181. * done this way so that destinations can be defined after the location that links to them
  182. */
  183. var $destinations = array();
  184. /**
  185. * store the stack for the transaction commands, each item in here is a record of the values of all the 
  186. * variables within the class, so that the user can rollback at will (from each 'start' command)
  187. * note that this includes the objects array, so these can be large.
  188. */
  189. var $checkpoint = '';
  190. /**
  191. * class constructor
  192. * this will start a new document
  193. * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
  194. */
  195. function Cpdf ($pageSize=array(0,0,612,792)){
  196.   $this->newDocument($pageSize);
  197.   
  198.   // also initialize the font families that are known about already
  199.   $this->setFontFamily('init');
  200. //  $this->fileIdentifier = md5('xxxxxxxx'.time());
  201.  
  202. }
  203.  
  204. /**
  205. * Document object methods (internal use only)
  206. *
  207. * There is about one object method for each type of object in the pdf document
  208. * Each function has the same call list ($id,$action,$options).
  209. * $id = the object ID of the object, or what it is to be if it is being created
  210. * $action = a string specifying the action to be performed, though ALL must support:
  211. *           'new' - create the object with the id $id
  212. *           'out' - produce the output for the pdf object
  213. * $options = optional, a string or array containing the various parameters for the object
  214. *
  215. * These, in conjunction with the output function are the ONLY way for output to be produced 
  216. * within the pdf 'file'.
  217. */
  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. /**
  255. * set the viewer preferences
  256. */
  257. function o_viewerPreferences($id,$action,$options=''){
  258.   if ($action!='new'){
  259.     $o =& $this->objects[$id];
  260.   }
  261.   switch ($action){
  262.     case 'new':
  263.       $this->objects[$id]=array('t'=>'viewerPreferences','info'=>array());
  264.       break;
  265.     case 'add':
  266.       foreach($options as $k=>$v){
  267.         switch ($k){
  268.           case 'HideToolbar':
  269.           case 'HideMenubar':
  270.           case 'HideWindowUI':
  271.           case 'FitWindow':
  272.           case 'CenterWindow':
  273.           case 'NonFullScreenPageMode':
  274.           case 'Direction':
  275.             $o['info'][$k]=$v;
  276.           break;
  277.         }
  278.       }
  279.       break;
  280.     case 'out':
  281.  
  282.       $res="\n".$id." 0 obj\n".'<< ';
  283.       foreach($o['info'] as $k=>$v){
  284.         $res.="\n/".$k.' '.$v;
  285.       }
  286.       $res.="\n>>\n";
  287.       return $res;
  288.       break;
  289.   }
  290. }
  291.  
  292. /**
  293. * define the document catalog, the overall controller for the document
  294. */
  295. function o_catalog($id,$action,$options=''){
  296.   if ($action!='new'){
  297.     $o =& $this->objects[$id];
  298.   }
  299.   switch ($action){
  300.     case 'new':
  301.       $this->objects[$id]=array('t'=>'catalog','info'=>array());
  302.       $this->catalogId=$id;
  303.       break;
  304.     case 'outlines':
  305.     case 'pages':
  306.     case 'openHere':
  307.       $o['info'][$action]=$options;
  308.       break;
  309.     case 'viewerPreferences':
  310.       if (!isset($o['info']['viewerPreferences'])){
  311.         $this->numObj++;
  312.         $this->o_viewerPreferences($this->numObj,'new');
  313.         $o['info']['viewerPreferences']=$this->numObj;
  314.       }
  315.       $vp = $o['info']['viewerPreferences'];
  316.       $this->o_viewerPreferences($vp,'add',$options);
  317.       break;
  318.     case 'out':
  319.       $res="\n".$id." 0 obj\n".'<< /Type /Catalog';
  320.       foreach($o['info'] as $k=>$v){
  321.         switch($k){
  322.           case 'outlines':
  323.             $res.="\n".'/Outlines '.$v.' 0 R';
  324.             break;
  325.           case 'pages':
  326.             $res.="\n".'/Pages '.$v.' 0 R';
  327.             break;
  328.           case 'viewerPreferences':
  329.             $res.="\n".'/ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
  330.             break;
  331.           case 'openHere':
  332.             $res.="\n".'/OpenAction '.$o['info']['openHere'].' 0 R';
  333.             break;
  334.         }
  335.       }
  336.       $res.=" >>\nendobj";
  337.       return $res;
  338.       break;
  339.   }
  340. }
  341.  
  342. /**
  343. * object which is a parent to the pages in the document
  344. */
  345. function o_pages($id,$action,$options=''){
  346.   if ($action!='new'){
  347.     $o =& $this->objects[$id];
  348.   }
  349.   switch ($action){
  350.     case 'new':
  351.       $this->objects[$id]=array('t'=>'pages','info'=>array());
  352.       $this->o_catalog($this->catalogId,'pages',$id);
  353.       break;
  354.     case 'page':
  355.       if (!is_array($options)){
  356.         // then it will just be the id of the new page
  357.         $o['info']['pages'][]=$options;
  358.       } else {
  359.         // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
  360.         // and pos is either 'before' or 'after', saying where this page will fit.
  361.         if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])){
  362.           $i = array_search($options['rid'],$o['info']['pages']);
  363.           if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i]==$options['rid']){
  364.             // then there is a match
  365.             // make a space
  366.             switch ($options['pos']){
  367.               case 'before':
  368.                 $k = $i;
  369.                 break;
  370.               case 'after':
  371.                 $k=$i+1;
  372.                 break;
  373.               default:
  374.                 $k=-1;
  375.                 break;
  376.             }
  377.             if ($k>=0){
  378.               for ($j=count($o['info']['pages'])-1;$j>=$k;$j--){
  379.                 $o['info']['pages'][$j+1]=$o['info']['pages'][$j];
  380.               }
  381.               $o['info']['pages'][$k]=$options['id'];
  382.             }
  383.           }
  384.         } 
  385.       }
  386.       break;
  387.     case 'procset':
  388.       $o['info']['procset']=$options;
  389.       break;
  390.     case 'mediaBox':
  391.       $o['info']['mediaBox']=$options; // which should be an array of 4 numbers
  392.       break;
  393.     case 'font':
  394.       $o['info']['fonts'][]=array('objNum'=>$options['objNum'],'fontNum'=>$options['fontNum']);
  395.       break;
  396.     case 'xObject':
  397.       $o['info']['xObjects'][]=array('objNum'=>$options['objNum'],'label'=>$options['label']);
  398.       break;
  399.     case 'out':
  400.       if (count($o['info']['pages'])){
  401.         $res="\n".$id." 0 obj\n<< /Type /Pages\n/Kids [";
  402.         foreach($o['info']['pages'] as $k=>$v){
  403.           $res.=$v." 0 R\n";
  404.         }
  405.         $res.="]\n/Count ".count($this->objects[$id]['info']['pages']);
  406.         if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || isset($o['info']['procset'])){
  407.           $res.="\n/Resources <<";
  408.           if (isset($o['info']['procset'])){
  409.             $res.="\n/ProcSet ".$o['info']['procset']." 0 R";
  410.           }
  411.           if (isset($o['info']['fonts']) && count($o['info']['fonts'])){
  412.             $res.="\n/Font << ";
  413.             foreach($o['info']['fonts'] as $finfo){
  414.               $res.="\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
  415.             }
  416.             $res.=" >>";
  417.           }
  418.           if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])){
  419.             $res.="\n/XObject << ";
  420.             foreach($o['info']['xObjects'] as $finfo){
  421.               $res.="\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
  422.             }
  423.             $res.=" >>";
  424.           }
  425.           $res.="\n>>";
  426.           if (isset($o['info']['mediaBox'])){
  427.             $tmp=$o['info']['mediaBox'];
  428.             $res.="\n/MediaBox [".sprintf('%.3f',$tmp[0]).' '.sprintf('%.3f',$tmp[1]).' '.sprintf('%.3f',$tmp[2]).' '.sprintf('%.3f',$tmp[3]).']';
  429.           }
  430.         }
  431.         $res.="\n >>\nendobj";
  432.       } else {
  433.         $res="\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
  434.       }
  435.       return $res;
  436.     break;
  437.   }
  438. }
  439.  
  440. /**
  441. * define the outlines in the doc, empty for now
  442. */
  443. function o_outlines($id,$action,$options=''){
  444.   if ($action!='new'){
  445.     $o =& $this->objects[$id];
  446.   }
  447.   switch ($action){
  448.     case 'new':
  449.       $this->objects[$id]=array('t'=>'outlines','info'=>array('outlines'=>array()));
  450.       $this->o_catalog($this->catalogId,'outlines',$id);
  451.       break;
  452.     case 'outline':
  453.       $o['info']['outlines'][]=$options;
  454.       break;
  455.     case 'out':
  456.       if (count($o['info']['outlines'])){
  457.         $res="\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
  458.         foreach($o['info']['outlines'] as $k=>$v){
  459.           $res.=$v." 0 R ";
  460.         }
  461.         $res.="] /Count ".count($o['info']['outlines'])." >>\nendobj";
  462.       } else {
  463.         $res="\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
  464.       }
  465.       return $res;
  466.       break;
  467.   }
  468. }
  469.  
  470. /**
  471. * an object to hold the font description
  472. */
  473. function o_font($id,$action,$options=''){
  474.   if ($action!='new'){
  475.     $o =& $this->objects[$id];
  476.   }
  477.   switch ($action){
  478.     case 'new':
  479.       $this->objects[$id]=array('t'=>'font','info'=>array('name'=>$options['name'],'SubType'=>'Type1'));
  480.       $fontNum=$this->numFonts;
  481.       $this->objects[$id]['info']['fontNum']=$fontNum;
  482.       // deal with the encoding and the differences
  483.       if (isset($options['differences'])){
  484.         // then we'll need an encoding dictionary
  485.         $this->numObj++;
  486.         $this->o_fontEncoding($this->numObj,'new',$options);
  487.         $this->objects[$id]['info']['encodingDictionary']=$this->numObj;
  488.       } else if (isset($options['encoding'])){
  489.         // we can specify encoding here
  490.         switch($options['encoding']){
  491.           case 'WinAnsiEncoding':
  492.           case 'MacRomanEncoding':
  493.           case 'MacExpertEncoding':
  494.             $this->objects[$id]['info']['encoding']=$options['encoding'];
  495.             break;
  496.           case 'none':
  497.             break;
  498.           default:
  499.             $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
  500.             break;
  501.         }
  502.       } else {
  503.         $this->objects[$id]['info']['encoding']='WinAnsiEncoding';
  504.       }
  505.       // also tell the pages node about the new font
  506.       $this->o_pages($this->currentNode,'font',array('fontNum'=>$fontNum,'objNum'=>$id));
  507.       break;
  508.     case 'add':
  509.       foreach ($options as $k=>$v){
  510.         switch ($k){
  511.           case 'BaseFont':
  512.             $o['info']['name'] = $v;
  513.             break;
  514.           case 'FirstChar':
  515.           case 'LastChar':
  516.           case 'Widths':
  517.           case 'FontDescriptor':
  518.           case 'SubType':
  519.           $this->addMessage('o_font '.$k." : ".$v);
  520.             $o['info'][$k] = $v;
  521.             break;
  522.         }
  523.      }
  524.       break;
  525.     case 'out':
  526.       $res="\n".$id." 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
  527.       $res.="/Name /F".$o['info']['fontNum']."\n";
  528.       $res.="/BaseFont /".$o['info']['name']."\n";
  529.       if (isset($o['info']['encodingDictionary'])){
  530.         // then place a reference to the dictionary
  531.         $res.="/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
  532.       } else if (isset($o['info']['encoding'])){
  533.         // use the specified encoding
  534.         $res.="/Encoding /".$o['info']['encoding']."\n";
  535.       }
  536.       if (isset($o['info']['FirstChar'])){
  537.         $res.="/FirstChar ".$o['info']['FirstChar']."\n";
  538.       }
  539.       if (isset($o['info']['LastChar'])){
  540.         $res.="/LastChar ".$o['info']['LastChar']."\n";
  541.       }
  542.       if (isset($o['info']['Widths'])){
  543.         $res.="/Widths ".$o['info']['Widths']." 0 R\n";
  544.       }
  545.       if (isset($o['info']['FontDescriptor'])){
  546.         $res.="/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
  547.       }
  548.       $res.=">>\nendobj";
  549.       return $res;
  550.       break;
  551.   }
  552. }
  553.  
  554. /**
  555. * a font descriptor, needed for including additional fonts
  556. */
  557. function o_fontDescriptor($id,$action,$options=''){
  558.   if ($action!='new'){
  559.     $o =& $this->objects[$id];
  560.   }
  561.   switch ($action){
  562.     case 'new':
  563.       $this->objects[$id]=array('t'=>'fontDescriptor','info'=>$options);
  564.       break;
  565.     case 'out':
  566.       $res="\n".$id." 0 obj\n<< /Type /FontDescriptor\n";
  567.       foreach ($o['info'] as $label => $value){
  568.         switch ($label){
  569.           case 'Ascent':
  570.           case 'CapHeight':
  571.           case 'Descent':
  572.           case 'Flags':
  573.           case 'ItalicAngle':
  574.           case 'StemV':
  575.           case 'AvgWidth':
  576.           case 'Leading':
  577.           case 'MaxWidth':
  578.           case 'MissingWidth':
  579.           case 'StemH':
  580.           case 'XHeight':
  581.           case 'CharSet':
  582.             if (strlen($value)){
  583.               $res.='/'.$label.' '.$value."\n";
  584.             }
  585.             break;
  586.           case 'FontFile':
  587.           case 'FontFile2':
  588.           case 'FontFile3':
  589.             $res.='/'.$label.' '.$value." 0 R\n";
  590.             break;
  591.           case 'FontBBox':
  592.             $res.='/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
  593.             break;
  594.           case 'FontName':
  595.             $res.='/'.$label.' /'.$value."\n";
  596.             break;
  597.         }
  598.       }
  599.       $res.=">>\nendobj";
  600.       return $res;
  601.       break;
  602.   }
  603. }
  604.  
  605. /**
  606. * the font encoding
  607. */
  608. function o_fontEncoding($id,$action,$options=''){
  609.   if ($action!='new'){
  610.     $o =& $this->objects[$id];
  611.   }
  612.   switch ($action){
  613.     case 'new':
  614.       // the options array should contain 'differences' and maybe 'encoding'
  615.       $this->objects[$id]=array('t'=>'fontEncoding','info'=>$options);
  616.       break;
  617.     case 'out':
  618.       $res="\n".$id." 0 obj\n<< /Type /Encoding\n";
  619.       if (!isset($o['info']['encoding'])){
  620.         $o['info']['encoding']='WinAnsiEncoding';
  621.       }
  622.       if ($o['info']['encoding']!='none'){
  623.         $res.="/BaseEncoding /".$o['info']['encoding']."\n";
  624.       }
  625.       $res.="/Differences \n[";
  626.       $onum=-100;
  627.       foreach($o['info']['differences'] as $num=>$label){
  628.         if ($num!=$onum+1){
  629.           // we cannot make use of consecutive numbering
  630.           $res.= "\n".$num." /".$label;
  631.         } else {
  632.           $res.= " /".$label;
  633.         }
  634.         $onum=$num;
  635.       }
  636.       $res.="\n]\n>>\nendobj";
  637.       return $res;
  638.       break;
  639.   }
  640. }
  641.  
  642. /**
  643. * the document procset, solves some problems with printing to old PS printers
  644. */
  645. function o_procset($id,$action,$options=''){
  646.   if ($action!='new'){
  647.     $o =& $this->objects[$id];
  648.   }
  649.   switch ($action){
  650.     case 'new':
  651.       $this->objects[$id]=array('t'=>'procset','info'=>array('PDF'=>1,'Text'=>1));
  652.       $this->o_pages($this->currentNode,'procset',$id);
  653.       $this->procsetObjectId=$id;
  654.       break;
  655.     case 'add':
  656.       // this is to add new items to the procset list, despite the fact that this is considered
  657.       // obselete, the items are required for printing to some postscript printers
  658.       switch ($options) {
  659.         case 'ImageB':
  660.         case 'ImageC':
  661.         case 'ImageI':
  662.           $o['info'][$options]=1;
  663.           break;
  664.       }
  665.       break;
  666.     case 'out':
  667.       $res="\n".$id." 0 obj\n[";
  668.       foreach ($o['info'] as $label=>$val){
  669.         $res.='/'.$label.' ';
  670.       }
  671.       $res.="]\nendobj";
  672.       return $res;
  673.       break;
  674.   }
  675. }
  676.  
  677. /**
  678. * define the document information
  679. */
  680. function o_info($id,$action,$options=''){
  681.   if ($action!='new'){
  682.     $o =& $this->objects[$id];
  683.   }
  684.   switch ($action){
  685.     case 'new':
  686.       $this->infoObject=$id;
  687.       $date='D:'.date('Ymd');
  688.       $this->objects[$id]=array('t'=>'info','info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz','CreationDate'=>$date));
  689.       break;
  690.     case 'Title':
  691.     case 'Author':
  692.     case 'Subject':
  693.     case 'Keywords':
  694.     case 'Creator':
  695.     case 'Producer':
  696.     case 'CreationDate':
  697.     case 'ModDate':
  698.     case 'Trapped':
  699.       $o['info'][$action]=$options;
  700.       break;
  701.     case 'out':
  702.       if ($this->encrypted){
  703.         $this->encryptInit($id);
  704.       }
  705.       $res="\n".$id." 0 obj\n<<\n";
  706.       foreach ($o['info']  as $k=>$v){
  707.         $res.='/'.$k.' (';
  708.         if ($this->encrypted){
  709.           $res.=$this->filterText($this->ARC4($v));
  710.         } else {
  711.           $res.=$this->filterText($v);
  712.         }
  713.         $res.=")\n";
  714.       }
  715.       $res.=">>\nendobj";
  716.       return $res;
  717.       break;
  718.   }
  719. }
  720.  
  721. /**
  722. * an action object, used to link to URLS initially
  723. */
  724. function o_action($id,$action,$options=''){
  725.   if ($action!='new'){
  726.     $o =& $this->objects[$id];
  727.   }
  728.   switch ($action){
  729.     case 'new':
  730.       if (is_array($options)){
  731.         $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>$options['type']);
  732.       } else {
  733.         // then assume a URI action
  734.         $this->objects[$id]=array('t'=>'action','info'=>$options,'type'=>'URI');
  735.       }
  736.       break;
  737.     case 'out':
  738.       if ($this->encrypted){
  739.         $this->encryptInit($id);
  740.       }
  741.       $res="\n".$id." 0 obj\n<< /Type /Action";
  742.       switch($o['type']){
  743.         case 'ilink':
  744.           // there will be an 'label' setting, this is the name of the destination
  745.           $res.="\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
  746.           break;
  747.         case 'URI':
  748.           $res.="\n/S /URI\n/URI (";
  749.           if ($this->encrypted){
  750.             $res.=$this->filterText($this->ARC4($o['info']));
  751.           } else {
  752.             $res.=$this->filterText($o['info']);
  753.           }
  754.           $res.=")";
  755.           break;
  756.       }
  757.       $res.="\n>>\nendobj";
  758.       return $res;
  759.       break;
  760.   }
  761. }
  762.  
  763. /**
  764. * an annotation object, this will add an annotation to the current page.
  765. * initially will support just link annotations 
  766. */
  767. function o_annotation($id,$action,$options=''){
  768.   if ($action!='new'){
  769.     $o =& $this->objects[$id];
  770.   }
  771.   switch ($action){
  772.     case 'new':
  773.       // add the annotation to the current page
  774.       $pageId = $this->currentPage;
  775.       $this->o_page($pageId,'annot',$id);
  776.       // and add the action object which is going to be required
  777.       switch($options['type']){
  778.         case 'link':
  779.           $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  780.           $this->numObj++;
  781.           $this->o_action($this->numObj,'new',$options['url']);
  782.           $this->objects[$id]['info']['actionId']=$this->numObj;
  783.           break;
  784.         case 'ilink':
  785.           // this is to a named internal link
  786.           $label = $options['label'];
  787.           $this->objects[$id]=array('t'=>'annotation','info'=>$options);
  788.           $this->numObj++;
  789.           $this->o_action($this->numObj,'new',array('type'=>'ilink','label'=>$label));
  790.           $this->objects[$id]['info']['actionId']=$this->numObj;
  791.           break;
  792.       }
  793.       break;
  794.     case 'out':
  795.       $res="\n".$id." 0 obj\n<< /Type /Annot";
  796.       switch($o['info']['type']){
  797.         case 'link':
  798.         case 'ilink':
  799.           $res.= "\n/Subtype /Link";
  800.           break;
  801.       }
  802.       $res.="\n/A ".$o['info']['actionId']." 0 R";
  803.       $res.="\n/Border [0 0 0]";
  804.       $res.="\n/H /I";
  805.       $res.="\n/Rect [ ";
  806.       foreach($o['info']['rect'] as $v){
  807.         $res.= sprintf("%.4f ",$v);
  808.       }
  809.       $res.="]";
  810.       $res.="\n>>\nendobj";
  811.       return $res;
  812.       break;
  813.   }
  814. }
  815.  
  816. /**
  817. * a page object, it also creates a contents object to hold its contents
  818. */
  819. function o_page($id,$action,$options=''){
  820.   if ($action!='new'){
  821.     $o =& $this->objects[$id];
  822.   }
  823.   switch ($action){
  824.     case 'new':
  825.       $this->numPages++;
  826.       $this->objects[$id]=array('t'=>'page','info'=>array('parent'=>$this->currentNode,'pageNum'=>$this->numPages));
  827.       if (is_array($options)){
  828.         // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
  829.         $options['id']=$id;
  830.         $this->o_pages($this->currentNode,'page',$options);
  831.       } else {
  832.         $this->o_pages($this->currentNode,'page',$id);
  833.       }
  834.       $this->currentPage=$id;
  835.       //make a contents object to go with this page
  836.       $this->numObj++;
  837.       $this->o_contents($this->numObj,'new',$id);
  838.       $this->currentContents=$this->numObj;
  839.       $this->objects[$id]['info']['contents']=array();
  840.       $this->objects[$id]['info']['contents'][]=$this->numObj;
  841.       $match = ($this->numPages%2 ? 'odd' : 'even');
  842.       foreach($this->addLooseObjects as $oId=>$target){
  843.         if ($target=='all' || $match==$target){
  844.           $this->objects[$id]['info']['contents'][]=$oId;
  845.         }
  846.       }
  847.       break;
  848.     case 'content':
  849.       $o['info']['contents'][]=$options;
  850.       break;
  851.     case 'annot':
  852.       // add an annotation to this page
  853.       if (!isset($o['info']['annot'])){
  854.         $o['info']['annot']=array();
  855.       }
  856.       // $options should contain the id of the annotation dictionary
  857.       $o['info']['annot'][]=$options;
  858.       break;
  859.     case 'out':
  860.       $res="\n".$id." 0 obj\n<< /Type /Page";
  861.       $res.="\n/Parent ".$o['info']['parent']." 0 R";
  862.       if (isset($o['info']['annot'])){
  863.         $res.="\n/Annots [";
  864.         foreach($o['info']['annot'] as $aId){
  865.           $res.=" ".$aId." 0 R";
  866.         }
  867.         $res.=" ]";
  868.       }
  869.       $count = count($o['info']['contents']);
  870.       if ($count==1){
  871.         $res.="\n/Contents ".$o['info']['contents'][0]." 0 R";
  872.       } else if ($count>1){
  873.         $res.="\n/Contents [\n";
  874.         foreach ($o['info']['contents'] as $cId){
  875.           $res.=$cId." 0 R\n";
  876.         }
  877.         $res.="]";
  878.       }
  879.       $res.="\n>>\nendobj";
  880.       return $res;
  881.       break;
  882.   }
  883. }
  884.  
  885. /**
  886. * the contents objects hold all of the content which appears on pages
  887. */
  888. function o_contents($id,$action,$options=''){
  889.   if ($action!='new'){
  890.     $o =& $this->objects[$id];
  891.   }
  892.   switch ($action){
  893.     case 'new':
  894.       $this->objects[$id]=array('t'=>'contents','c'=>'','info'=>array());
  895.       if (strlen($options) && intval($options)){
  896.         // then this contents is the primary for a page
  897.         $this->objects[$id]['onPage']=$options;
  898.       } else if ($options=='raw'){
  899.         // then this page contains some other type of system object
  900.         $this->objects[$id]['raw']=1;
  901.       }
  902.       break;
  903.     case 'add':
  904.       // add more options to the decleration
  905.       foreach ($options as $k=>$v){
  906.         $o['info'][$k]=$v;
  907.       }
  908.     case 'out':
  909.       $tmp=$o['c'];
  910.       $res= "\n".$id." 0 obj\n";
  911.       if (isset($this->objects[$id]['raw'])){
  912.         $res.=$tmp;
  913.       } else {
  914.         $res.= "<<";
  915.         if (function_exists('gzcompress') && $this->options['compression']){
  916.           // then implement ZLIB based compression on this content stream
  917.           $res.=" /Filter /FlateDecode";
  918.           $tmp = gzcompress($tmp);
  919.         }
  920.         if ($this->encrypted){
  921.           $this->encryptInit($id);
  922.           $tmp = $this->ARC4($tmp);
  923.         }
  924.         foreach($o['info'] as $k=>$v){
  925.           $res .= "\n/".$k.' '.$v;
  926.         }
  927.         $res.="\n/Length ".strlen($tmp)." >>\nstream\n".$tmp."\nendstream";
  928.       }
  929.       $res.="\nendobj\n";
  930.       return $res;
  931.       break;
  932.   }
  933. }
  934.  
  935. /**
  936. * an image object, will be an XObject in the document, includes description and data
  937. */
  938. function o_image($id,$action,$options=''){
  939.   if ($action!='new'){
  940.     $o =& $this->objects[$id];
  941.   }
  942.   switch($action){
  943.     case 'new':
  944.       // make the new object
  945.       $this->objects[$id]=array('t'=>'image','data'=>$options['data'],'info'=>array());
  946.       $this->objects[$id]['info']['Type']='/XObject';
  947.       $this->objects[$id]['info']['Subtype']='/Image';
  948.       $this->objects[$id]['info']['Width']=$options['iw'];
  949.       $this->objects[$id]['info']['Height']=$options['ih'];
  950.       if (!isset($options['type']) || $options['type']=='jpg'){
  951.         if (!isset($options['channels'])){
  952.           $options['channels']=3;
  953.         }
  954.         switch($options['channels']){
  955.           case 1:
  956.             $this->objects[$id]['info']['ColorSpace']='/DeviceGray';
  957.             break;
  958.           default:
  959.             $this->objects[$id]['info']['ColorSpace']='/DeviceRGB';
  960.             break;
  961.         }
  962.         $this->objects[$id]['info']['Filter']='/DCTDecode';
  963.         $this->objects[$id]['info']['BitsPerComponent']=8;
  964.       } else if ($options['type']=='png'){
  965.         $this->objects[$id]['info']['Filter']='/FlateDecode';
  966.         $this->objects[$id]['info']['DecodeParms']='<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
  967.         if (strlen($options['pdata'])){
  968.           $tmp = ' [ /Indexed /DeviceRGB '.(strlen($options['pdata'])/3-1).' ';
  969.           $this->numObj++;
  970.           $this->o_contents($this->numObj,'new');
  971.           $this->objects[$this->numObj]['c']=$options['pdata'];
  972.           $tmp.=$this->numObj.' 0 R';
  973.           $tmp .=' ]';
  974.           $this->objects[$id]['info']['ColorSpace'] = $tmp;
  975.           if (isset($options['transparency'])){
  976.             switch($options['transparency']['type']){
  977.               case 'indexed':
  978.                 $tmp=' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
  979.                 $this->objects[$id]['info']['Mask'] = $tmp;
  980.                 break;
  981.             }
  982.           }
  983.         } else {
  984.           $this->objects[$id]['info']['ColorSpace']='/'.$options['color'];
  985.         }
  986.         $this->objects[$id]['info']['BitsPerComponent']=$options['bitsPerComponent'];
  987.       }
  988.       // assign it a place in the named resource dictionary as an external object, according to
  989.       // the label passed in with it.
  990.       $this->o_pages($this->currentNode,'xObject',array('label'=>$options['label'],'objNum'=>$id));
  991.       // also make sure that we have the right procset object for it.
  992.       $this->o_procset($this->procsetObjectId,'add','ImageC');
  993.       break;
  994.     case 'out':
  995.       $tmp=$o['data'];
  996.       $res= "\n".$id." 0 obj\n<<";
  997.       foreach($o['info'] as $k=>$v){
  998.         $res.="\n/".$k.' '.$v;
  999.       }
  1000.       if ($this->encrypted){
  1001.         $this->encryptInit($id);
  1002.         $tmp = $this->ARC4($tmp);
  1003.       }
  1004.       $res.="\n/Length ".strlen($tmp)." >>\nstream\n".$tmp."\nendstream\nendobj\n";
  1005.       return $res;
  1006.       break;
  1007.   }
  1008. }
  1009.  
  1010. /**
  1011. * encryption object.
  1012. */
  1013. function o_encryption($id,$action,$options=''){
  1014.   if ($action!='new'){
  1015.     $o =& $this->objects[$id];
  1016.   }
  1017.   switch($action){
  1018.     case 'new':
  1019.       // make the new object
  1020.       $this->objects[$id]=array('t'=>'encryption','info'=>$options);
  1021.       $this->arc4_objnum=$id;
  1022.       // figure out the additional paramaters required
  1023.       $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);
  1024.       $len = strlen($options['owner']);
  1025.       if ($len>32){
  1026.         $owner = substr($options['owner'],0,32);
  1027.       } else if ($len<32){
  1028.         $owner = $options['owner'].substr($pad,0,32-$len);
  1029.       } else {
  1030.         $owner = $options['owner'];
  1031.       }
  1032.       $len = strlen($options['user']);
  1033.       if ($len>32){
  1034.         $user = substr($options['user'],0,32);
  1035.       } else if ($len<32){
  1036.         $user = $options['user'].substr($pad,0,32-$len);
  1037.       } else {
  1038.         $user = $options['user'];
  1039.       }
  1040.       $tmp = $this->md5_16($owner);
  1041.       $okey = substr($tmp,0,5);
  1042.       $this->ARC4_init($okey);
  1043.       $ovalue=$this->ARC4($user);
  1044.       $this->objects[$id]['info']['O']=$ovalue;
  1045.       // now make the u value, phew.
  1046.       $tmp = $this->md5_16($user.$ovalue.chr($options['p']).chr(255).chr(255).chr(255).$this->fileIdentifier);
  1047.       $ukey = substr($tmp,0,5);
  1048.  
  1049.       $this->ARC4_init($ukey);
  1050.       $this->encryptionKey = $ukey;
  1051.       $this->encrypted=1;
  1052.       $uvalue=$this->ARC4($pad);
  1053.  
  1054.       $this->objects[$id]['info']['U']=$uvalue;
  1055.       $this->encryptionKey=$ukey;
  1056.      
  1057.       // initialize the arc4 array
  1058.       break;
  1059.     case 'out':
  1060.       $res= "\n".$id." 0 obj\n<<";
  1061.       $res.="\n/Filter /Standard";
  1062.       $res.="\n/V 1";
  1063.       $res.="\n/R 2";
  1064.       $res.="\n/O (".$this->filterText($o['info']['O']).')';
  1065.       $res.="\n/U (".$this->filterText($o['info']['U']).')';
  1066.       // and the p-value needs to be converted to account for the twos-complement approach
  1067.       $o['info']['p'] = (($o['info']['p']^255)+1)*-1;
  1068.       $res.="\n/P ".($o['info']['p']);
  1069.       $res.="\n>>\nendobj\n";
  1070.       
  1071.       return $res;
  1072.       break;
  1073.   }
  1074. }
  1075.       
  1076. /**
  1077. * ARC4 functions
  1078. * A series of function to implement ARC4 encoding in PHP
  1079. */
  1080.  
  1081. /**
  1082. * calculate the 16 byte version of the 128 bit md5 digest of the string
  1083. */
  1084. function md5_16($string){
  1085.   $tmp = md5($string);
  1086.   $out='';
  1087.   for ($i=0;$i<=30;$i=$i+2){
  1088.     $out.=chr(hexdec(substr($tmp,$i,2)));
  1089.   }
  1090.   return $out;
  1091. }
  1092.  
  1093. /**
  1094. * initialize the encryption for processing a particular object 
  1095. */
  1096. function encryptInit($id){
  1097.   $tmp = $this->encryptionKey;
  1098.   $hex = dechex($id);
  1099.   if (strlen($hex)<6){
  1100.     $hex = substr('000000',0,6-strlen($hex)).$hex;
  1101.   }
  1102.   $tmp.= chr(hexdec(substr($hex,4,2))).chr(hexdec(substr($hex,2,2))).chr(hexdec(substr($hex,0,2))).chr(0).chr(0);
  1103.   $key = $this->md5_16($tmp);
  1104.   $this->ARC4_init(substr($key,0,10));
  1105. }
  1106.  
  1107. /**
  1108. * initialize the ARC4 encryption
  1109. */
  1110. function ARC4_init($key=''){
  1111.   $this->arc4 = '';
  1112.   // setup the control array
  1113.   if (strlen($key)==0){
  1114.     return;
  1115.   }
  1116.   $k = '';
  1117.   while(strlen($k)<256){
  1118.     $k.=$key;
  1119.   }
  1120.   $k=substr($k,0,256);
  1121.   for ($i=0;$i<256;$i++){
  1122.     $this->arc4 .= chr($i);
  1123.   }
  1124.   $j=0;
  1125.   for ($i=0;$i<256;$i++){
  1126.     $t = $this->arc4[$i];
  1127.     $j = ($j + ord($t) + ord($k[$i]))%256;
  1128.     $this->arc4[$i]=$this->arc4[$j];
  1129.     $this->arc4[$j]=$t;
  1130.   }    
  1131. }
  1132.  
  1133. /**
  1134. * ARC4 encrypt a text string
  1135. */
  1136. function ARC4($text){
  1137.   $len=strlen($text);
  1138.   $a=0;
  1139.   $b=0;
  1140.   $c = $this->arc4;
  1141.   $out='';
  1142.   for ($i=0;$i<$len;$i++){
  1143.     $a = ($a+1)%256;
  1144.     $t= $c[$a];
  1145.     $b = ($b+ord($t))%256;
  1146.     $c[$a]=$c[$b];
  1147.     $c[$b]=$t;
  1148.     $k = ord($c[(ord($c[$a])+ord($c[$b]))%256]);
  1149.     $out.=chr(ord($text[$i]) ^ $k);
  1150.   }
  1151.   
  1152.   return $out;
  1153. }
  1154.  
  1155. /**
  1156. * functions which can be called to adjust or add to the document
  1157. */
  1158.  
  1159. /**
  1160. * add a link in the document to an external URL
  1161. */
  1162. function addLink($url,$x0,$y0,$x1,$y1){
  1163.   $this->numObj++;
  1164.   $info = array('type'=>'link','url'=>$url,'rect'=>array($x0,$y0,$x1,$y1));
  1165.   $this->o_annotation($this->numObj,'new',$info);
  1166. }
  1167.  
  1168. /**
  1169. * add a link in the document to an internal destination (ie. within the document)
  1170. */
  1171. function addInternalLink($label,$x0,$y0,$x1,$y1){
  1172.   $this->numObj++;
  1173.   $info = array('type'=>'ilink','label'=>$label,'rect'=>array($x0,$y0,$x1,$y1));
  1174.   $this->o_annotation($this->numObj,'new',$info);
  1175. }
  1176.  
  1177. /**
  1178. * set the encryption of the document
  1179. * can be used to turn it on and/or set the passwords which it will have.
  1180. * also the functions that the user will have are set here, such as print, modify, add
  1181. */
  1182. function setEncryption($userPass='',$ownerPass='',$pc=array()){
  1183.   $p=bindec(11000000);
  1184.  
  1185.   $options = array(
  1186.      'print'=>4
  1187.     ,'modify'=>8
  1188.     ,'copy'=>16
  1189.     ,'add'=>32
  1190.   );
  1191.   foreach($pc as $k=>$v){
  1192.     if ($v && isset($options[$k])){
  1193.       $p+=$options[$k];
  1194.     } else if (isset($options[$v])){
  1195.       $p+=$options[$v];
  1196.     }
  1197.   }
  1198.   // implement encryption on the document
  1199.   if ($this->arc4_objnum == 0){
  1200.     // then the block does not exist already, add it.
  1201.     $this->numObj++;
  1202.     if (strlen($ownerPass)==0){
  1203.       $ownerPass=$userPass;
  1204.     }
  1205.     $this->o_encryption($this->numObj,'new',array('user'=>$userPass,'owner'=>$ownerPass,'p'=>$p));
  1206.   }
  1207. }
  1208.  
  1209. /**
  1210. * should be used for internal checks, not implemented as yet
  1211. */
  1212. function checkAllHere(){
  1213. }
  1214.  
  1215. /**
  1216. * return the pdf stream as a string returned from the function
  1217. */
  1218. function output($debug=0){
  1219.  
  1220.   if ($debug){
  1221.     // turn compression off
  1222.     $this->options['compression']=0;
  1223.   }
  1224.  
  1225.   if ($this->arc4_objnum){
  1226.     $this->ARC4_init($this->encryptionKey);
  1227.   }
  1228.  
  1229.   $this->checkAllHere();
  1230.  
  1231.   $xref=array();
  1232.   $content="%PDF-1.3\n%Γπ╧╙\n";
  1233. //  $content="%PDF-1.3\n";
  1234.   $pos=strlen($content);
  1235.   foreach($this->objects as $k=>$v){
  1236.     $tmp='o_'.$v['t'];
  1237.     $cont=$this->$tmp($k,'out');
  1238.     $content.=$cont;
  1239.     $xref[]=$pos;
  1240.     $pos+=strlen($cont);
  1241.   }
  1242.   $content.="\nxref\n0 ".(count($xref)+1)."\n0000000000 65535 f \n";
  1243.   foreach($xref as $p){
  1244.     $content.=substr('0000000000',0,10-strlen($p)).$p." 00000 n \n";
  1245.   }
  1246.   $content.="\ntrailer\n  << /Size ".(count($xref)+1)."\n     /Root 1 0 R\n     /Info ".$this->infoObject." 0 R\n";
  1247.   // if encryption has been applied to this document then add the marker for this dictionary
  1248.   if ($this->arc4_objnum > 0){
  1249.     $content .= "/Encrypt ".$this->arc4_objnum." 0 R\n";
  1250.   }
  1251.   if (strlen($this->fileIdentifier)){
  1252.     $content .= "/ID[<".$this->fileIdentifier."><".$this->fileIdentifier.">]\n";
  1253.   }
  1254.   $content .= "  >>\nstartxref\n".$pos."\n%%EOF\n";
  1255.   return $content;
  1256. }
  1257.  
  1258. /**
  1259. * intialize a new document
  1260. * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
  1261. * this function is called automatically by the constructor function
  1262. *
  1263. * @access private
  1264. */
  1265. function newDocument($pageSize=array(0,0,612,792)){
  1266.   $this->numObj=0;
  1267.   $this->objects = array();
  1268.  
  1269.   $this->numObj++;
  1270.   $this->o_catalog($this->numObj,'new');
  1271.  
  1272.   $this->numObj++;
  1273.   $this->o_outlines($this->numObj,'new');
  1274.  
  1275.   $this->numObj++;
  1276.   $this->o_pages($this->numObj,'new');
  1277.  
  1278.   $this->o_pages($this->numObj,'mediaBox',$pageSize);
  1279.   $this->currentNode = 3;
  1280.  
  1281.   $this->numObj++;
  1282.   $this->o_procset($this->numObj,'new');
  1283.  
  1284.   $this->numObj++;
  1285.   $this->o_info($this->numObj,'new');
  1286.  
  1287.   $this->numObj++;
  1288.   $this->o_page($this->numObj,'new');
  1289.  
  1290.   // need to store the first page id as there is no way to get it to the user during 
  1291.   // startup
  1292.   $this->firstPageId = $this->currentContents;
  1293. }
  1294.  
  1295. /**
  1296. * open the font file and return a php structure containing it.
  1297. * first check if this one has been done before and saved in a form more suited to php
  1298. * note that if a php serialized version does not exist it will try and make one, but will
  1299. * require write access to the directory to do it... it is MUCH faster to have these serialized
  1300. * files.
  1301. *
  1302. * @access private
  1303. */
  1304. function openFont($font){
  1305.   // assume that $font contains both the path and perhaps the extension to the file, split them
  1306.   $pos=strrpos($font,'/');
  1307.   if ($pos===false){
  1308.     $dir = './';
  1309.     $name = $font;
  1310.   } else {
  1311.     $dir=substr($font,0,$pos+1);
  1312.     $name=substr($font,$pos+1);
  1313.   }
  1314.  
  1315.   if (substr($name,-4)=='.afm'){
  1316.     $name=substr($name,0,strlen($name)-4);
  1317.   }
  1318.   $this->addMessage('openFont: '.$font.' - '.$name);
  1319.   if (file_exists($dir.'php_'.$name.'.afm')){
  1320.     $this->addMessage('openFont: php file exists '.$dir.'php_'.$name.'.afm');
  1321.     $tmp = file($dir.'php_'.$name.'.afm');
  1322.     $this->fonts[$font]=unserialize($tmp[0]);
  1323.     if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_']<1){
  1324.       // if the font file is old, then clear it out and prepare for re-creation
  1325.       $this->addMessage('openFont: clear out, make way for new version.');
  1326.       unset($this->fonts[$font]);
  1327.     }
  1328.   }
  1329.   if (!isset($this->fonts[$font]) && file_exists($dir.$name.'.afm')){
  1330.     // then rebuild the php_<font>.afm file from the <font>.afm file
  1331.     $this->addMessage('openFont: build php file from '.$dir.$name.'.afm');
  1332.     $data = array();
  1333.     $file = file($dir.$name.'.afm');
  1334.     foreach ($file as $rowA){
  1335.       $row=trim($rowA);
  1336.       $pos=strpos($row,' ');
  1337.       if ($pos){
  1338.         // then there must be some keyword
  1339.         $key = substr($row,0,$pos);
  1340.         switch ($key){
  1341.           case 'FontName':
  1342.           case 'FullName':
  1343.           case 'FamilyName':
  1344.           case 'Weight':
  1345.           case 'ItalicAngle':
  1346.           case 'IsFixedPitch':
  1347.           case 'CharacterSet':
  1348.           case 'UnderlinePosition':
  1349.           case 'UnderlineThickness':
  1350.           case 'Version':
  1351.           case 'EncodingScheme':
  1352.           case 'CapHeight':
  1353.           case 'XHeight':
  1354.           case 'Ascender':
  1355.           case 'Descender':
  1356.           case 'StdHW':
  1357.           case 'StdVW':
  1358.           case 'StartCharMetrics':
  1359.             $data[$key]=trim(substr($row,$pos));
  1360.             break;
  1361.           case 'FontBBox':
  1362.             $data[$key]=explode(' ',trim(substr($row,$pos)));
  1363.             break;
  1364.           case 'C':
  1365.             //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
  1366.             $bits=explode(';',trim($row));
  1367.             $dtmp=array();
  1368.             foreach($bits as $bit){
  1369.               $bits2 = explode(' ',trim($bit));
  1370.               if (strlen($bits2[0])){
  1371.                 if (count($bits2)>2){
  1372.                   $dtmp[$bits2[0]]=array();
  1373.                   for ($i=1;$i<count($bits2);$i++){
  1374.                     $dtmp[$bits2[0]][]=$bits2[$i];
  1375.                   }
  1376.                 } else if (count($bits2)==2){
  1377.                   $dtmp[$bits2[0]]=$bits2[1];
  1378.                 }
  1379.               }
  1380.             }
  1381.             if ($dtmp['C']>=0){
  1382.               $data['C'][$dtmp['C']]=$dtmp;
  1383.               $data['C'][$dtmp['N']]=$dtmp;
  1384.             } else {
  1385.               $data['C'][$dtmp['N']]=$dtmp;
  1386.             }
  1387.             break;
  1388.           case 'KPX':
  1389.             //KPX Adieresis yacute -40
  1390.             $bits=explode(' ',trim($row));
  1391.             $data['KPX'][$bits[1]][$bits[2]]=$bits[3];
  1392.             break;
  1393.         }
  1394.       }
  1395.     }
  1396.     $data['_version_']=1;
  1397.     $this->fonts[$font]=$data;
  1398.     $fp = fopen($dir.'php_'.$name.'.afm','w');
  1399.     fwrite($fp,serialize($data));
  1400.     fclose($fp);
  1401.   } else if (!isset($this->fonts[$font])){
  1402.     $this->addMessage('openFont: no font file found');
  1403. //    echo 'Font not Found '.$font;
  1404.   }
  1405. }
  1406.  
  1407. /**
  1408. * if the font is not loaded then load it and make the required object
  1409. * else just make it the current font
  1410. * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
  1411. * note that encoding='none' will need to be used for symbolic fonts
  1412. * and 'differences' => an array of mappings between numbers 0->255 and character names.
  1413. *
  1414. */
  1415. function selectFont($fontName,$encoding='',$set=1){
  1416.   if (!isset($this->fonts[$fontName])){
  1417.     // load the file
  1418.     $this->openFont($fontName);
  1419.     if (isset($this->fonts[$fontName])){
  1420.       $this->numObj++;
  1421.       $this->numFonts++;
  1422.       $pos=strrpos($fontName,'/');
  1423. //      $dir=substr($fontName,0,$pos+1);
  1424.       $name=substr($fontName,$pos+1);
  1425.       if (substr($name,-4)=='.afm'){
  1426.         $name=substr($name,0,strlen($name)-4);
  1427.       }
  1428.       $options=array('name'=>$name);
  1429.       if (is_array($encoding)){
  1430.         // then encoding and differences might be set
  1431.         if (isset($encoding['encoding'])){
  1432.           $options['encoding']=$encoding['encoding'];
  1433.         }
  1434.         if (isset($encoding['differences'])){
  1435.           $options['differences']=$encoding['differences'];
  1436.         }
  1437.       } else if (strlen($encoding)){
  1438.         // then perhaps only the encoding has been set
  1439.         $options['encoding']=$encoding;
  1440.       }
  1441.       $fontObj = $this->numObj;
  1442.       $this->o_font($this->numObj,'new',$options);
  1443.       $this->fonts[$fontName]['fontNum']=$this->numFonts;
  1444.       // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there
  1445.       // should be for all non-basic fonts), then load it into an object and put the
  1446.       // references into the font object
  1447.       $basefile = substr($fontName,0,strlen($fontName)-4);
  1448.       if (file_exists($basefile.'.pfb')){
  1449.         $fbtype = 'pfb';
  1450.       } else if (file_exists($basefile.'.ttf')){
  1451.         $fbtype = 'ttf';
  1452.       } else {
  1453.         $fbtype='';
  1454.       }
  1455.       $fbfile = $basefile.'.'.$fbtype;
  1456.       
  1457. //      $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
  1458. //      $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
  1459.       $this->addMessage('selectFont: checking for - '.$fbfile);
  1460.       if (substr($fontName,-4)=='.afm' && strlen($fbtype) ){
  1461.         $adobeFontName = $this->fonts[$fontName]['FontName'];
  1462. //        $fontObj = $this->numObj;
  1463.         $this->addMessage('selectFont: adding font file - '.$fbfile.' - '.$adobeFontName);
  1464.         // find the array of fond widths, and put that into an object.
  1465.         $firstChar = -1;
  1466.         $lastChar = 0;
  1467.         $widths = array();
  1468.         foreach ($this->fonts[$fontName]['C'] as $num=>$d){
  1469.           if (intval($num)>0 || $num=='0'){
  1470.             if ($lastChar>0 && $num>$lastChar+1){
  1471.               for($i=$lastChar+1;$i<$num;$i++){
  1472.                 $widths[] = 0;
  1473.               }
  1474.             }
  1475.             $widths[] = $d['WX'];
  1476.             if ($firstChar==-1){
  1477.               $firstChar = $num;
  1478.             }
  1479.             $lastChar = $num;
  1480.           }
  1481.         }
  1482.         // also need to adjust the widths for the differences array
  1483.         if (isset($options['differences'])){
  1484.           foreach($options['differences'] as $charNum=>$charName){
  1485.             if ($charNum>$lastChar){
  1486.               for($i=$lastChar+1;$i<=$charNum;$i++){
  1487.                 $widths[]=0;
  1488.               }
  1489.               $lastChar=$charNum;
  1490.             }
  1491.             if (isset($this->fonts[$fontName]['C'][$charName])){
  1492.               $widths[$charNum-$firstChar]=$this->fonts[$fontName]['C'][$charName]['WX'];
  1493.             }
  1494.           }
  1495.         }
  1496.         $this->addMessage('selectFont: FirstChar='.$firstChar);
  1497.         $this->addMessage('selectFont: LastChar='.$lastChar);
  1498.         $this->numObj++;
  1499.         $this->o_contents($this->numObj,'new','raw');
  1500.         $this->objects[$this->numObj]['c'].='[';
  1501.         foreach($widths as $width){
  1502.           $this->objects[$this->numObj]['c'].=' '.$width;
  1503.         }
  1504.         $this->objects[$this->numObj]['c'].=' ]';
  1505.         $widthid = $this->numObj;
  1506.  
  1507.         // load the pfb file, and put that into an object too.
  1508.         // note that pdf supports only binary format type 1 font files, though there is a 
  1509.         // simple utility to convert them from pfa to pfb.
  1510.         $fp = fopen($fbfile,'rb');
  1511.         $tmp = get_magic_quotes_runtime();
  1512.         set_magic_quotes_runtime(0);
  1513.         $data = fread($fp,filesize($fbfile));
  1514.         set_magic_quotes_runtime($tmp);
  1515.         fclose($fp);
  1516.  
  1517.         // create the font descriptor
  1518.         $this->numObj++;
  1519.         $fontDescriptorId = $this->numObj;
  1520.         $this->numObj++;
  1521.         $pfbid = $this->numObj;
  1522.         // determine flags (more than a little flakey, hopefully will not matter much)
  1523.         $flags=0;
  1524.         if ($this->fonts[$fontName]['ItalicAngle']!=0){ $flags+=pow(2,6); }
  1525.         if ($this->fonts[$fontName]['IsFixedPitch']=='true'){ $flags+=1; }
  1526.         $flags+=pow(2,5); // assume non-sybolic
  1527.  
  1528.         $list = array('Ascent'=>'Ascender','CapHeight'=>'CapHeight','Descent'=>'Descender','FontBBox'=>'FontBBox','ItalicAngle'=>'ItalicAngle');
  1529.         $fdopt = array(
  1530.          'Flags'=>$flags
  1531.          ,'FontName'=>$adobeFontName
  1532.          ,'StemV'=>100  // don't know what the value for this should be!
  1533.         );
  1534.         foreach($list as $k=>$v){
  1535.           if (isset($this->fonts[$fontName][$v])){
  1536.             $fdopt[$k]=$this->fonts[$fontName][$v];
  1537.           }
  1538.         }
  1539.  
  1540.         if ($fbtype=='pfb'){
  1541.           $fdopt['FontFile']=$pfbid;
  1542.         } else if ($fbtype=='ttf'){
  1543.           $fdopt['FontFile2']=$pfbid;
  1544.         }
  1545.         $this->o_fontDescriptor($fontDescriptorId,'new',$fdopt);        
  1546.  
  1547.         // embed the font program
  1548.         $this->o_contents($this->numObj,'new');
  1549.         $this->objects[$pfbid]['c'].=$data;
  1550.         // determine the cruicial lengths within this file
  1551.         if ($fbtype=='pfb'){
  1552.           $l1 = strpos($data,'eexec')+6;
  1553.           $l2 = strpos($data,'00000000')-$l1;
  1554.           $l3 = strlen($data)-$l2-$l1;
  1555.           $this->o_contents($this->numObj,'add',array('Length1'=>$l1,'Length2'=>$l2,'Length3'=>$l3));
  1556.         } else if ($fbtype=='ttf'){
  1557.           $l1 = strlen($data);
  1558.           $this->o_contents($this->numObj,'add',array('Length1'=>$l1));
  1559.         }
  1560.  
  1561.  
  1562.         // tell the font object about all this new stuff
  1563.         $tmp = array('BaseFont'=>$adobeFontName,'Widths'=>$widthid
  1564.                                       ,'FirstChar'=>$firstChar,'LastChar'=>$lastChar
  1565.                                       ,'FontDescriptor'=>$fontDescriptorId);
  1566.         if ($fbtype=='ttf'){
  1567.           $tmp['SubType']='TrueType';
  1568.         }
  1569.         $this->addMessage('adding extra info to font.('.$fontObj.')');
  1570.         foreach($tmp as $fk=>$fv){
  1571.           $this->addMessage($fk." : ".$fv);
  1572.         }
  1573.         $this->o_font($fontObj,'add',$tmp);
  1574.  
  1575.       } else {
  1576.         $this->addMessage('selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts');
  1577.       }
  1578.  
  1579.  
  1580.       // also set the differences here, note that this means that these will take effect only the 
  1581.       //first time that a font is selected, else they are ignored
  1582.       if (isset($options['differences'])){
  1583.         $this->fonts[$fontName]['differences']=$options['differences'];
  1584.       }
  1585.     }
  1586.   }
  1587.   if ($set && isset($this->fonts[$fontName])){
  1588.     // so if for some reason the font was not set in the last one then it will not be selected
  1589.     $this->currentBaseFont=$fontName;
  1590.     // the next line means that if a new font is selected, then the current text state will be
  1591.     // applied to it as well.
  1592.     $this->setCurrentFont();
  1593.   }
  1594.   return $this->currentFontNum;
  1595. }
  1596.  
  1597. /**
  1598. * sets up the current font, based on the font families, and the current text state
  1599. * note that this system is quite flexible, a <b><i> font can be completely different to a
  1600. * <i><b> font, and even <b><b> will have to be defined within the family to have meaning
  1601. * This function is to be called whenever the currentTextState is changed, it will update
  1602. * the currentFont setting to whatever the appropriatte family one is.
  1603. * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
  1604. * This function will change the currentFont to whatever it should be, but will not change the 
  1605. * currentBaseFont.
  1606. *
  1607. * @access private
  1608. */
  1609. function setCurrentFont(){
  1610.   if (strlen($this->currentBaseFont)==0){
  1611.     // then assume an initial font
  1612.     $this->selectFont('./fonts/Helvetica.afm');
  1613.   }
  1614.   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
  1615.   if (strlen($this->currentTextState)
  1616.     && isset($this->fontFamilies[$cf]) 
  1617.       && isset($this->fontFamilies[$cf][$this->currentTextState])){
  1618.     // then we are in some state or another
  1619.     // and this font has a family, and the current setting exists within it
  1620.     // select the font, then return it
  1621.     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
  1622.     $this->selectFont($nf,'',0);
  1623.     $this->currentFont = $nf;
  1624.     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
  1625.   } else {
  1626.     // the this font must not have the right family member for the current state
  1627.     // simply assume the base font
  1628.     $this->currentFont = $this->currentBaseFont;
  1629.     $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];    
  1630.   }
  1631. }
  1632.  
  1633. /**
  1634. * function for the user to find out what the ID is of the first page that was created during
  1635. * startup - useful if they wish to add something to it later.
  1636. */
  1637. function getFirstPageId(){
  1638.   return $this->firstPageId;
  1639. }
  1640.  
  1641. /**
  1642. * add content to the currently active object
  1643. *
  1644. * @access private
  1645. */
  1646. function addContent($content){
  1647.   $this->objects[$this->currentContents]['c'].=$content;
  1648. }
  1649.  
  1650. /**
  1651. * sets the colour for fill operations
  1652. */
  1653. function setColor($r,$g,$b,$force=0){
  1654.   if ($r>=0 && ($force || $r!=$this->currentColour['r'] || $g!=$this->currentColour['g'] || $b!=$this->currentColour['b'])){
  1655.     $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$r).' '.sprintf('%.3f',$g).' '.sprintf('%.3f',$b).' rg';
  1656.     $this->currentColour=array('r'=>$r,'g'=>$g,'b'=>$b);
  1657.   }
  1658. }
  1659.  
  1660. /**
  1661. * sets the colour for stroke operations
  1662. */
  1663. function setStrokeColor($r,$g,$b,$force=0){
  1664.   if ($r>=0 && ($force || $r!=$this->currentStrokeColour['r'] || $g!=$this->currentStrokeColour['g'] || $b!=$this->currentStrokeColour['b'])){
  1665.     $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$r).' '.sprintf('%.3f',$g).' '.sprintf('%.3f',$b).' RG';
  1666.     $this->currentStrokeColour=array('r'=>$r,'g'=>$g,'b'=>$b);
  1667.   }
  1668. }
  1669.  
  1670. /**
  1671. * draw a line from one set of coordinates to another
  1672. */
  1673. function line($x1,$y1,$x2,$y2){
  1674.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$x1).' '.sprintf('%.3f',$y1).' m '.sprintf('%.3f',$x2).' '.sprintf('%.3f',$y2).' l S';
  1675. }
  1676.  
  1677. /**
  1678. * draw a bezier curve based on 4 control points
  1679. */
  1680. function curve($x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3){
  1681.   // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
  1682.   // as the control points for the curve.
  1683.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$x0).' '.sprintf('%.3f',$y0).' m '.sprintf('%.3f',$x1).' '.sprintf('%.3f',$y1);
  1684.   $this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3f',$x2).' '.sprintf('%.3f',$y2).' '.sprintf('%.3f',$x3).' '.sprintf('%.3f',$y3).' c S';
  1685. }
  1686.  
  1687. /**
  1688. * draw a part of an ellipse
  1689. */
  1690. function partEllipse($x0,$y0,$astart,$afinish,$r1,$r2=0,$angle=0,$nSeg=8){
  1691.   $this->ellipse($x0,$y0,$r1,$r2,$angle,$nSeg,$astart,$afinish,0);
  1692. }
  1693.  
  1694. /**
  1695. * draw a filled ellipse
  1696. */
  1697. function filledEllipse($x0,$y0,$r1,$r2=0,$angle=0,$nSeg=8,$astart=0,$afinish=360){
  1698.   return $this->ellipse($x0,$y0,$r1,$r2=0,$angle,$nSeg,$astart,$afinish,1,1);
  1699. }
  1700.  
  1701. /**
  1702. * draw an ellipse
  1703. * note that the part and filled ellipse are just special cases of this function
  1704. *
  1705. * draws an ellipse in the current line style
  1706. * centered at $x0,$y0, radii $r1,$r2
  1707. * if $r2 is not set, then a circle is drawn
  1708. * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a 
  1709. * pretty crappy shape at 2, as we are approximating with bezier curves.
  1710. */
  1711. function ellipse($x0,$y0,$r1,$r2=0,$angle=0,$nSeg=8,$astart=0,$afinish=360,$close=1,$fill=0){
  1712.   if ($r1==0){
  1713.     return;
  1714.   }
  1715.   if ($r2==0){
  1716.     $r2=$r1;
  1717.   }
  1718.   if ($nSeg<2){
  1719.     $nSeg=2;
  1720.   }
  1721.  
  1722.   $astart = deg2rad((float)$astart);
  1723.   $afinish = deg2rad((float)$afinish);
  1724.   $totalAngle =$afinish-$astart;
  1725.  
  1726.   $dt = $totalAngle/$nSeg;
  1727.   $dtm = $dt/3;
  1728.  
  1729.   if ($angle != 0){
  1730.     $a = -1*deg2rad((float)$angle);
  1731.     $tmp = "\n q ";
  1732.     $tmp .= sprintf('%.3f',cos($a)).' '.sprintf('%.3f',(-1.0*sin($a))).' '.sprintf('%.3f',sin($a)).' '.sprintf('%.3f',cos($a)).' ';
  1733.     $tmp .= sprintf('%.3f',$x0).' '.sprintf('%.3f',$y0).' cm';
  1734.     $this->objects[$this->currentContents]['c'].= $tmp;
  1735.     $x0=0;
  1736.     $y0=0;
  1737.   }
  1738.  
  1739.   $t1 = $astart;
  1740.   $a0 = $x0+$r1*cos($t1);
  1741.   $b0 = $y0+$r2*sin($t1);
  1742.   $c0 = -$r1*sin($t1);
  1743.   $d0 = $r2*cos($t1);
  1744.  
  1745.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$a0).' '.sprintf('%.3f',$b0).' m ';
  1746.   for ($i=1;$i<=$nSeg;$i++){
  1747.     // draw this bit of the total curve
  1748.     $t1 = $i*$dt+$astart;
  1749.     $a1 = $x0+$r1*cos($t1);
  1750.     $b1 = $y0+$r2*sin($t1);
  1751.     $c1 = -$r1*sin($t1);
  1752.     $d1 = $r2*cos($t1);
  1753.     $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',($a0+$c0*$dtm)).' '.sprintf('%.3f',($b0+$d0*$dtm));
  1754.     $this->objects[$this->currentContents]['c'].= ' '.sprintf('%.3f',($a1-$c1*$dtm)).' '.sprintf('%.3f',($b1-$d1*$dtm)).' '.sprintf('%.3f',$a1).' '.sprintf('%.3f',$b1).' c';
  1755.     $a0=$a1;
  1756.     $b0=$b1;
  1757.     $c0=$c1;
  1758.     $d0=$d1;    
  1759.   }
  1760.   if ($fill){
  1761.     $this->objects[$this->currentContents]['c'].=' f';
  1762.   } else {
  1763.     if ($close){
  1764.       $this->objects[$this->currentContents]['c'].=' s'; // small 's' signifies closing the path as well
  1765.     } else {
  1766.       $this->objects[$this->currentContents]['c'].=' S';
  1767.     }
  1768.   }
  1769.   if ($angle !=0){
  1770.     $this->objects[$this->currentContents]['c'].=' Q';
  1771.   }
  1772. }
  1773.  
  1774. /**
  1775. * this sets the line drawing style.
  1776. * width, is the thickness of the line in user units
  1777. * cap is the type of cap to put on the line, values can be 'butt','round','square'
  1778. *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
  1779. *    end of the line.
  1780. * join can be 'miter', 'round', 'bevel'
  1781. * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
  1782. *   on and off dashes.
  1783. *   (2) represents 2 on, 2 off, 2 on , 2 off ...
  1784. *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
  1785. * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. 
  1786. */
  1787. function setLineStyle($width=1,$cap='',$join='',$dash='',$phase=0){
  1788.  
  1789.   // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
  1790.   $string = '';
  1791.   if ($width>0){
  1792.     $string.= $width.' w';
  1793.   }
  1794.   $ca = array('butt'=>0,'round'=>1,'square'=>2);
  1795.   if (isset($ca[$cap])){
  1796.     $string.= ' '.$ca[$cap].' J';
  1797.   }
  1798.   $ja = array('miter'=>0,'round'=>1,'bevel'=>2);
  1799.   if (isset($ja[$join])){
  1800.     $string.= ' '.$ja[$join].' j';
  1801.   }
  1802.   if (is_array($dash)){
  1803.     $string.= ' [';
  1804.     foreach ($dash as $len){
  1805.       $string.=' '.$len;
  1806.     }
  1807.     $string.= ' ] '.$phase.' d';
  1808.   }
  1809.   $this->currentLineStyle = $string;
  1810.   $this->objects[$this->currentContents]['c'].="\n".$string;
  1811. }
  1812.  
  1813. /**
  1814. * draw a polygon, the syntax for this is similar to the GD polygon command
  1815. */
  1816. function polygon($p,$np,$f=0){
  1817.   $this->objects[$this->currentContents]['c'].="\n";
  1818.   $this->objects[$this->currentContents]['c'].=sprintf('%.3f',$p[0]).' '.sprintf('%.3f',$p[1]).' m ';
  1819.   for ($i=2;$i<$np*2;$i=$i+2){
  1820.     $this->objects[$this->currentContents]['c'].= sprintf('%.3f',$p[$i]).' '.sprintf('%.3f',$p[$i+1]).' l ';
  1821.   }
  1822.   if ($f==1){
  1823.     $this->objects[$this->currentContents]['c'].=' f';
  1824.   } else {
  1825.     $this->objects[$this->currentContents]['c'].=' S';
  1826.   }
  1827. }
  1828.  
  1829. /**
  1830. * a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
  1831. * the coordinates of the upper-right corner
  1832. */
  1833. function filledRectangle($x1,$y1,$width,$height){
  1834.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$x1).' '.sprintf('%.3f',$y1).' '.sprintf('%.3f',$width).' '.sprintf('%.3f',$height).' re f';
  1835. }
  1836.  
  1837. /**
  1838. * draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
  1839. * the coordinates of the upper-right corner
  1840. */
  1841. function rectangle($x1,$y1,$width,$height){
  1842.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$x1).' '.sprintf('%.3f',$y1).' '.sprintf('%.3f',$width).' '.sprintf('%.3f',$height).' re S';
  1843. }
  1844.  
  1845. /**
  1846. * add a new page to the document
  1847. * this also makes the new page the current active object
  1848. */
  1849. function newPage($insert=0,$id=0,$pos='after'){
  1850.  
  1851.   // if there is a state saved, then go up the stack closing them
  1852.   // then on the new page, re-open them with the right setings
  1853.   
  1854.   if ($this->nStateStack){
  1855.     for ($i=$this->nStateStack;$i>=1;$i--){
  1856.       $this->restoreState($i);
  1857.     }
  1858.   }
  1859.  
  1860.   $this->numObj++;
  1861.   if ($insert){
  1862.     // the id from the ezPdf class is the od of the contents of the page, not the page object itself
  1863.     // query that object to find the parent
  1864.     $rid = $this->objects[$id]['onPage'];
  1865.     $opt= array('rid'=>$rid,'pos'=>$pos);
  1866.     $this->o_page($this->numObj,'new',$opt);
  1867.   } else {
  1868.     $this->o_page($this->numObj,'new');
  1869.   }
  1870.   // if there is a stack saved, then put that onto the page
  1871.   if ($this->nStateStack){
  1872.     for ($i=1;$i<=$this->nStateStack;$i++){
  1873.       $this->saveState($i);
  1874.     }
  1875.   }  
  1876.   // and if there has been a stroke or fill colour set, then transfer them
  1877.   if ($this->currentColour['r']>=0){
  1878.     $this->setColor($this->currentColour['r'],$this->currentColour['g'],$this->currentColour['b'],1);
  1879.   }
  1880.   if ($this->currentStrokeColour['r']>=0){
  1881.     $this->setStrokeColor($this->currentStrokeColour['r'],$this->currentStrokeColour['g'],$this->currentStrokeColour['b'],1);
  1882.   }
  1883.  
  1884.   // if there is a line style set, then put this in too
  1885.   if (strlen($this->currentLineStyle)){
  1886.     $this->objects[$this->currentContents]['c'].="\n".$this->currentLineStyle;
  1887.   }
  1888.  
  1889.   // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
  1890.   return $this->currentContents;
  1891. }
  1892.  
  1893. /**
  1894. * output the pdf code, streaming it to the browser
  1895. * the relevant headers are set so that hopefully the browser will recognise it
  1896. */
  1897. function stream($options=''){
  1898.   // setting the options allows the adjustment of the headers
  1899.   // values at the moment are:
  1900.   // 'Content-Disposition'=>'filename'  - sets the filename, though not too sure how well this will 
  1901.   //        work as in my trial the browser seems to use the filename of the php file with .pdf on the end
  1902.   // 'Accept-Ranges'=>1 or 0 - if this is not set to 1, then this header is not included, off by default
  1903.   //    this header seems to have caused some problems despite tha fact that it is supposed to solve
  1904.   //    them, so I am leaving it off by default.
  1905.   // 'compress'=> 1 or 0 - apply content stream compression, this is on (1) by default
  1906.   if (!is_array($options)){
  1907.     $options=array();
  1908.   }
  1909.   if ( isset($options['compress']) && $options['compress']==0){
  1910.     $tmp = $this->output(1);
  1911.   } else {
  1912.     $tmp = $this->output();
  1913.   }
  1914.   header("Content-type: application/pdf");
  1915.   header("Content-Length: ".strlen(ltrim($tmp)));
  1916.   $fileName = (isset($options['Content-Disposition'])?$options['Content-Disposition']:'file.pdf');
  1917.   header("Content-Disposition: inline; filename=".$fileName);
  1918.   if (isset($options['Accept-Ranges']) && $options['Accept-Ranges']==1){
  1919.     header("Accept-Ranges: ".strlen(ltrim($tmp))); 
  1920.   }
  1921.   echo ltrim($tmp);
  1922. }
  1923.  
  1924. /**
  1925. * return the height in units of the current font in the given size
  1926. */
  1927. function getFontHeight($size){
  1928.   if (!$this->numFonts){
  1929.     $this->selectFont('./fonts/Helvetica');
  1930.   }
  1931.   // for the current font, and the given size, what is the height of the font in user units
  1932.   $h = $this->fonts[$this->currentFont]['FontBBox'][3]-$this->fonts[$this->currentFont]['FontBBox'][1];
  1933.   return $size*$h/1000;
  1934. }
  1935.  
  1936. /**
  1937. * return the font decender, this will normally return a negative number
  1938. * if you add this number to the baseline, you get the level of the bottom of the font
  1939. * it is in the pdf user units
  1940. */
  1941. function getFontDecender($size){
  1942.   // note that this will most likely return a negative value
  1943.   if (!$this->numFonts){
  1944.     $this->selectFont('./fonts/Helvetica');
  1945.   }
  1946.   $h = $this->fonts[$this->currentFont]['FontBBox'][1];
  1947.   return $size*$h/1000;
  1948. }
  1949.  
  1950. /**
  1951. * filter the text, this is applied to all text just before being inserted into the pdf document
  1952. * it escapes the various things that need to be escaped, and so on
  1953. *
  1954. * @access private
  1955. */
  1956. function filterText($text){
  1957.   $text = str_replace('\\','\\\\',$text);
  1958.   $text = str_replace('(','\(',$text);
  1959.   $text = str_replace(')','\)',$text);
  1960.   $text = str_replace('<','<',$text);
  1961.   $text = str_replace('>','>',$text);
  1962.   $text = str_replace(''','\'',$text);
  1963.   $text = str_replace('"','"',$text);
  1964.   $text = str_replace('&','&',$text);
  1965.  
  1966.   return $text;
  1967. }
  1968.  
  1969. /**
  1970. * given a start position and information about how text is to be laid out, calculate where 
  1971. * on the page the text will end
  1972. *
  1973. * @access private
  1974. */
  1975. function PRVTgetTextPosition($x,$y,$angle,$size,$wa,$text){
  1976.   // given this information return an array containing x and y for the end position as elements 0 and 1
  1977.   $w = $this->getTextWidth($size,$text);
  1978.   // need to adjust for the number of spaces in this text
  1979.   $words = explode(' ',$text);
  1980.   $nspaces=count($words)-1;
  1981.   $w += $wa*$nspaces;
  1982.   $a = deg2rad((float)$angle);
  1983.   return array(cos($a)*$w+$x,-sin($a)*$w+$y);
  1984. }
  1985.  
  1986. /**
  1987. * wrapper function for PRVTcheckTextDirective1
  1988. *
  1989. * @access private
  1990. */
  1991. function PRVTcheckTextDirective(&$text,$i,&$f){
  1992.   $x=0;
  1993.   $y=0;
  1994.   return $this->PRVTcheckTextDirective1($text,$i,$f,0,$x,$y);
  1995. }
  1996.  
  1997. /**
  1998. * checks if the text stream contains a control directive
  1999. * if so then makes some changes and returns the number of characters involved in the directive
  2000. * this has been re-worked to include everything neccesary to fins the current writing point, so that
  2001. * the location can be sent to the callback function if required
  2002. * if the directive does not require a font change, then $f should be set to 0
  2003. *
  2004. * @access private
  2005. */
  2006. function PRVTcheckTextDirective1(&$text,$i,&$f,$final,&$x,&$y,$size=0,$angle=0,$wordSpaceAdjust=0){
  2007.   $directive = 0;
  2008.   $j=$i;
  2009.   if ($text[$j]=='<'){
  2010.     $j++;
  2011.     switch($text[$j]){
  2012.       case '/':
  2013.         $j++;
  2014.         if (strlen($text) <= $j){
  2015.           return $directive;
  2016.         }
  2017.         switch($text[$j]){
  2018.           case 'b':
  2019.           case 'i':
  2020.             $j++;
  2021.             if ($text[$j]=='>'){
  2022.               $p = strrpos($this->currentTextState,$text[$j-1]);
  2023.               if ($p !== false){
  2024.                 // then there is one to remove
  2025.                 $this->currentTextState = substr($this->currentTextState,0,$p).substr($this->currentTextState,$p+1);
  2026.               }
  2027.               $directive=$j-$i+1;
  2028.             }
  2029.             break;
  2030.           case 'c':
  2031.             // this this might be a callback function
  2032.             $j++;
  2033.             $k = strpos($text,'>',$j);
  2034.             if ($k!==false && $text[$j]==':'){
  2035.               // then this will be treated as a callback directive
  2036.               $directive = $k-$i+1;
  2037.               $f=0;
  2038.               // split the remainder on colons to get the function name and the paramater
  2039.               $tmp = substr($text,$j+1,$k-$j-1);
  2040.               $b1 = strpos($tmp,':');
  2041.               if ($b1!==false){
  2042.                 $func = substr($tmp,0,$b1);
  2043.                 $parm = substr($tmp,$b1+1);
  2044.               } else {
  2045.                 $func=$tmp;
  2046.                 $parm='';
  2047.               }
  2048.               if (!isset($func) || !strlen(trim($func))){
  2049.                 $directive=0;
  2050.               } else {
  2051.                 // only call the function if this is the final call
  2052.                 if ($final){
  2053.                   // need to assess the text position, calculate the text width to this point
  2054.                   // can use getTextWidth to find the text width I think
  2055.                   $tmp = $this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,substr($text,0,$i));
  2056.                   $info = array('x'=>$tmp[0],'y'=>$tmp[1],'angle'=>$angle,'status'=>'end','p'=>$parm,'nCallback'=>$this->nCallback);
  2057.                   $x=$tmp[0];
  2058.                   $y=$tmp[1];
  2059.                   $ret = $this->$func($info);
  2060.                   if (is_array($ret)){
  2061.                     // then the return from the callback function could set the position, to start with, later will do font colour, and font
  2062.                     foreach($ret as $rk=>$rv){
  2063.                       switch($rk){
  2064.                         case 'x':
  2065.                         case 'y':
  2066.                           $$rk=$rv;
  2067.                           break;
  2068.                       }
  2069.                     }
  2070.                   }
  2071.                   // also remove from to the stack
  2072.                   // for simplicity, just take from the end, fix this another day
  2073.                   $this->nCallback--;
  2074.                   if ($this->nCallback<0){
  2075.                     $this->nCallBack=0;
  2076.                   }
  2077.                 }
  2078.               }
  2079.             }
  2080.             break;
  2081.         }
  2082.         break;
  2083.       case 'b':
  2084.       case 'i':
  2085.         $j++;
  2086.         if ($text[$j]=='>'){
  2087.           $this->currentTextState.=$text[$j-1];
  2088.           $directive=$j-$i+1;
  2089.         }
  2090.         break;
  2091.       case 'C':
  2092.         $noClose=1;
  2093.       case 'c':
  2094.         // this this might be a callback function
  2095.         $j++;
  2096.         $k = strpos($text,'>',$j);
  2097.         if ($k!==false && $text[$j]==':'){
  2098.           // then this will be treated as a callback directive
  2099.           $directive = $k-$i+1;
  2100.           $f=0;
  2101.           // split the remainder on colons to get the function name and the paramater
  2102. //          $bits = explode(':',substr($text,$j+1,$k-$j-1));
  2103.           $tmp = substr($text,$j+1,$k-$j-1);
  2104.           $b1 = strpos($tmp,':');
  2105.           if ($b1!==false){
  2106.             $func = substr($tmp,0,$b1);
  2107.             $parm = substr($tmp,$b1+1);
  2108.           } else {
  2109.             $func=$tmp;
  2110.             $parm='';
  2111.           }
  2112.           if (!isset($func) || !strlen(trim($func))){
  2113.             $directive=0;
  2114.           } else {
  2115.             // only call the function if this is the final call, ie, the one actually doing printing, not measurement
  2116.             if ($final){
  2117.               // need to assess the text position, calculate the text width to this point
  2118.               // can use getTextWidth to find the text width I think
  2119.               // also add the text height and decender
  2120.               $tmp = $this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,substr($text,0,$i));
  2121.               $info = array('x'=>$tmp[0],'y'=>$tmp[1],'angle'=>$angle,'status'=>'start','p'=>$parm,'f'=>$func,'height'=>$this->getFontHeight($size),'decender'=>$this->getFontDecender($size));
  2122.               $x=$tmp[0];
  2123.               $y=$tmp[1];
  2124.               if (!isset($noClose) || !$noClose){
  2125.                 // only add to the stack if this is a small 'c', therefore is a start-stop pair
  2126.                 $this->nCallback++;
  2127.                 $info['nCallback']=$this->nCallback;
  2128.                 $this->callback[$this->nCallback]=$info;
  2129.               }
  2130.               $ret = $this->$func($info);
  2131.               if (is_array($ret)){
  2132.                 // then the return from the callback function could set the position, to start with, later will do font colour, and font
  2133.                 foreach($ret as $rk=>$rv){
  2134.                   switch($rk){
  2135.                     case 'x':
  2136.                     case 'y':
  2137.                       $$rk=$rv;
  2138.                       break;
  2139.                   }
  2140.                 }
  2141.               }
  2142.             }
  2143.           }
  2144.         }
  2145.         break;
  2146.     }
  2147.   } 
  2148.   return $directive;
  2149. }
  2150.  
  2151. /**
  2152. * add text to the document, at a specified location, size and angle on the page
  2153. */
  2154. function addText($x,$y,$size,$text,$angle=0,$wordSpaceAdjust=0){
  2155.   if (!$this->numFonts){$this->selectFont('./fonts/Helvetica');}
  2156.  
  2157.   // if there are any open callbacks, then they should be called, to show the start of the line
  2158.   if ($this->nCallback>0){
  2159.     for ($i=$this->nCallback;$i>0;$i--){
  2160.       // call each function
  2161.       $info = array('x'=>$x,'y'=>$y,'angle'=>$angle,'status'=>'sol','p'=>$this->callback[$i]['p'],'nCallback'=>$this->callback[$i]['nCallback'],'height'=>$this->callback[$i]['height'],'decender'=>$this->callback[$i]['decender']);
  2162.       $func = $this->callback[$i]['f'];
  2163.       $this->$func($info);
  2164.     }
  2165.   }
  2166.   if ($angle==0){
  2167.     $this->objects[$this->currentContents]['c'].="\n".'BT '.sprintf('%.3f',$x).' '.sprintf('%.3f',$y).' Td';
  2168.   } else {
  2169.     $a = deg2rad((float)$angle);
  2170.     $tmp = "\n".'BT ';
  2171.     $tmp .= sprintf('%.3f',cos($a)).' '.sprintf('%.3f',(-1.0*sin($a))).' '.sprintf('%.3f',sin($a)).' '.sprintf('%.3f',cos($a)).' ';
  2172.     $tmp .= sprintf('%.3f',$x).' '.sprintf('%.3f',$y).' Tm';
  2173.     $this->objects[$this->currentContents]['c'] .= $tmp;
  2174.   }
  2175.   if ($wordSpaceAdjust!=0 || $wordSpaceAdjust != $this->wordSpaceAdjust){
  2176.     $this->wordSpaceAdjust=$wordSpaceAdjust;
  2177.     $this->objects[$this->currentContents]['c'].=' '.sprintf('%.3f',$wordSpaceAdjust).' Tw';
  2178.   }
  2179.   $len=strlen($text);
  2180.   $start=0;
  2181.   for ($i=0;$i<$len;$i++){
  2182.     $f=1;
  2183.     $directive = $this->PRVTcheckTextDirective($text,$i,$f);
  2184.     if ($directive){
  2185.       // then we should write what we need to
  2186.       if ($i>$start){
  2187.         $part = substr($text,$start,$i-$start);
  2188.         $this->objects[$this->currentContents]['c'].=' /F'.$this->currentFontNum.' '.sprintf('%.1f',$size).' Tf ';
  2189.         $this->objects[$this->currentContents]['c'].=' ('.$this->filterText($part).') Tj';
  2190.       }
  2191.       if ($f){
  2192.         // then there was nothing drastic done here, restore the contents
  2193.         $this->setCurrentFont();
  2194.       } else {
  2195.         $this->objects[$this->currentContents]['c'] .= ' ET';
  2196.         $f=1;
  2197.         $xp=$x;
  2198.         $yp=$y;
  2199.         $directive = $this->PRVTcheckTextDirective1($text,$i,$f,1,$xp,$yp,$size,$angle,$wordSpaceAdjust);
  2200.         
  2201.         // restart the text object
  2202.           if ($angle==0){
  2203.             $this->objects[$this->currentContents]['c'].="\n".'BT '.sprintf('%.3f',$xp).' '.sprintf('%.3f',$yp).' Td';
  2204.           } else {
  2205.             $a = deg2rad((float)$angle);
  2206.             $tmp = "\n".'BT ';
  2207.             $tmp .= sprintf('%.3f',cos($a)).' '.sprintf('%.3f',(-1.0*sin($a))).' '.sprintf('%.3f',sin($a)).' '.sprintf('%.3f',cos($a)).' ';
  2208.             $tmp .= sprintf('%.3f',$xp).' '.sprintf('%.3f',$yp).' Tm';
  2209.             $this->objects[$this->currentContents]['c'] .= $tmp;
  2210.           }
  2211.           if ($wordSpaceAdjust!=0 || $wordSpaceAdjust != $this->wordSpaceAdjust){
  2212.             $this->wordSpaceAdjust=$wordSpaceAdjust;
  2213.             $this->objects[$this->currentContents]['c'].=' '.sprintf('%.3f',$wordSpaceAdjust).' Tw';
  2214.           }
  2215.       }
  2216.       // and move the writing point to the next piece of text
  2217.       $i=$i+$directive-1;
  2218.       $start=$i+1;
  2219.     }
  2220.     
  2221.   }
  2222.   if ($start<$len){
  2223.     $part = substr($text,$start);
  2224.     $this->objects[$this->currentContents]['c'].=' /F'.$this->currentFontNum.' '.sprintf('%.1f',$size).' Tf ';
  2225.     $this->objects[$this->currentContents]['c'].=' ('.$this->filterText($part).') Tj';
  2226.   }
  2227.   $this->objects[$this->currentContents]['c'].=' ET';
  2228.  
  2229.   // if there are any open callbacks, then they should be called, to show the end of the line
  2230.   if ($this->nCallback>0){
  2231.     for ($i=$this->nCallback;$i>0;$i--){
  2232.       // call each function
  2233.       $tmp = $this->PRVTgetTextPosition($x,$y,$angle,$size,$wordSpaceAdjust,$text);
  2234.       $info = array('x'=>$tmp[0],'y'=>$tmp[1],'angle'=>$angle,'status'=>'eol','p'=>$this->callback[$i]['p'],'nCallback'=>$this->callback[$i]['nCallback'],'height'=>$this->callback[$i]['height'],'decender'=>$this->callback[$i]['decender']);
  2235.       $func = $this->callback[$i]['f'];
  2236.       $this->$func($info);
  2237.     }
  2238.   }
  2239.  
  2240. }
  2241.  
  2242. /**
  2243. * calculate how wide a given text string will be on a page, at a given size.
  2244. * this can be called externally, but is alse used by the other class functions
  2245. */
  2246. function getTextWidth($size,$text){
  2247.   // this function should not change any of the settings, though it will need to
  2248.   // track any directives which change during calculation, so copy them at the start
  2249.   // and put them back at the end.
  2250.   $store_currentTextState = $this->currentTextState;
  2251.  
  2252.   if (!$this->numFonts){
  2253.     $this->selectFont('./fonts/Helvetica');
  2254.   }
  2255.  
  2256.   // converts a number or a float to a string so it can get the width
  2257.   $text = "$text";
  2258.  
  2259.   // hmm, this is where it all starts to get tricky - use the font information to
  2260.   // calculate the width of each character, add them up and convert to user units
  2261.   $w=0;
  2262.   $len=strlen($text);
  2263.   $cf = $this->currentFont;
  2264.   for ($i=0;$i<$len;$i++){
  2265.     $f=1;
  2266.     $directive = $this->PRVTcheckTextDirective($text,$i,$f);
  2267.     if ($directive){
  2268.       if ($f){
  2269.         $this->setCurrentFont();
  2270.         $cf = $this->currentFont;
  2271.       }
  2272.       $i=$i+$directive-1;
  2273.     } else {
  2274.       $char=ord($text[$i]);
  2275.       if (isset($this->fonts[$cf]['differences'][$char])){
  2276.         // then this character is being replaced by another
  2277.         $name = $this->fonts[$cf]['differences'][$char];
  2278.         if (isset($this->fonts[$cf]['C'][$name]['WX'])){
  2279.           $w+=$this->fonts[$cf]['C'][$name]['WX'];
  2280.         }
  2281.       } else if (isset($this->fonts[$cf]['C'][$char]['WX'])){
  2282.         $w+=$this->fonts[$cf]['C'][$char]['WX'];
  2283.       }
  2284.     }
  2285.   }
  2286.   
  2287.   $this->currentTextState = $store_currentTextState;
  2288.   $this->setCurrentFont();
  2289.  
  2290.   return $w*$size/1000;
  2291. }
  2292.  
  2293. /**
  2294. * do a part of the calculation for sorting out the justification of the text
  2295. *
  2296. * @access private
  2297. */
  2298. function PRVTadjustWrapText($text,$actual,$width,&$x,&$adjust,$justification){
  2299.   switch ($justification){
  2300.     case 'left':
  2301.       return;
  2302.       break;
  2303.     case 'right':
  2304.       $x+=$width-$actual;
  2305.       break;
  2306.     case 'center':
  2307.     case 'centre':
  2308.       $x+=($width-$actual)/2;
  2309.       break;
  2310.     case 'full':
  2311.       // count the number of words
  2312.       $words = explode(' ',$text);
  2313.       $nspaces=count($words)-1;
  2314.       if ($nspaces>0){
  2315.         $adjust = ($width-$actual)/$nspaces;
  2316.       } else {
  2317.         $adjust=0;
  2318.       }
  2319.       break;
  2320.   }
  2321. }
  2322.  
  2323. /**
  2324. * add text to the page, but ensure that it fits within a certain width
  2325. * if it does not fit then put in as much as possible, splitting at word boundaries
  2326. * and return the remainder.
  2327. * justification and angle can also be specified for the text
  2328. */
  2329. function addTextWrap($x,$y,$width,$size,$text,$justification='left',$angle=0,$test=0){
  2330.   // this will display the text, and if it goes beyond the width $width, will backtrack to the 
  2331.   // previous space or hyphen, and return the remainder of the text.
  2332.  
  2333.   // $justification can be set to 'left','right','center','centre','full'
  2334.  
  2335.   // need to store the initial text state, as this will change during the width calculation
  2336.   // but will need to be re-set before printing, so that the chars work out right
  2337.   $store_currentTextState = $this->currentTextState;
  2338.  
  2339.   if (!$this->numFonts){$this->selectFont('./fonts/Helvetica');}
  2340.   if ($width<=0){
  2341.     // error, pretend it printed ok, otherwise risking a loop
  2342.     return '';
  2343.   }
  2344.   $w=0;
  2345.   $break=0;
  2346.   $breakWidth=0;
  2347.   $len=strlen($text);
  2348.   $cf = $this->currentFont;
  2349.   $tw = $width/$size*1000;
  2350.   for ($i=0;$i<$len;$i++){
  2351.     $f=1;
  2352.     $directive = $this->PRVTcheckTextDirective($text,$i,$f);
  2353.     if ($directive){
  2354.       if ($f){
  2355.         $this->setCurrentFont();
  2356.         $cf = $this->currentFont;
  2357.       }
  2358.       $i=$i+$directive-1;
  2359.     } else {
  2360.       $cOrd = ord($text[$i]);
  2361.       if (isset($this->fonts[$cf]['differences'][$cOrd])){
  2362.         // then this character is being replaced by another
  2363.         $cOrd2 = $this->fonts[$cf]['differences'][$cOrd];
  2364.       } else {
  2365.         $cOrd2 = $cOrd;
  2366.       }
  2367.   
  2368.       if (isset($this->fonts[$cf]['C'][$cOrd2]['WX'])){
  2369.         $w+=$this->fonts[$cf]['C'][$cOrd2]['WX'];
  2370.       }
  2371.       if ($w>$tw){
  2372.         // then we need to truncate this line
  2373.         if ($break>0){
  2374.           // then we have somewhere that we can split :)
  2375.           if ($text[$break]==' '){
  2376.             $tmp = substr($text,0,$break);
  2377.           } else {
  2378.             $tmp = substr($text,0,$break+1);
  2379.           }
  2380.           $adjust=0;
  2381.           $this->PRVTadjustWrapText($tmp,$breakWidth,$width,$x,$adjust,$justification);
  2382.  
  2383.           // reset the text state
  2384.           $this->currentTextState = $store_currentTextState;
  2385.           $this->setCurrentFont();
  2386.           if (!$test){
  2387.             $this->addText($x,$y,$size,$tmp,$angle,$adjust);
  2388.           }
  2389.           return substr($text,$break+1);
  2390.         } else {
  2391.           // just split before the current character
  2392.           $tmp = substr($text,0,$i);
  2393.           $adjust=0;
  2394.           $ctmp=ord($text[$i]);
  2395.           if (isset($this->fonts[$cf]['differences'][$ctmp])){
  2396.             $ctmp=$this->fonts[$cf]['differences'][$ctmp];
  2397.           }
  2398.           $tmpw=($w-$this->fonts[$cf]['C'][$ctmp]['WX'])*$size/1000;
  2399.           $this->PRVTadjustWrapText($tmp,$tmpw,$width,$x,$adjust,$justification);
  2400.           // reset the text state
  2401.           $this->currentTextState = $store_currentTextState;
  2402.           $this->setCurrentFont();
  2403.           if (!$test){
  2404.             $this->addText($x,$y,$size,$tmp,$angle,$adjust);
  2405.           }
  2406.           return substr($text,$i);
  2407.         }
  2408.       }
  2409.       if ($text[$i]=='-'){
  2410.         $break=$i;
  2411.         $breakWidth = $w*$size/1000;
  2412.       }
  2413.       if ($text[$i]==' '){
  2414.         $break=$i;
  2415.         $ctmp=ord($text[$i]);
  2416.         if (isset($this->fonts[$cf]['differences'][$ctmp])){
  2417.           $ctmp=$this->fonts[$cf]['differences'][$ctmp];
  2418.         }
  2419.         $breakWidth = ($w-$this->fonts[$cf]['C'][$ctmp]['WX'])*$size/1000;
  2420.       }
  2421.     }
  2422.   }
  2423.   // then there was no need to break this line
  2424.   if ($justification=='full'){
  2425.     $justification='left';
  2426.   }
  2427.   $adjust=0;
  2428.   $tmpw=$w*$size/1000;
  2429.   $this->PRVTadjustWrapText($text,$tmpw,$width,$x,$adjust,$justification);
  2430.   // reset the text state
  2431.   $this->currentTextState = $store_currentTextState;
  2432.   $this->setCurrentFont();
  2433.   if (!$test){
  2434.     $this->addText($x,$y,$size,$text,$angle,$adjust,$angle);
  2435.   }
  2436.   return '';
  2437. }
  2438.  
  2439. /**
  2440. * this will be called at a new page to return the state to what it was on the 
  2441. * end of the previous page, before the stack was closed down
  2442. * This is to get around not being able to have open 'q' across pages
  2443. *
  2444. */
  2445. function saveState($pageEnd=0){
  2446.   if ($pageEnd){
  2447.     // this will be called at a new page to return the state to what it was on the 
  2448.     // end of the previous page, before the stack was closed down
  2449.     // This is to get around not being able to have open 'q' across pages
  2450.     $opt = $this->stateStack[$pageEnd]; // ok to use this as stack starts numbering at 1
  2451.     $this->setColor($opt['col']['r'],$opt['col']['g'],$opt['col']['b'],1);
  2452.     $this->setStrokeColor($opt['str']['r'],$opt['str']['g'],$opt['str']['b'],1);
  2453.     $this->objects[$this->currentContents]['c'].="\n".$opt['lin'];
  2454. //    $this->currentLineStyle = $opt['lin'];
  2455.   } else {
  2456.     $this->nStateStack++;
  2457.     $this->stateStack[$this->nStateStack]=array(
  2458.       'col'=>$this->currentColour
  2459.      ,'str'=>$this->currentStrokeColour
  2460.      ,'lin'=>$this->currentLineStyle
  2461.     );
  2462.   }
  2463.   $this->objects[$this->currentContents]['c'].="\nq";
  2464. }
  2465.  
  2466. /**
  2467. * restore a previously saved state
  2468. */
  2469. function restoreState($pageEnd=0){
  2470.   if (!$pageEnd){
  2471.     $n = $this->nStateStack;
  2472.     $this->currentColour = $this->stateStack[$n]['col'];
  2473.     $this->currentStrokeColour = $this->stateStack[$n]['str'];
  2474.     $this->objects[$this->currentContents]['c'].="\n".$this->stateStack[$n]['lin'];
  2475.     $this->currentLineStyle = $this->stateStack[$n]['lin'];
  2476.     unset($this->stateStack[$n]);
  2477.     $this->nStateStack--;
  2478.   }
  2479.   $this->objects[$this->currentContents]['c'].="\nQ";
  2480. }
  2481.  
  2482. /**
  2483. * make a loose object, the output will go into this object, until it is closed, then will revert to
  2484. * the current one.
  2485. * this object will not appear until it is included within a page.
  2486. * the function will return the object number
  2487. */
  2488. function openObject(){
  2489.   $this->nStack++;
  2490.   $this->stack[$this->nStack]=array('c'=>$this->currentContents,'p'=>$this->currentPage);
  2491.   // add a new object of the content type, to hold the data flow
  2492.   $this->numObj++;
  2493.   $this->o_contents($this->numObj,'new');
  2494.   $this->currentContents=$this->numObj;
  2495.   $this->looseObjects[$this->numObj]=1;
  2496.   
  2497.   return $this->numObj;
  2498. }
  2499.  
  2500. /**
  2501. * open an existing object for editing
  2502. */
  2503. function reopenObject($id){
  2504.    $this->nStack++;
  2505.    $this->stack[$this->nStack]=array('c'=>$this->currentContents,'p'=>$this->currentPage);
  2506.    $this->currentContents=$id;
  2507.    // also if this object is the primary contents for a page, then set the current page to its parent
  2508.    if (isset($this->objects[$id]['onPage'])){
  2509.      $this->currentPage = $this->objects[$id]['onPage'];
  2510.    }
  2511. }
  2512.  
  2513. /**
  2514. * close an object
  2515. */
  2516. function closeObject(){
  2517.   // close the object, as long as there was one open in the first place, which will be indicated by
  2518.   // an objectId on the stack.
  2519.   if ($this->nStack>0){
  2520.     $this->currentContents=$this->stack[$this->nStack]['c'];
  2521.     $this->currentPage=$this->stack[$this->nStack]['p'];
  2522.     $this->nStack--;
  2523.     // easier to probably not worry about removing the old entries, they will be overwritten
  2524.     // if there are new ones.
  2525.   }
  2526. }
  2527.  
  2528. /**
  2529. * stop an object from appearing on pages from this point on
  2530. */
  2531. function stopObject($id){
  2532.   // if an object has been appearing on pages up to now, then stop it, this page will
  2533.   // be the last one that could contian it.
  2534.   if (isset($this->addLooseObjects[$id])){
  2535.     $this->addLooseObjects[$id]='';
  2536.   }
  2537. }
  2538.  
  2539. /**
  2540. * after an object has been created, it wil only show if it has been added, using this function.
  2541. */
  2542. function addObject($id,$options='add'){
  2543.   // add the specified object to the page
  2544.   if (isset($this->looseObjects[$id]) && $this->currentContents!=$id){
  2545.     // then it is a valid object, and it is not being added to itself
  2546.     switch($options){
  2547.       case 'all':
  2548.         // then this object is to be added to this page (done in the next block) and 
  2549.         // all future new pages. 
  2550.         $this->addLooseObjects[$id]='all';
  2551.       case 'add':
  2552.         if (isset($this->objects[$this->currentContents]['onPage'])){
  2553.           // then the destination contents is the primary for the page
  2554.           // (though this object is actually added to that page)
  2555.           $this->o_page($this->objects[$this->currentContents]['onPage'],'content',$id);
  2556.         }
  2557.         break;
  2558.       case 'even':
  2559.         $this->addLooseObjects[$id]='even';
  2560.         $pageObjectId=$this->objects[$this->currentContents]['onPage'];
  2561.         if ($this->objects[$pageObjectId]['info']['pageNum']%2==0){
  2562.           $this->addObject($id); // hacky huh :)
  2563.         }
  2564.         break;
  2565.       case 'odd':
  2566.         $this->addLooseObjects[$id]='odd';
  2567.         $pageObjectId=$this->objects[$this->currentContents]['onPage'];
  2568.         if ($this->objects[$pageObjectId]['info']['pageNum']%2==1){
  2569.           $this->addObject($id); // hacky huh :)
  2570.         }
  2571.         break;
  2572.       case 'next':
  2573.         $this->addLooseObjects[$id]='all';
  2574.         break;
  2575.       case 'nexteven':
  2576.         $this->addLooseObjects[$id]='even';
  2577.         break;
  2578.       case 'nextodd':
  2579.         $this->addLooseObjects[$id]='odd';
  2580.         break;
  2581.     }
  2582.   }
  2583. }
  2584.  
  2585. /**
  2586. * add content to the documents info object
  2587. */
  2588. function addInfo($label,$value=0){
  2589.   // this will only work if the label is one of the valid ones.
  2590.   // modify this so that arrays can be passed as well.
  2591.   // if $label is an array then assume that it is key=>value pairs
  2592.   // else assume that they are both scalar, anything else will probably error
  2593.   if (is_array($label)){
  2594.     foreach ($label as $l=>$v){
  2595.       $this->o_info($this->infoObject,$l,$v);
  2596.     }
  2597.   } else {
  2598.     $this->o_info($this->infoObject,$label,$value);
  2599.   }
  2600. }
  2601.  
  2602. /**
  2603. * set the viewer preferences of the document, it is up to the browser to obey these.
  2604. */
  2605. function setPreferences($label,$value=0){
  2606.   // this will only work if the label is one of the valid ones.
  2607.   if (is_array($label)){
  2608.     foreach ($label as $l=>$v){
  2609.       $this->o_catalog($this->catalogId,'viewerPreferences',array($l=>$v));
  2610.     }
  2611.   } else {
  2612.     $this->o_catalog($this->catalogId,'viewerPreferences',array($label=>$value));
  2613.   }
  2614. }
  2615.  
  2616. /**
  2617. * extract an integer from a position in a byte stream
  2618. *
  2619. * @access private
  2620. */
  2621. function PRVT_getBytes(&$data,$pos,$num){
  2622.   // return the integer represented by $num bytes from $pos within $data
  2623.   $ret=0;
  2624.   for ($i=0;$i<$num;$i++){
  2625.     $ret=$ret*256;
  2626.     $ret+=ord($data[$pos+$i]);
  2627.   }
  2628.   return $ret;
  2629. }
  2630.  
  2631. /**
  2632. * add a PNG image into the document, from a file
  2633. * this should work with remote files
  2634. */
  2635. function addPngFromFile($file,$x,$y,$w=0,$h=0){
  2636.   // read in a png file, interpret it, then add to the system
  2637.   $error=0;
  2638.   $tmp = get_magic_quotes_runtime();
  2639.   set_magic_quotes_runtime(0);
  2640.   $fp = @fopen($file,'rb');
  2641.   if ($fp){
  2642.     $data='';
  2643.     while(!feof($fp)){
  2644.       $data .= fread($fp,1024);
  2645.     }
  2646.     fclose($fp);
  2647.   } else {
  2648.     $error = 1;
  2649.     $errormsg = 'trouble opening file: '.$file;
  2650.   }
  2651.   set_magic_quotes_runtime($tmp);
  2652.   
  2653.   if (!$error){
  2654.     $header = chr(137).chr(80).chr(78).chr(71).chr(13).chr(10).chr(26).chr(10);
  2655.     if (substr($data,0,8)!=$header){
  2656.       $error=1;
  2657.       $errormsg = 'this file does not have a valid header';
  2658.     }
  2659.   }
  2660.  
  2661.   if (!$error){
  2662.     // set pointer
  2663.     $p = 8;
  2664.     $len = strlen($data);
  2665.     // cycle through the file, identifying chunks
  2666.     $haveHeader=0;
  2667.     $info=array();
  2668.     $idata='';
  2669.     $pdata='';
  2670.     while ($p<$len){
  2671.       $chunkLen = $this->PRVT_getBytes($data,$p,4);
  2672.       $chunkType = substr($data,$p+4,4);
  2673. //      echo $chunkType.' - '.$chunkLen.'<br>';
  2674.     
  2675.       switch($chunkType){
  2676.         case 'IHDR':
  2677.           // this is where all the file information comes from
  2678.           $info['width']=$this->PRVT_getBytes($data,$p+8,4);
  2679.           $info['height']=$this->PRVT_getBytes($data,$p+12,4);
  2680.           $info['bitDepth']=ord($data[$p+16]);
  2681.           $info['colorType']=ord($data[$p+17]);
  2682.           $info['compressionMethod']=ord($data[$p+18]);
  2683.           $info['filterMethod']=ord($data[$p+19]);
  2684.           $info['interlaceMethod']=ord($data[$p+20]);
  2685. //print_r($info);
  2686.           $haveHeader=1;
  2687.           if ($info['compressionMethod']!=0){
  2688.             $error=1;
  2689.             $errormsg = 'unsupported compression method';
  2690.           }
  2691.           if ($info['filterMethod']!=0){
  2692.             $error=1;
  2693.             $errormsg = 'unsupported filter method';
  2694.           }
  2695.           break;
  2696.         case 'PLTE':
  2697.           $pdata.=substr($data,$p+8,$chunkLen);
  2698.           break;
  2699.         case 'IDAT':
  2700.           $idata.=substr($data,$p+8,$chunkLen);
  2701.           break;
  2702.         case 'tRNS': 
  2703.           //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk 
  2704.           //print "tRNS found, color type = ".$info['colorType']."<BR>"; 
  2705.           $transparency = array();
  2706.           if ($info['colorType'] == 3) { // indexed color, rbg 
  2707.           /* corresponding to entries in the plte chunk 
  2708.           Alpha for palette index 0: 1 byte 
  2709.           Alpha for palette index 1: 1 byte 
  2710.           ...etc... 
  2711.           */ 
  2712.             // there will be one entry for each palette entry. up until the last non-opaque entry.
  2713.             // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
  2714.             $transparency['type']='indexed';
  2715.             $numPalette = strlen($pdata)/3;
  2716.             $trans=0;
  2717.             for ($i=$chunkLen;$i>=0;$i--){
  2718.               if (ord($data[$p+8+$i])==0){
  2719.                 $trans=$i;
  2720.               }
  2721.             }
  2722.             $transparency['data'] = $trans;
  2723.             
  2724.           } elseif($info['colorType'] == 0) { // grayscale 
  2725.           /* corresponding to entries in the plte chunk 
  2726.           Gray: 2 bytes, range 0 .. (2^bitdepth)-1 
  2727.           */ 
  2728. //            $transparency['grayscale']=$this->PRVT_getBytes($data,$p+8,2); // g = grayscale 
  2729.             $transparency['type']='indexed';
  2730.             $transparency['data'] = ord($data[$p+8+1]);
  2731.           
  2732.           } elseif($info['colorType'] == 2) { // truecolor 
  2733.           /* corresponding to entries in the plte chunk 
  2734.           Red: 2 bytes, range 0 .. (2^bitdepth)-1 
  2735.           Green: 2 bytes, range 0 .. (2^bitdepth)-1 
  2736.           Blue: 2 bytes, range 0 .. (2^bitdepth)-1 
  2737.           */ 
  2738.             $transparency['r']=$this->PRVT_getBytes($data,$p+8,2); // r from truecolor 
  2739.             $transparency['g']=$this->PRVT_getBytes($data,$p+10,2); // g from truecolor 
  2740.             $transparency['b']=$this->PRVT_getBytes($data,$p+12,2); // b from truecolor 
  2741.           
  2742.           } else { 
  2743.           //unsupported transparency type 
  2744.           } 
  2745.           // KS End new code 
  2746.           break; 
  2747.         default:
  2748.           break;
  2749.       }
  2750.     
  2751.       $p += $chunkLen+12;
  2752.     }
  2753.     
  2754.     if(!$haveHeader){
  2755.       $error = 1;
  2756.       $errormsg = 'information header is missing';
  2757.     }
  2758.     if (isset($info['interlaceMethod']) && $info['interlaceMethod']){
  2759.       $error = 1;
  2760.       $errormsg = 'There appears to be no support for interlaced images in pdf.';
  2761.     }
  2762.   }
  2763.  
  2764.   if (!$error && $info['bitDepth'] > 8){
  2765.     $error = 1;
  2766.     $errormsg = 'only bit depth of 8 or less is supported';
  2767.   }
  2768.  
  2769.   if (!$error){
  2770.     if ($info['colorType']!=2 && $info['colorType']!=0 && $info['colorType']!=3){
  2771.       $error = 1;
  2772.       $errormsg = 'transparancey alpha channel not supported, transparency only supported for palette images.';
  2773.     } else {
  2774.       switch ($info['colorType']){
  2775.         case 3:
  2776.           $color = 'DeviceRGB';
  2777.           $ncolor=1;
  2778.           break;
  2779.         case 2:
  2780.           $color = 'DeviceRGB';
  2781.           $ncolor=3;
  2782.           break;
  2783.         case 0:
  2784.           $color = 'DeviceGray';
  2785.           $ncolor=1;
  2786.           break;
  2787.       }
  2788.     }
  2789.   }
  2790.   if ($error){
  2791.     $this->addMessage('PNG error - ('.$file.') '.$errormsg);
  2792.     return;
  2793.   }
  2794.   if ($w==0){
  2795.     $w=$h/$info['height']*$info['width'];
  2796.   }
  2797.   if ($h==0){
  2798.     $h=$w*$info['height']/$info['width'];
  2799.   }
  2800. //print_r($info);
  2801.   // so this image is ok... add it in.
  2802.   $this->numImages++;
  2803.   $im=$this->numImages;
  2804.   $label='I'.$im;
  2805.   $this->numObj++;
  2806. //  $this->o_image($this->numObj,'new',array('label'=>$label,'data'=>$idata,'iw'=>$w,'ih'=>$h,'type'=>'png','ic'=>$info['width']));
  2807.   $options = array('label'=>$label,'data'=>$idata,'bitsPerComponent'=>$info['bitDepth'],'pdata'=>$pdata
  2808.                                       ,'iw'=>$info['width'],'ih'=>$info['height'],'type'=>'png','color'=>$color,'ncolor'=>$ncolor);
  2809.   if (isset($transparency)){
  2810.     $options['transparency']=$transparency;
  2811.   }
  2812.   $this->o_image($this->numObj,'new',$options);
  2813.  
  2814.   $this->objects[$this->currentContents]['c'].="\nq";
  2815.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$w)." 0 0 ".sprintf('%.3f',$h)." ".sprintf('%.3f',$x)." ".sprintf('%.3f',$y)." cm";
  2816.   $this->objects[$this->currentContents]['c'].="\n/".$label.' Do';
  2817.   $this->objects[$this->currentContents]['c'].="\nQ";
  2818. }
  2819.  
  2820. /**
  2821. * add a JPEG image into the document, from a file
  2822. */
  2823. function addJpegFromFile($img,$x,$y,$w=0,$h=0){
  2824.   // attempt to add a jpeg image straight from a file, using no GD commands
  2825.   // note that this function is unable to operate on a remote file.
  2826.  
  2827.   if (!file_exists($img)){
  2828.     return;
  2829.   }
  2830.  
  2831.   $tmp=getimagesize($img);
  2832.   $imageWidth=$tmp[0];
  2833.   $imageHeight=$tmp[1];
  2834.  
  2835.   if (isset($tmp['channels'])){
  2836.     $channels = $tmp['channels'];
  2837.   } else {
  2838.     $channels = 3;
  2839.   }
  2840.  
  2841.   if ($w<=0 && $h<=0){
  2842.     $w=$imageWidth;
  2843.   }
  2844.   if ($w==0){
  2845.     $w=$h/$imageHeight*$imageWidth;
  2846.   }
  2847.   if ($h==0){
  2848.     $h=$w*$imageHeight/$imageWidth;
  2849.   }
  2850.  
  2851.   $fp=fopen($img,'rb');
  2852.  
  2853.   $tmp = get_magic_quotes_runtime();
  2854.   set_magic_quotes_runtime(0);
  2855.   $data = fread($fp,filesize($img));
  2856.   set_magic_quotes_runtime($tmp);
  2857.   
  2858.   fclose($fp);
  2859.  
  2860.   $this->addJpegImage_common($data,$x,$y,$w,$h,$imageWidth,$imageHeight,$channels);
  2861. }
  2862.  
  2863. /**
  2864. * add an image into the document, from a GD object
  2865. * this function is not all that reliable, and I would probably encourage people to use 
  2866. * the file based functions
  2867. */
  2868. function addImage(&$img,$x,$y,$w=0,$h=0,$quality=75){
  2869.   // add a new image into the current location, as an external object
  2870.   // add the image at $x,$y, and with width and height as defined by $w & $h
  2871.   
  2872.   // note that this will only work with full colour images and makes them jpg images for display
  2873.   // later versions could present lossless image formats if there is interest.
  2874.   
  2875.   // there seems to be some problem here in that images that have quality set above 75 do not appear
  2876.   // not too sure why this is, but in the meantime I have restricted this to 75.  
  2877.   if ($quality>75){
  2878.     $quality=75;
  2879.   }
  2880.  
  2881.   // if the width or height are set to zero, then set the other one based on keeping the image
  2882.   // height/width ratio the same, if they are both zero, then give up :)
  2883.   $imageWidth=imagesx($img);
  2884.   $imageHeight=imagesy($img);
  2885.   
  2886.   if ($w<=0 && $h<=0){
  2887.     return;
  2888.   }
  2889.   if ($w==0){
  2890.     $w=$h/$imageHeight*$imageWidth;
  2891.   }
  2892.   if ($h==0){
  2893.     $h=$w*$imageHeight/$imageWidth;
  2894.   }
  2895.   
  2896.   // gotta get the data out of the img..
  2897.  
  2898.   // so I write to a temp file, and then read it back.. soo ugly, my apologies.
  2899.   $tmpDir='/tmp';
  2900.   $tmpName=tempnam($tmpDir,'img');
  2901.   imagejpeg($img,$tmpName,$quality);
  2902.   $fp=fopen($tmpName,'rb');
  2903.  
  2904.   $tmp = get_magic_quotes_runtime();
  2905.   set_magic_quotes_runtime(0);
  2906.   $fp = @fopen($tmpName,'rb');
  2907.   if ($fp){
  2908.     $data='';
  2909.     while(!feof($fp)){
  2910.       $data .= fread($fp,1024);
  2911.     }
  2912.     fclose($fp);
  2913.   } else {
  2914.     $error = 1;
  2915.     $errormsg = 'trouble opening file';
  2916.   }
  2917. //  $data = fread($fp,filesize($tmpName));
  2918.   set_magic_quotes_runtime($tmp);
  2919. //  fclose($fp);
  2920.   unlink($tmpName);
  2921.   $this->addJpegImage_common($data,$x,$y,$w,$h,$imageWidth,$imageHeight);
  2922. }
  2923.  
  2924. /**
  2925. * common code used by the two JPEG adding functions
  2926. *
  2927. * @access private
  2928. */
  2929. function addJpegImage_common(&$data,$x,$y,$w=0,$h=0,$imageWidth,$imageHeight,$channels=3){
  2930.   // note that this function is not to be called externally
  2931.   // it is just the common code between the GD and the file options
  2932.   $this->numImages++;
  2933.   $im=$this->numImages;
  2934.   $label='I'.$im;
  2935.   $this->numObj++;
  2936.   $this->o_image($this->numObj,'new',array('label'=>$label,'data'=>$data,'iw'=>$imageWidth,'ih'=>$imageHeight,'channels'=>$channels));
  2937.  
  2938.   $this->objects[$this->currentContents]['c'].="\nq";
  2939.   $this->objects[$this->currentContents]['c'].="\n".sprintf('%.3f',$w)." 0 0 ".sprintf('%.3f',$h)." ".sprintf('%.3f',$x)." ".sprintf('%.3f',$y)." cm";
  2940.   $this->objects[$this->currentContents]['c'].="\n/".$label.' Do';
  2941.   $this->objects[$this->currentContents]['c'].="\nQ";
  2942. }
  2943.  
  2944. /**
  2945. * specify where the document should open when it first starts
  2946. */
  2947. function openHere($style,$a=0,$b=0,$c=0){
  2948.   // this function will open the document at a specified page, in a specified style
  2949.   // the values for style, and the required paramters are:
  2950.   // 'XYZ'  left, top, zoom
  2951.   // 'Fit'
  2952.   // 'FitH' top
  2953.   // 'FitV' left
  2954.   // 'FitR' left,bottom,right
  2955.   // 'FitB'
  2956.   // 'FitBH' top
  2957.   // 'FitBV' left
  2958.   $this->numObj++;
  2959.   $this->o_destination($this->numObj,'new',array('page'=>$this->currentPage,'type'=>$style,'p1'=>$a,'p2'=>$b,'p3'=>$c));
  2960.   $id = $this->catalogId;
  2961.   $this->o_catalog($id,'openHere',$this->numObj);
  2962. }
  2963.  
  2964. /**
  2965. * create a labelled destination within the document
  2966. */
  2967. function addDestination($label,$style,$a=0,$b=0,$c=0){
  2968.   // associates the given label with the destination, it is done this way so that a destination can be specified after
  2969.   // it has been linked to
  2970.   // styles are the same as the 'openHere' function
  2971.   $this->numObj++;
  2972.   $this->o_destination($this->numObj,'new',array('page'=>$this->currentPage,'type'=>$style,'p1'=>$a,'p2'=>$b,'p3'=>$c));
  2973.   $id = $this->numObj;
  2974.   // store the label->idf relationship, note that this means that labels can be used only once
  2975.   $this->destinations["$label"]=$id;
  2976. }
  2977.  
  2978. /**
  2979. * define font families, this is used to initialize the font families for the default fonts
  2980. * and for the user to add new ones for their fonts. The default bahavious can be overridden should
  2981. * that be desired.
  2982. */
  2983. function setFontFamily($family,$options=''){
  2984.   if (!is_array($options)){
  2985.     if ($family=='init'){
  2986.       // set the known family groups
  2987.       // these font families will be used to enable bold and italic markers to be included
  2988.       // within text streams. html forms will be used... <b></b> <i></i>
  2989.       $this->fontFamilies['Helvetica.afm']=array(
  2990.          'b'=>'Helvetica-Bold.afm'
  2991.         ,'i'=>'Helvetica-Oblique.afm'
  2992.         ,'bi'=>'Helvetica-BoldOblique.afm'
  2993.         ,'ib'=>'Helvetica-BoldOblique.afm'
  2994.       );
  2995.       $this->fontFamilies['Courier.afm']=array(
  2996.          'b'=>'Courier-Bold.afm'
  2997.         ,'i'=>'Courier-Oblique.afm'
  2998.         ,'bi'=>'Courier-BoldOblique.afm'
  2999.         ,'ib'=>'Courier-BoldOblique.afm'
  3000.       );
  3001.       $this->fontFamilies['Times-Roman.afm']=array(
  3002.          'b'=>'Times-Bold.afm'
  3003.         ,'i'=>'Times-Italic.afm'
  3004.         ,'bi'=>'Times-BoldItalic.afm'
  3005.         ,'ib'=>'Times-BoldItalic.afm'
  3006.       );
  3007.     }
  3008.   } else {
  3009.     // the user is trying to set a font family
  3010.     // note that this can also be used to set the base ones to something else
  3011.     if (strlen($family)){
  3012.       $this->fontFamilies[$family] = $options;
  3013.     }
  3014.   }
  3015. }
  3016.  
  3017. /**
  3018. * used to add messages for use in debugging
  3019. */
  3020. function addMessage($message){
  3021.   $this->messages.=$message."\n";
  3022. }
  3023.  
  3024. /**
  3025. * a few functions which should allow the document to be treated transactionally.
  3026. */
  3027. function transaction($action){
  3028.   switch ($action){
  3029.     case 'start':
  3030.       // store all the data away into the checkpoint variable
  3031.       $data = get_object_vars($this);
  3032.       $this->checkpoint = $data;
  3033.       unset($data);
  3034.       break;
  3035.     case 'commit':
  3036.       if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])){
  3037.         $tmp = $this->checkpoint['checkpoint'];
  3038.         $this->checkpoint = $tmp;
  3039.         unset($tmp);
  3040.       } else {
  3041.         $this->checkpoint='';
  3042.       }
  3043.       break;
  3044.     case 'rewind':
  3045.       // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
  3046.       if (is_array($this->checkpoint)){
  3047.         // can only abort if were inside a checkpoint
  3048.         $tmp = $this->checkpoint;
  3049.         foreach ($tmp as $k=>$v){
  3050.           if ($k != 'checkpoint'){
  3051.             $this->$k=$v;
  3052.           }
  3053.         }
  3054.         unset($tmp);
  3055.       }
  3056.       break;
  3057.     case 'abort':
  3058.       if (is_array($this->checkpoint)){
  3059.         // can only abort if were inside a checkpoint
  3060.         $tmp = $this->checkpoint;
  3061.         foreach ($tmp as $k=>$v){
  3062.           $this->$k=$v;
  3063.         }
  3064.         unset($tmp);
  3065.       }
  3066.       break;
  3067.   }
  3068.  
  3069. }
  3070.  
  3071. } // end of class
  3072.  
  3073. ?>