Source for file class.pdf.php

Documentation is available at class.pdf.php

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

Documentation generated on Mon, 05 May 2008 16:17:26 +0400 by phpDocumentor 1.4.0