[ PHPXref.com ] [ Generated: Sun Jul 20 18:02:19 2008 ] [ Group-Office 2.15-FINAL-7 ]
[ Index ]     [ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/classes/dompdf/lib/ -> class.pdf.php (source)

   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    /**
  27     * the current number of pdf objects in the document
  28     */
  29    public  $numObj = 0;
  30  
  31    /**
  32     * this array contains all of the pdf objects, ready for final assembly
  33     */
  34    public  $objects =  array();
  35  
  36    /**
  37     * the objectId (number within the objects array) of the document catalog
  38     */
  39    public  $catalogId;
  40  
  41    /**
  42     * array carrying information about the fonts that the system currently knows about
  43     * used to ensure that a font is not loaded twice, among other things
  44     */
  45    public  $fonts = array();
  46  
  47    /**
  48     * a record of the current font
  49     */
  50    public  $currentFont = '';
  51  
  52    /**
  53     * the current base font
  54     */
  55    public  $currentBaseFont = '';
  56  
  57    /**
  58     * the number of the current font within the font array
  59     */
  60    public  $currentFontNum = 0;
  61  
  62    /**
  63     *
  64     */
  65    public  $currentNode;
  66  
  67    /**
  68     * object number of the current page
  69     */
  70    public  $currentPage;
  71  
  72    /**
  73     * object number of the currently active contents block
  74     */
  75    public  $currentContents;
  76  
  77    /**
  78     * number of fonts within the system
  79     */
  80    public  $numFonts = 0;
  81  
  82    /**
  83     * Number of graphic state resources used
  84     */
  85    private  $numStates =  0;
  86  
  87  
  88    /**
  89     * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
  90     */
  91    public  $currentColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
  92  
  93    /**
  94     * current colour for stroke operations (lines etc.)
  95     */
  96    public  $currentStrokeColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
  97  
  98    /**
  99     * current style that lines are drawn in
 100     */
 101    public  $currentLineStyle = '';
 102  
 103    /**
 104     * current line transparency (partial graphics state)
 105     */
 106    public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
 107    
 108    /**
 109     * current fill transparency (partial graphics state)
 110     */
 111    public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
 112    
 113    /**
 114     * an array which is used to save the state of the document, mainly the colours and styles
 115     * it is used to temporarily change to another state, the change back to what it was before
 116     */
 117    public  $stateStack =  array();
 118  
 119    /**
 120     * number of elements within the state stack
 121     */
 122    public  $nStateStack =  0;
 123  
 124    /**
 125     * number of page objects within the document
 126     */
 127    public  $numPages = 0;
 128  
 129    /**
 130     * object Id storage stack
 131     */
 132    public  $stack = array();
 133  
 134    /**
 135     * number of elements within the object Id storage stack
 136     */
 137    public  $nStack = 0;
 138  
 139    /**
 140     * an array which contains information about the objects which are not firmly attached to pages
 141     * these have been added with the addObject function
 142     */
 143    public  $looseObjects = array();
 144  
 145    /**
 146     * array contains infomation about how the loose objects are to be added to the document
 147     */
 148    public  $addLooseObjects = array();
 149  
 150    /**
 151     * the objectId of the information object for the document
 152     * this contains authorship, title etc.
 153     */
 154    public  $infoObject = 0;
 155  
 156    /**
 157     * number of images being tracked within the document
 158     */
 159    public  $numImages = 0;
 160  
 161    /**
 162     * an array containing options about the document
 163     * it defaults to turning on the compression of the objects
 164     */
 165    public  $options = array('compression'=>1);
 166  
 167    /**
 168     * the objectId of the first page of the document
 169     */
 170    public  $firstPageId;
 171  
 172    /**
 173     * used to track the last used value of the inter-word spacing, this is so that it is known
 174     * when the spacing is changed.
 175     */
 176    public  $wordSpaceAdjust = 0;
 177  
 178    /**
 179     * the object Id of the procset object
 180     */
 181    public  $procsetObjectId;
 182  
 183    /**
 184     * store the information about the relationship between font families
 185     * this used so that the code knows which font is the bold version of another font, etc.
 186     * the value of this array is initialised in the constuctor function.
 187     */
 188    public  $fontFamilies =  array();
 189  
 190    /**
 191     * track if the current font is bolded or italicised
 192     */
 193    public  $currentTextState =  '';
 194  
 195    /**
 196     * messages are stored here during processing, these can be selected afterwards to give some useful debug information
 197     */
 198    public  $messages = '';
 199  
 200    /**
 201     * the ancryption array for the document encryption is stored here
 202     */
 203    public  $arc4 = '';
 204  
 205    /**
 206     * the object Id of the encryption information
 207     */
 208    public  $arc4_objnum = 0;
 209  
 210    /**
 211     * the file identifier, used to uniquely identify a pdf document
 212     */
 213    public  $fileIdentifier = '';
 214  
 215    /**
 216     * a flag to say if a document is to be encrypted or not
 217     */
 218    public  $encrypted = 0;
 219  
 220    /**
 221     * the ancryption key for the encryption of all the document content (structure is not encrypted)
 222     */
 223    public  $encryptionKey = '';
 224  
 225    /**
 226     * array which forms a stack to keep track of nested callback functions
 227     */
 228    public  $callback =  array();
 229  
 230    /**
 231     * the number of callback functions in the callback array
 232     */
 233    public  $nCallback =  0;
 234  
 235    /**
 236     * store label->id pairs for named destinations, these will be used to replace internal links
 237     * done this way so that destinations can be defined after the location that links to them
 238     */
 239    public  $destinations =  array();
 240  
 241    /**
 242     * store the stack for the transaction commands, each item in here is a record of the values of all the
 243     * publiciables within the class, so that the user can rollback at will (from each 'start' command)
 244     * note that this includes the objects array, so these can be large.
 245     */
 246    public  $checkpoint =  '';
 247  
 248    /**
 249     * class constructor
 250     * this will start a new document
 251     * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
 252     */
 253    function  Cpdf ($pageSize = array(0, 0, 612, 792)) {
 254  
 255      $this->newDocument($pageSize);
 256  
 257  
 258      // also initialize the font families that are known about already
 259      $this->setFontFamily('init');
 260  
 261      //  $this->fileIdentifier = md5('xxxxxxxx'.time());
 262  
 263  
 264    }
 265  
 266  
 267    /**
 268     * Document object methods (internal use only)
 269     *
 270     * There is about one object method for each type of object in the pdf document
 271     * Each function has the same call list ($id,$action,$options).
 272     * $id = the object ID of the object, or what it is to be if it is being created
 273     * $action = a string specifying the action to be performed, though ALL must support:
 274     *           'new' - create the object with the id $id
 275     *           'out' - produce the output for the pdf object
 276     * $options = optional, a string or array containing the various parameters for the object
 277     *
 278     * These, in conjunction with the output function are the ONLY way for output to be produced
 279     * within the pdf 'file'.
 280     */
 281  
 282    /**
 283     *destination object, used to specify the location for the user to jump to, presently on opening
 284     */
 285    function  o_destination($id, $action, $options = '') {
 286  
 287      if  ($action != 'new') {
 288  
 289        $o = & $this->objects[$id];
 290      }
 291  
 292      switch ($action) {
 293  
 294      case  'new':
 295  
 296        $this->objects[$id] = array('t'=>'destination', 'info'=>array());
 297  
 298        $tmp =  '';
 299  
 300        switch  ($options['type']) {
 301  
 302        case  'XYZ':
 303  
 304        case  'FitR':
 305  
 306          $tmp =   ' '.$options['p3'].$tmp;
 307  
 308        case  'FitH':
 309  
 310        case  'FitV':
 311  
 312        case  'FitBH':
 313  
 314        case  'FitBV':
 315  
 316          $tmp =   ' '.$options['p1'].' '.$options['p2'].$tmp;
 317  
 318        case  'Fit':
 319  
 320        case  'FitB':
 321  
 322          $tmp =   $options['type'].$tmp;
 323  
 324          $this->objects[$id]['info']['string'] = $tmp;
 325  
 326          $this->objects[$id]['info']['page'] = $options['page'];
 327        }
 328  
 329        break;
 330  
 331      case  'out':
 332  
 333        $tmp =  $o['info'];
 334  
 335        $res = "\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj\n";
 336  
 337        return  $res;
 338  
 339        break;
 340      }
 341    }
 342  
 343  
 344    /**
 345     * set the viewer preferences
 346     */
 347    function  o_viewerPreferences($id, $action, $options = '') {
 348  
 349      if  ($action != 'new') {
 350  
 351        $o = & $this->objects[$id];
 352      }
 353  
 354      switch  ($action) {
 355  
 356      case  'new':
 357  
 358        $this->objects[$id] = array('t'=>'viewerPreferences', 'info'=>array());
 359  
 360        break;
 361  
 362      case  'add':
 363  
 364        foreach($options as  $k=>$v) {
 365  
 366          switch  ($k) {
 367  
 368          case  'HideToolbar':
 369  
 370          case  'HideMenubar':
 371  
 372          case  'HideWindowUI':
 373  
 374          case  'FitWindow':
 375  
 376          case  'CenterWindow':
 377  
 378          case  'NonFullScreenPageMode':
 379  
 380          case  'Direction':
 381  
 382            $o['info'][$k] = $v;
 383  
 384            break;
 385          }
 386        }
 387  
 388        break;
 389  
 390      case  'out':
 391  
 392  
 393        $res = "\n".$id." 0 obj\n".'<< ';
 394  
 395        foreach($o['info'] as  $k=>$v) {
 396  
 397          $res.= "\n/".$k.' '.$v;
 398        }
 399  
 400        $res.= "\n>>\n";
 401  
 402        return  $res;
 403  
 404        break;
 405      }
 406    }
 407  
 408  
 409    /**
 410     * define the document catalog, the overall controller for the document
 411     */
 412    function  o_catalog($id, $action, $options = '') {
 413  
 414      if  ($action != 'new') {
 415  
 416        $o = & $this->objects[$id];
 417      }
 418  
 419      switch  ($action) {
 420  
 421      case  'new':
 422  
 423        $this->objects[$id] = array('t'=>'catalog', 'info'=>array());
 424  
 425        $this->catalogId = $id;
 426  
 427        break;
 428  
 429      case  'outlines':
 430  
 431      case  'pages':
 432  
 433      case  'openHere':
 434  
 435        $o['info'][$action] = $options;
 436  
 437        break;
 438  
 439      case  'viewerPreferences':
 440  
 441        if  (!isset($o['info']['viewerPreferences'])) {
 442  
 443          $this->numObj++;
 444  
 445          $this->o_viewerPreferences($this->numObj, 'new');
 446  
 447          $o['info']['viewerPreferences'] = $this->numObj;
 448        }
 449  
 450        $vp =  $o['info']['viewerPreferences'];
 451  
 452        $this->o_viewerPreferences($vp, 'add', $options);
 453  
 454        break;
 455  
 456      case  'out':
 457  
 458        $res = "\n".$id." 0 obj\n".'<< /Type /Catalog';
 459  
 460        foreach($o['info'] as  $k=>$v) {
 461  
 462          switch ($k) {
 463  
 464          case  'outlines':
 465  
 466            $res.= "\n".'/Outlines '.$v.' 0 R';
 467  
 468            break;
 469  
 470          case  'pages':
 471  
 472            $res.= "\n".'/Pages '.$v.' 0 R';
 473  
 474            break;
 475  
 476          case  'viewerPreferences':
 477  
 478            $res.= "\n".'/ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
 479  
 480            break;
 481  
 482          case  'openHere':
 483  
 484            $res.= "\n".'/OpenAction '.$o['info']['openHere'].' 0 R';
 485  
 486            break;
 487          }
 488        }
 489  
 490        $res.= " >>\nendobj";
 491  
 492        return  $res;
 493  
 494        break;
 495      }
 496    }
 497  
 498  
 499    /**
 500     * object which is a parent to the pages in the document
 501     */
 502    function  o_pages($id, $action, $options = '') {
 503  
 504      if  ($action != 'new') {
 505  
 506        $o = & $this->objects[$id];
 507      }
 508  
 509      switch  ($action) {
 510  
 511      case  'new':
 512  
 513        $this->objects[$id] = array('t'=>'pages', 'info'=>array());
 514  
 515        $this->o_catalog($this->catalogId, 'pages', $id);
 516  
 517        break;
 518  
 519      case  'page':
 520  
 521        if  (!is_array($options)) {
 522  
 523          // then it will just be the id of the new page
 524          $o['info']['pages'][] = $options;
 525        } else {
 526  
 527          // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
 528          // and pos is either 'before' or 'after', saying where this page will fit.
 529          if  (isset($options['id']) &&  isset($options['rid']) &&  isset($options['pos'])) {
 530  
 531            $i =  array_search($options['rid'], $o['info']['pages']);
 532  
 533            if  (isset($o['info']['pages'][$i]) &&  $o['info']['pages'][$i] == $options['rid']) {
 534  
 535              // then there is a match
 536              // make a space
 537              switch  ($options['pos']) {
 538  
 539              case  'before':
 540  
 541                $k =  $i;
 542  
 543                break;
 544  
 545              case  'after':
 546  
 547                $k = $i+1;
 548  
 549                break;
 550  
 551              default:
 552  
 553                $k = -1;
 554  
 555                break;
 556              }
 557  
 558              if  ($k >= 0) {
 559  
 560                for  ($j = count($o['info']['pages']) -1;$j >= $k;$j--) {
 561  
 562                  $o['info']['pages'][$j+1] = $o['info']['pages'][$j];
 563                }
 564  
 565                $o['info']['pages'][$k] = $options['id'];
 566              }
 567            }
 568          }
 569        }
 570  
 571        break;
 572  
 573      case  'procset':
 574  
 575        $o['info']['procset'] = $options;
 576  
 577        break;
 578  
 579      case  'mediaBox':
 580  
 581        $o['info']['mediaBox'] = $options;
 582        // which should be an array of 4 numbers
 583        break;
 584  
 585      case  'font':
 586  
 587        $o['info']['fonts'][] = array('objNum'=>$options['objNum'], 'fontNum'=>$options['fontNum']);
 588  
 589        break;
 590  
 591  
 592      case  'extGState':
 593  
 594        $o['info']['extGStates'][] =  array('objNum' => $options['objNum'],  'stateNum' => $options['stateNum']);
 595  
 596        break;
 597  
 598  
 599      case  'xObject':
 600  
 601        $o['info']['xObjects'][] = array('objNum'=>$options['objNum'], 'label'=>$options['label']);
 602  
 603        break;
 604  
 605      case  'out':
 606  
 607        if  (count($o['info']['pages'])) {
 608  
 609          $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Kids [";
 610  
 611          foreach($o['info']['pages'] as  $k=>$v) {
 612  
 613            $res.= $v." 0 R\n";
 614          }
 615  
 616          $res.= "]\n/Count ".count($this->objects[$id]['info']['pages']);
 617  
 618  
 619          if  ( (isset($o['info']['fonts']) &&  count($o['info']['fonts'])) ||
 620                isset($o['info']['procset']) ||
 621                (isset($o['info']['extGStates']) &&  count($o['info']['extGStates']))) {
 622  
 623  
 624            $res.= "\n/Resources <<";
 625  
 626            if  (isset($o['info']['procset'])) {
 627  
 628              $res.= "\n/ProcSet ".$o['info']['procset']." 0 R";
 629            }
 630  
 631            if  (isset($o['info']['fonts']) &&  count($o['info']['fonts'])) {
 632  
 633              $res.= "\n/Font << ";
 634  
 635              foreach($o['info']['fonts'] as  $finfo) {
 636  
 637                $res.= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
 638              }
 639  
 640              $res.= " >>";
 641            }
 642  
 643            if  (isset($o['info']['xObjects']) &&  count($o['info']['xObjects'])) {
 644  
 645              $res.= "\n/XObject << ";
 646  
 647              foreach($o['info']['xObjects'] as  $finfo) {
 648  
 649                $res.= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
 650              }
 651  
 652              $res.= " >>";
 653            }
 654  
 655            if  ( isset($o['info']['extGStates']) &&  count($o['info']['extGStates'])) {
 656  
 657              $res.=  "\n/ExtGState << ";
 658  
 659              foreach ($o['info']['extGStates'] as  $gstate) {
 660  
 661                $res.=  "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
 662              }
 663  
 664              $res.=  " >>";
 665            }
 666  
 667  
 668            $res.= "\n>>";
 669  
 670            if  (isset($o['info']['mediaBox'])) {
 671  
 672              $tmp = $o['info']['mediaBox'];
 673  
 674              $res.= "\n/MediaBox [".sprintf('%.3f', $tmp[0]) .' '.sprintf('%.3f', $tmp[1]) .' '.sprintf('%.3f', $tmp[2]) .' '.sprintf('%.3f', $tmp[3]) .']';
 675            }
 676          }
 677  
 678          $res.= "\n >>\nendobj";
 679        } else {
 680  
 681          $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
 682        }
 683  
 684        return  $res;
 685  
 686        break;
 687      }
 688    }
 689  
 690  
 691    /**
 692     * define the outlines in the doc, empty for now
 693     */
 694    function  o_outlines($id, $action, $options = '') {
 695  
 696      if  ($action != 'new') {
 697  
 698        $o = & $this->objects[$id];
 699      }
 700  
 701      switch  ($action) {
 702  
 703      case  'new':
 704  
 705        $this->objects[$id] = array('t'=>'outlines', 'info'=>array('outlines'=>array()));
 706  
 707        $this->o_catalog($this->catalogId, 'outlines', $id);
 708  
 709        break;
 710  
 711      case  'outline':
 712  
 713        $o['info']['outlines'][] = $options;
 714  
 715        break;
 716  
 717      case  'out':
 718  
 719        if  (count($o['info']['outlines'])) {
 720  
 721          $res = "\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
 722  
 723          foreach($o['info']['outlines'] as  $k=>$v) {
 724  
 725            $res.= $v." 0 R ";
 726          }
 727  
 728          $res.= "] /Count ".count($o['info']['outlines']) ." >>\nendobj";
 729        } else {
 730  
 731          $res = "\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
 732        }
 733  
 734        return  $res;
 735  
 736        break;
 737      }
 738    }
 739  
 740  
 741    /**
 742     * an object to hold the font description
 743     */
 744    function  o_font($id, $action, $options = '') {
 745  
 746      if  ($action != 'new') {
 747  
 748        $o = & $this->objects[$id];
 749      }
 750  
 751      switch  ($action) {
 752  
 753      case  'new':
 754  
 755        $this->objects[$id] =  array('t' => 'font', 'info' => array('name' => $options['name'], 'SubType' => 'Type1'));
 756  
 757        $fontNum =  $this->numFonts;
 758  
 759        $this->objects[$id]['info']['fontNum'] =  $fontNum;
 760  
 761        // deal with the encoding and the differences
 762        if  (isset($options['differences'])) {
 763  
 764          // then we'll need an encoding dictionary
 765          $this->numObj++;
 766  
 767          $this->o_fontEncoding($this->numObj, 'new', $options);
 768  
 769          $this->objects[$id]['info']['encodingDictionary'] =  $this->numObj;
 770        } else  if  (isset($options['encoding'])) {
 771  
 772          // we can specify encoding here
 773          switch ($options['encoding']) {
 774  
 775          case  'WinAnsiEncoding':
 776  
 777          case  'MacRomanEncoding':
 778  
 779          case  'MacExpertEncoding':
 780  
 781            $this->objects[$id]['info']['encoding'] =  $options['encoding'];
 782  
 783            break;
 784  
 785          case  'none':
 786  
 787            break;
 788  
 789          default:
 790  
 791            $this->objects[$id]['info']['encoding'] =  'WinAnsiEncoding';
 792  
 793            break;
 794          }
 795        } else {
 796  
 797          $this->objects[$id]['info']['encoding'] =  'WinAnsiEncoding';
 798        }
 799  
 800        // also tell the pages node about the new font
 801        $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
 802  
 803        break;
 804  
 805  
 806      case  'add':
 807  
 808        foreach ($options as  $k => $v) {
 809  
 810          switch  ($k) {
 811  
 812          case  'BaseFont':
 813  
 814            $o['info']['name'] =  $v;
 815  
 816            break;
 817  
 818          case  'FirstChar':
 819  
 820          case  'LastChar':
 821  
 822          case  'Widths':
 823  
 824          case  'FontDescriptor':
 825  
 826          case  'SubType':
 827  
 828            $this->addMessage('o_font '.$k." : ".$v);
 829  
 830            $o['info'][$k] =  $v;
 831  
 832            break;
 833          }
 834        }
 835  
 836        break;
 837  
 838  
 839      case  'out':
 840  
 841        $res =  "\n".$id." 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
 842  
 843        $res.=  "/Name /F".$o['info']['fontNum']."\n";
 844  
 845        $res.=  "/BaseFont /".$o['info']['name']."\n";
 846  
 847        if  (isset($o['info']['encodingDictionary'])) {
 848  
 849          // then place a reference to the dictionary
 850          $res.=  "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
 851        } else  if  (isset($o['info']['encoding'])) {
 852  
 853          // use the specified encoding
 854          $res.=  "/Encoding /".$o['info']['encoding']."\n";
 855        }
 856  
 857        if  (isset($o['info']['FirstChar'])) {
 858  
 859          $res.=  "/FirstChar ".$o['info']['FirstChar']."\n";
 860        }
 861  
 862        if  (isset($o['info']['LastChar'])) {
 863  
 864          $res.=  "/LastChar ".$o['info']['LastChar']."\n";
 865        }
 866  
 867        if  (isset($o['info']['Widths'])) {
 868  
 869          $res.=  "/Widths ".$o['info']['Widths']." 0 R\n";
 870        }
 871  
 872        if  (isset($o['info']['FontDescriptor'])) {
 873  
 874          $res.=  "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
 875        }
 876  
 877        $res.=  ">>\nendobj";
 878  
 879        return  $res;
 880  
 881        break;
 882      }
 883    }
 884  
 885  
 886    /**
 887     * a font descriptor, needed for including additional fonts
 888     */
 889    function  o_fontDescriptor($id, $action, $options = '') {
 890  
 891      if  ($action != 'new') {
 892  
 893        $o = & $this->objects[$id];
 894      }
 895  
 896      switch  ($action) {
 897  
 898      case  'new':
 899  
 900        $this->objects[$id] = array('t'=>'fontDescriptor', 'info'=>$options);
 901  
 902        break;
 903  
 904      case  'out':
 905  
 906        $res = "\n".$id." 0 obj\n<< /Type /FontDescriptor\n";
 907  
 908        foreach ($o['info'] as  $label => $value) {
 909  
 910          switch  ($label) {
 911  
 912          case  'Ascent':
 913  
 914          case  'CapHeight':
 915  
 916          case  'Descent':
 917  
 918          case  'Flags':
 919  
 920          case  'ItalicAngle':
 921  
 922          case  'StemV':
 923  
 924          case  'AvgWidth':
 925  
 926          case  'Leading':
 927  
 928          case  'MaxWidth':
 929  
 930          case  'MissingWidth':
 931  
 932          case  'StemH':
 933  
 934          case  'XHeight':
 935  
 936          case  'CharSet':
 937  
 938            if  (strlen($value)) {
 939  
 940              $res.= '/'.$label.' '.$value."\n";
 941            }
 942  
 943            break;
 944  
 945          case  'FontFile':
 946  
 947          case  'FontFile2':
 948  
 949          case  'FontFile3':
 950  
 951            $res.= '/'.$label.' '.$value." 0 R\n";
 952  
 953            break;
 954  
 955          case  'FontBBox':
 956  
 957            $res.= '/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
 958  
 959            break;
 960  
 961          case  'FontName':
 962  
 963            $res.= '/'.$label.' /'.$value."\n";
 964  
 965            break;
 966          }
 967        }
 968  
 969        $res.= ">>\nendobj";
 970  
 971        return  $res;
 972  
 973        break;
 974      }
 975    }
 976  
 977  
 978    /**
 979     * the font encoding
 980     */
 981    function  o_fontEncoding($id, $action, $options = '') {
 982  
 983      if  ($action != 'new') {
 984  
 985        $o = & $this->objects[$id];
 986      }
 987  
 988      switch  ($action) {
 989  
 990      case  'new':
 991  
 992        // the options array should contain 'differences' and maybe 'encoding'
 993        $this->objects[$id] = array('t'=>'fontEncoding', 'info'=>$options);
 994  
 995        break;
 996  
 997      case  'out':
 998  
 999        $res = "\n".$id." 0 obj\n<< /Type /Encoding\n";
1000  
1001        if  (!isset($o['info']['encoding'])) {
1002  
1003          $o['info']['encoding'] = 'WinAnsiEncoding';
1004        }
1005  
1006        if  ($o['info']['encoding'] != 'none') {
1007  
1008          $res.= "/BaseEncoding /".$o['info']['encoding']."\n";
1009        }
1010  
1011        $res.= "/Differences \n[";
1012  
1013        $onum = -100;
1014  
1015        foreach($o['info']['differences'] as  $num=>$label) {
1016  
1017          if  ($num != $onum+1) {
1018  
1019            // we cannot make use of consecutive numbering
1020            $res.=  "\n".$num." /".$label;
1021          } else {
1022  
1023            $res.=  " /".$label;
1024          }
1025  
1026          $onum = $num;
1027        }
1028  
1029        $res.= "\n]\n>>\nendobj";
1030  
1031        return  $res;
1032  
1033        break;
1034      }
1035    }
1036  
1037  
1038    /**
1039     * the document procset, solves some problems with printing to old PS printers
1040     */
1041    function  o_procset($id, $action, $options = '') {
1042  
1043      if  ($action != 'new') {
1044  
1045        $o = & $this->objects[$id];
1046      }
1047  
1048      switch  ($action) {
1049  
1050      case  'new':
1051  
1052        $this->objects[$id] = array('t'=>'procset', 'info'=>array('PDF'=>1, 'Text'=>1));
1053  
1054        $this->o_pages($this->currentNode, 'procset', $id);
1055  
1056        $this->procsetObjectId = $id;
1057  
1058        break;
1059  
1060      case  'add':
1061  
1062        // this is to add new items to the procset list, despite the fact that this is considered
1063        // obselete, the items are required for printing to some postscript printers
1064        switch  ($options) {
1065  
1066        case  'ImageB':
1067  
1068        case  'ImageC':
1069  
1070        case  'ImageI':
1071  
1072          $o['info'][$options] = 1;
1073  
1074          break;
1075        }
1076  
1077        break;
1078  
1079      case  'out':
1080  
1081        $res = "\n".$id." 0 obj\n[";
1082  
1083        foreach ($o['info'] as  $label=>$val) {
1084  
1085          $res.= '/'.$label.' ';
1086        }
1087  
1088        $res.= "]\nendobj";
1089  
1090        return  $res;
1091  
1092        break;
1093      }
1094    }
1095  
1096  
1097    /**
1098     * define the document information
1099     */
1100    function  o_info($id, $action, $options = '') {
1101  
1102      if  ($action != 'new') {
1103  
1104        $o = & $this->objects[$id];
1105      }
1106  
1107      switch  ($action) {
1108  
1109      case  'new':
1110  
1111        $this->infoObject = $id;
1112  
1113        $date = 'D:'.@date('Ymd');
1114  
1115        $this->objects[$id] = array('t'=>'info', 'info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate'=>$date));
1116  
1117        break;
1118  
1119      case  'Title':
1120  
1121      case  'Author':
1122  
1123      case  'Subject':
1124  
1125      case  'Keywords':
1126  
1127      case  'Creator':
1128  
1129      case  'Producer':
1130  
1131      case  'CreationDate':
1132  
1133      case  'ModDate':
1134  
1135      case  'Trapped':
1136  
1137        $o['info'][$action] = $options;
1138  
1139        break;
1140  
1141      case  'out':
1142  
1143        if  ($this->encrypted) {
1144  
1145          $this->encryptInit($id);
1146        }
1147  
1148        $res = "\n".$id." 0 obj\n<<\n";
1149  
1150        foreach ($o['info'] as  $k=>$v) {
1151  
1152          $res.= '/'.$k.' (';
1153  
1154          if  ($this->encrypted) {
1155  
1156            $res.= $this->filterText($this->ARC4($v));
1157          } else {
1158  
1159            $res.= $this->filterText($v);
1160          }
1161  
1162          $res.= ")\n";
1163        }
1164  
1165        $res.= ">>\nendobj";
1166  
1167        return  $res;
1168  
1169        break;
1170      }
1171    }
1172  
1173  
1174    /**
1175     * an action object, used to link to URLS initially
1176     */
1177    function  o_action($id, $action, $options = '') {
1178  
1179      if  ($action != 'new') {
1180  
1181        $o = & $this->objects[$id];
1182      }
1183  
1184      switch  ($action) {
1185  
1186      case  'new':
1187  
1188        if  (is_array($options)) {
1189  
1190          $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>$options['type']);
1191        } else {
1192  
1193          // then assume a URI action
1194          $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>'URI');
1195        }
1196  
1197        break;
1198  
1199      case  'out':
1200  
1201        if  ($this->encrypted) {
1202  
1203          $this->encryptInit($id);
1204        }
1205  
1206        $res = "\n".$id." 0 obj\n<< /Type /Action";
1207  
1208        switch ($o['type']) {
1209  
1210        case  'ilink':
1211  
1212          // there will be an 'label' setting, this is the name of the destination
1213          $res.= "\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
1214  
1215          break;
1216  
1217        case  'URI':
1218  
1219          $res.= "\n/S /URI\n/URI (";
1220  
1221          if  ($this->encrypted) {
1222  
1223            $res.= $this->filterText($this->ARC4($o['info']));
1224          } else {
1225  
1226            $res.= $this->filterText($o['info']);
1227          }
1228  
1229          $res.= ")";
1230  
1231          break;
1232        }
1233  
1234        $res.= "\n>>\nendobj";
1235  
1236        return  $res;
1237  
1238        break;
1239      }
1240    }
1241  
1242  
1243    /**
1244     * an annotation object, this will add an annotation to the current page.
1245     * initially will support just link annotations
1246     */
1247    function  o_annotation($id, $action, $options = '') {
1248  
1249      if  ($action != 'new') {
1250  
1251        $o = & $this->objects[$id];
1252      }
1253  
1254      switch  ($action) {
1255  
1256      case  'new':
1257  
1258        // add the annotation to the current page
1259        $pageId =  $this->currentPage;
1260  
1261        $this->o_page($pageId, 'annot', $id);
1262  
1263        // and add the action object which is going to be required
1264        switch ($options['type']) {
1265  
1266        case  'link':
1267  
1268          $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
1269  
1270          $this->numObj++;
1271  
1272          $this->o_action($this->numObj, 'new', $options['url']);
1273  
1274          $this->objects[$id]['info']['actionId'] = $this->numObj;
1275  
1276          break;
1277  
1278        case  'ilink':
1279  
1280          // this is to a named internal link
1281          $label =  $options['label'];
1282  
1283          $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
1284  
1285          $this->numObj++;
1286  
1287          $this->o_action($this->numObj, 'new', array('type'=>'ilink', 'label'=>$label));
1288  
1289          $this->objects[$id]['info']['actionId'] = $this->numObj;
1290  
1291          break;
1292        }
1293  
1294        break;
1295  
1296      case  'out':
1297  
1298        $res = "\n".$id." 0 obj\n<< /Type /Annot";
1299  
1300        switch ($o['info']['type']) {
1301  
1302        case  'link':
1303  
1304        case  'ilink':
1305  
1306          $res.=  "\n/Subtype /Link";
1307  
1308          break;
1309        }
1310  
1311        $res.= "\n/A ".$o['info']['actionId']." 0 R";
1312  
1313        $res.= "\n/Border [0 0 0]";
1314  
1315        $res.= "\n/H /I";
1316  
1317        $res.= "\n/Rect [ ";
1318  
1319        foreach($o['info']['rect'] as  $v) {
1320  
1321          $res.=  sprintf("%.4f ", $v);
1322        }
1323  
1324        $res.= "]";
1325  
1326        $res.= "\n>>\nendobj";
1327  
1328        return  $res;
1329  
1330        break;
1331      }
1332    }
1333  
1334  
1335    /**
1336     * a page object, it also creates a contents object to hold its contents
1337     */
1338    function  o_page($id, $action, $options = '') {
1339  
1340      if  ($action != 'new') {
1341  
1342        $o = & $this->objects[$id];
1343      }
1344  
1345      switch  ($action) {
1346  
1347      case  'new':
1348  
1349        $this->numPages++;
1350  
1351        $this->objects[$id] = array('t'=>'page', 'info'=>array('parent'=>$this->currentNode, 'pageNum'=>$this->numPages));
1352  
1353        if  (is_array($options)) {
1354  
1355          // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
1356          $options['id'] = $id;
1357  
1358          $this->o_pages($this->currentNode, 'page', $options);
1359        } else {
1360  
1361          $this->o_pages($this->currentNode, 'page', $id);
1362        }
1363  
1364        $this->currentPage = $id;
1365  
1366        //make a contents object to go with this page
1367        $this->numObj++;
1368  
1369        $this->o_contents($this->numObj, 'new', $id);
1370  
1371        $this->currentContents = $this->numObj;
1372  
1373        $this->objects[$id]['info']['contents'] = array();
1374  
1375        $this->objects[$id]['info']['contents'][] = $this->numObj;
1376  
1377        $match =  ($this->numPages%2 ?  'odd' :  'even');
1378  
1379        foreach($this->addLooseObjects as  $oId=>$target) {
1380  
1381          if  ($target == 'all' ||  $match == $target) {
1382  
1383            $this->objects[$id]['info']['contents'][] = $oId;
1384          }
1385        }
1386  
1387        break;
1388  
1389      case  'content':
1390  
1391        $o['info']['contents'][] = $options;
1392  
1393        break;
1394  
1395      case  'annot':
1396  
1397        // add an annotation to this page
1398        if  (!isset($o['info']['annot'])) {
1399  
1400          $o['info']['annot'] = array();
1401        }
1402  
1403        // $options should contain the id of the annotation dictionary
1404        $o['info']['annot'][] = $options;
1405  
1406        break;
1407  
1408      case  'out':
1409  
1410        $res = "\n".$id." 0 obj\n<< /Type /Page";
1411  
1412        $res.= "\n/Parent ".$o['info']['parent']." 0 R";
1413  
1414        if  (isset($o['info']['annot'])) {
1415  
1416          $res.= "\n/Annots [";
1417  
1418          foreach($o['info']['annot'] as  $aId) {
1419  
1420            $res.= " ".$aId." 0 R";
1421          }
1422  
1423          $res.= " ]";
1424        }
1425  
1426        $count =  count($o['info']['contents']);
1427  
1428        if  ($count == 1) {
1429  
1430          $res.= "\n/Contents ".$o['info']['contents'][0]." 0 R";
1431        } else  if  ($count>1) {
1432  
1433          $res.= "\n/Contents [\n";
1434  
1435          // reverse the page contents so added objects are below normal content
1436          //foreach (array_reverse($o['info']['contents']) as  $cId) {
1437  
1438          // Back to normal now that I've got transparency working --Benj
1439          foreach ($o['info']['contents'] as  $cId) {
1440            $res.= $cId." 0 R\n";
1441          }
1442  
1443          $res.= "]";
1444        }
1445  
1446        $res.= "\n>>\nendobj";
1447  
1448        return  $res;
1449  
1450        break;
1451      }
1452    }
1453  
1454  
1455    /**
1456     * the contents objects hold all of the content which appears on pages
1457     */
1458    function  o_contents($id, $action, $options = '') {
1459  
1460      if  ($action != 'new') {
1461  
1462        $o = & $this->objects[$id];
1463      }
1464  
1465      switch  ($action) {
1466  
1467      case  'new':
1468  
1469        $this->objects[$id] = array('t'=>'contents', 'c'=>'', 'info'=>array());
1470  
1471        if  (strlen($options) &&  intval($options)) {
1472  
1473          // then this contents is the primary for a page
1474          $this->objects[$id]['onPage'] = $options;
1475        } else  if  ($options == 'raw') {
1476  
1477          // then this page contains some other type of system object
1478          $this->objects[$id]['raw'] = 1;
1479        }
1480  
1481        break;
1482  
1483      case  'add':
1484  
1485        // add more options to the decleration
1486        foreach ($options as  $k=>$v) {
1487  
1488          $o['info'][$k] = $v;
1489        }
1490  
1491      case  'out':
1492  
1493        $tmp = $o['c'];
1494  
1495        $res =  "\n".$id." 0 obj\n";
1496  
1497        if  (isset($this->objects[$id]['raw'])) {
1498  
1499          $res.= $tmp;
1500        } else {
1501  
1502          $res.=  "<<";
1503  
1504          if  (function_exists('gzcompress') &&  $this->options['compression']) {
1505  
1506            // then implement ZLIB based compression on this content stream
1507            $res.= " /Filter /FlateDecode";
1508  
1509            $tmp =  gzcompress($tmp,  6);
1510          }
1511  
1512          if  ($this->encrypted) {
1513  
1514            $this->encryptInit($id);
1515  
1516            $tmp =  $this->ARC4($tmp);
1517          }
1518  
1519          foreach($o['info'] as  $k=>$v) {
1520  
1521            $res.=  "\n/".$k.' '.$v;
1522          }
1523  
1524          $res.= "\n/Length ".strlen($tmp) ." >>\nstream\n".$tmp."\nendstream";
1525        }
1526  
1527        $res.= "\nendobj\n";
1528  
1529        return  $res;
1530  
1531        break;
1532      }
1533    }
1534  
1535  
1536    /**
1537     * an image object, will be an XObject in the document, includes description and data
1538     */
1539    function  o_image($id, $action, $options = '') {
1540  
1541      if  ($action != 'new') {
1542  
1543        $o = & $this->objects[$id];
1544      }
1545  
1546      switch ($action) {
1547  
1548      case  'new':
1549  
1550        // make the new object
1551        $this->objects[$id] = array('t'=>'image', 'data'=>&$options['data'], 'info'=>array());
1552  
1553        $this->objects[$id]['info']['Type'] = '/XObject';
1554  
1555        $this->objects[$id]['info']['Subtype'] = '/Image';
1556  
1557        $this->objects[$id]['info']['Width'] = $options['iw'];
1558  
1559        $this->objects[$id]['info']['Height'] = $options['ih'];
1560  
1561        if  (!isset($options['type']) ||  $options['type'] == 'jpg') {
1562  
1563          if  (!isset($options['channels'])) {
1564  
1565            $options['channels'] = 3;
1566          }
1567  
1568          switch ($options['channels']) {
1569  
1570          case  1:
1571  
1572            $this->objects[$id]['info']['ColorSpace'] = '/DeviceGray';
1573  
1574            break;
1575  
1576          default:
1577  
1578            $this->objects[$id]['info']['ColorSpace'] = '/DeviceRGB';
1579  
1580            break;
1581          }
1582  
1583          $this->objects[$id]['info']['Filter'] = '/DCTDecode';
1584  
1585          $this->objects[$id]['info']['BitsPerComponent'] = 8;
1586        } else  if  ($options['type'] == 'png') {
1587          
1588          $this->objects[$id]['info']['Filter'] = '/FlateDecode';
1589  
1590          $this->objects[$id]['info']['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
1591          if  (strlen($options['pdata'])) {
1592  
1593            $tmp =  ' [ /Indexed /DeviceRGB '.(strlen($options['pdata']) /3-1) .' ';
1594  
1595            $this->numObj++;
1596  
1597            $this->o_contents($this->numObj, 'new');
1598  
1599            $this->objects[$this->numObj]['c'] = $options['pdata'];
1600  
1601            $tmp.= $this->numObj.' 0 R';
1602  
1603            $tmp.= ' ]';
1604  
1605            $this->objects[$id]['info']['ColorSpace'] =  $tmp;
1606  
1607            if  (isset($options['transparency'])) {
1608  
1609              switch ($options['transparency']['type']) {
1610  
1611              case  'indexed':
1612  
1613                $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
1614  
1615                $this->objects[$id]['info']['Mask'] =  $tmp;
1616  
1617                break;
1618  
1619              case 'color-key':
1620                $tmp = ' [ '.
1621                  $options['transparency']['r'] . ' ' . $options['transparency']['r'] .
1622                  $options['transparency']['g'] . ' ' . $options['transparency']['g'] .
1623                  $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
1624                  ' ] ';
1625                $this->objects[$id]['info']['Mask'] = $tmp;
1626                pre_r($tmp);
1627                break;
1628                  
1629              }
1630            }
1631          } else {
1632  
1633            if  (isset($options['transparency'])) {
1634  
1635              switch ($options['transparency']['type']) {
1636  
1637              case  'indexed':
1638  
1639                $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
1640  
1641                $this->objects[$id]['info']['Mask'] =  $tmp;
1642  
1643                break;
1644  
1645              case 'color-key':
1646                $tmp = ' [ '.
1647                  $options['transparency']['r'] . ' ' . $options['transparency']['r'] . ' ' .
1648                  $options['transparency']['g'] . ' ' . $options['transparency']['g'] . ' ' .
1649                  $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
1650                  ' ] ';
1651                $this->objects[$id]['info']['Mask'] = $tmp;
1652                break;
1653                  
1654              }
1655            }
1656            $this->objects[$id]['info']['ColorSpace'] = '/'.$options['color'];
1657          }
1658  
1659          $this->objects[$id]['info']['BitsPerComponent'] = $options['bitsPerComponent'];
1660        }
1661  
1662        // assign it a place in the named resource dictionary as an external object, according to
1663        // the label passed in with it.
1664        $this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'], 'objNum'=>$id));
1665  
1666        // also make sure that we have the right procset object for it.
1667        $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
1668  
1669        break;
1670  
1671      case  'out':
1672  
1673        $tmp = &$o['data'];
1674  
1675        $res =  "\n".$id." 0 obj\n<<";
1676  
1677        foreach($o['info'] as  $k=>$v) {
1678  
1679          $res.= "\n/".$k.' '.$v;
1680        }
1681  
1682        if  ($this->encrypted) {
1683  
1684          $this->encryptInit($id);
1685  
1686          $tmp =  $this->ARC4($tmp);
1687        }
1688  
1689        $res.= "\n/Length ".strlen($tmp) ." >>\nstream\n".$tmp."\nendstream\nendobj\n";
1690  
1691        return  $res;
1692  
1693        break;
1694      }
1695    }
1696  
1697  
1698    /**
1699     * graphics state object
1700     */
1701    function  o_extGState($id,  $action,  $options = "") {
1702  
1703      static  $valid_params =  array("LW",  "LC",  "LC",  "LJ",  "ML",
1704                                     "D",  "RI",  "OP",  "op",  "OPM",
1705                                     "Font",  "BG",  "BG2",  "UCR",
1706                                     "TR",  "TR2",  "HT",  "FL",
1707                                     "SM",  "SA",  "BM",  "SMask",
1708                                     "CA",  "ca",  "AIS",  "TK");
1709  
1710      if  ( $action !=  "new") {
1711        $o = & $this->objects[$id];
1712      }
1713  
1714      switch  ($action) {
1715  
1716      case  "new":
1717        $this->objects[$id] =  array('t' => 'extGState',  'info' => $options);
1718  
1719        // Tell the pages about the new resource
1720        $this->numStates++;
1721        $this->o_pages($this->currentNode,  'extGState',  array("objNum" => $id,  "stateNum" => $this->numStates));
1722        break;
1723  
1724  
1725      case  "out":
1726        $res =
1727          "\n" . $id . " 0 obj\n".
1728          "<< /Type /ExtGState\n";
1729  
1730        foreach ($o["info"] as  $parameter => $value) {
1731          if  ( !in_array($parameter,  $valid_params))
1732            continue;
1733          $res.=  "/$parameter $value\n";
1734        }
1735  
1736        $res.=
1737          ">>\n".
1738          "endobj";
1739  
1740        return  $res;
1741      }
1742    }
1743  
1744  
1745    /**
1746     * encryption object.
1747     */
1748    function  o_encryption($id, $action, $options = '') {
1749  
1750      if  ($action != 'new') {
1751  
1752        $o = & $this->objects[$id];
1753      }
1754  
1755      switch ($action) {
1756  
1757      case  'new':
1758  
1759        // make the new object
1760        $this->objects[$id] = array('t'=>'encryption', 'info'=>$options);
1761  
1762        $this->arc4_objnum = $id;
1763  
1764        // figure out the additional paramaters required
1765        $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);
1766  
1767        $len =  strlen($options['owner']);
1768  
1769        if  ($len>32) {
1770  
1771          $owner =  substr($options['owner'], 0, 32);
1772        } else  if  ($len<32) {
1773  
1774          $owner =  $options['owner'].substr($pad, 0, 32-$len);
1775        } else {
1776  
1777          $owner =  $options['owner'];
1778        }
1779  
1780        $len =  strlen($options['user']);
1781  
1782        if  ($len>32) {
1783  
1784          $user =  substr($options['user'], 0, 32);
1785        } else  if  ($len<32) {
1786  
1787          $user =  $options['user'].substr($pad, 0, 32-$len);
1788        } else {
1789  
1790          $user =  $options['user'];
1791        }
1792  
1793        $tmp =  $this->md5_16($owner);
1794  
1795        $okey =  substr($tmp, 0, 5);
1796  
1797        $this->ARC4_init($okey);
1798  
1799        $ovalue = $this->ARC4($user);
1800  
1801        $this->objects[$id]['info']['O'] = $ovalue;
1802  
1803        // now make the u value, phew.
1804        $tmp =  $this->md5_16($user.$ovalue.chr($options['p']) .chr(255) .chr(255) .chr(255) .$this->fileIdentifier);
1805  
1806        $ukey =  substr($tmp, 0, 5);
1807  
1808  
1809        $this->ARC4_init($ukey);
1810  
1811        $this->encryptionKey =  $ukey;
1812  
1813        $this->encrypted = 1;
1814  
1815        $uvalue = $this->ARC4($pad);
1816  
1817  
1818        $this->objects[$id]['info']['U'] = $uvalue;
1819  
1820        $this->encryptionKey = $ukey;
1821  
1822  
1823        // initialize the arc4 array
1824        break;
1825  
1826      case  'out':
1827  
1828        $res =  "\n".$id." 0 obj\n<<";
1829  
1830        $res.= "\n/Filter /Standard";
1831  
1832        $res.= "\n/V 1";
1833  
1834        $res.= "\n/R 2";
1835  
1836        $res.= "\n/O (".$this->filterText($o['info']['O']) .')';
1837  
1838        $res.= "\n/U (".$this->filterText($o['info']['U']) .')';
1839  
1840        // and the p-value needs to be converted to account for the twos-complement approach
1841        $o['info']['p'] =  (($o['info']['p']^255) +1) *-1;
1842  
1843        $res.= "\n/P ".($o['info']['p']);
1844  
1845        $res.= "\n>>\nendobj\n";
1846  
1847  
1848        return  $res;
1849  
1850        break;
1851      }
1852    }
1853  
1854  
1855    /**
1856     * ARC4 functions
1857     * A series of function to implement ARC4 encoding in PHP
1858     */
1859  
1860    /**
1861     * calculate the 16 byte version of the 128 bit md5 digest of the string
1862     */
1863    function  md5_16($string) {
1864  
1865      $tmp =  md5($string);
1866  
1867      $out = '';
1868  
1869      for  ($i = 0;$i <= 30;$i = $i+2) {
1870  
1871        $out.= chr(hexdec(substr($tmp, $i, 2)));
1872      }
1873  
1874      return  $out;
1875    }
1876  
1877  
1878    /**
1879     * initialize the encryption for processing a particular object
1880     */
1881    function  encryptInit($id) {
1882  
1883      $tmp =  $this->encryptionKey;
1884  
1885      $hex =  dechex($id);
1886  
1887      if  (strlen($hex) <6) {
1888  
1889        $hex =  substr('000000', 0, 6-strlen($hex)) .$hex;
1890      }
1891  
1892      $tmp.=  chr(hexdec(substr($hex, 4, 2))) .chr(hexdec(substr($hex, 2, 2))) .chr(hexdec(substr($hex, 0, 2))) .chr(0) .chr(0);
1893  
1894      $key =  $this->md5_16($tmp);
1895  
1896      $this->ARC4_init(substr($key, 0, 10));
1897    }
1898  
1899  
1900    /**
1901     * initialize the ARC4 encryption
1902     */
1903    function  ARC4_init($key = '') {
1904  
1905      $this->arc4 =  '';
1906  
1907      // setup the control array
1908      if  (strlen($key) == 0) {
1909  
1910        return;
1911      }
1912  
1913      $k =  '';
1914  
1915      while (strlen($k) <256) {
1916  
1917        $k.= $key;
1918      }
1919  
1920      $k = substr($k, 0, 256);
1921  
1922      for  ($i = 0;$i<256;$i++) {
1923  
1924        $this->arc4.=  chr($i);
1925      }
1926  
1927      $j = 0;
1928  
1929      for  ($i = 0;$i<256;$i++) {
1930  
1931        $t =  $this->arc4[$i];
1932  
1933        $j =  ($j + ord($t)  + ord($k[$i])) %256;
1934  
1935        $this->arc4[$i] = $this->arc4[$j];
1936  
1937        $this->arc4[$j] = $t;
1938      }
1939    }
1940  
1941  
1942    /**
1943     * ARC4 encrypt a text string
1944     */
1945    function  ARC4($text) {
1946  
1947      $len = strlen($text);
1948  
1949      $a = 0;
1950  
1951      $b = 0;
1952  
1953      $c =  $this->arc4;
1954  
1955      $out = '';
1956  
1957      for  ($i = 0;$i<$len;$i++) {
1958  
1959        $a =  ($a+1) %256;
1960  
1961        $t =  $c[$a];
1962  
1963        $b =  ($b+ord($t)) %256;
1964  
1965        $c[$a] = $c[$b];
1966  
1967        $c[$b] = $t;
1968  
1969        $k =  ord($c[(ord($c[$a]) +ord($c[$b])) %256]);
1970  
1971        $out.= chr(ord($text[$i])  ^ $k);
1972      }
1973  
1974  
1975      return  $out;
1976    }
1977  
1978  
1979    /**
1980     * functions which can be called to adjust or add to the document
1981     */
1982  
1983    /**
1984     * add a link in the document to an external URL
1985     */
1986    function  addLink($url, $x0, $y0, $x1, $y1) {
1987  
1988      $this->numObj++;
1989  
1990      $info =  array('type'=>'link', 'url'=>$url, 'rect'=>array($x0, $y0, $x1, $y1));
1991  
1992      $this->o_annotation($this->numObj, 'new', $info);
1993    }
1994  
1995  
1996    /**
1997     * add a link in the document to an internal destination (ie. within the document)
1998     */
1999    function  addInternalLink($label, $x0, $y0, $x1, $y1) {
2000  
2001      $this->numObj++;
2002  
2003      $info =  array('type'=>'ilink', 'label'=>$label, 'rect'=>array($x0, $y0, $x1, $y1));
2004  
2005      $this->o_annotation($this->numObj, 'new', $info);
2006    }
2007  
2008  
2009    /**
2010     * set the encryption of the document
2011     * can be used to turn it on and/or set the passwords which it will have.
2012     * also the functions that the user will have are set here, such as print, modify, add
2013     */
2014    function  setEncryption($userPass = '', $ownerPass = '', $pc = array()) {
2015  
2016      $p = bindec(11000000);
2017  
2018  
2019      $options =  array(
2020                        'print'=>4, 'modify'=>8, 'copy'=>16, 'add'=>32);
2021  
2022      foreach($pc as  $k=>$v) {
2023  
2024        if  ($v &&  isset($options[$k])) {
2025  
2026          $p+= $options[$k];
2027        } else  if  (isset($options[$v])) {
2028  
2029          $p+= $options[$v];
2030        }
2031      }
2032  
2033      // implement encryption on the document
2034      if  ($this->arc4_objnum ==  0) {
2035  
2036        // then the block does not exist already, add it.
2037        $this->numObj++;
2038  
2039        if  (strlen($ownerPass) == 0) {
2040  
2041          $ownerPass = $userPass;
2042        }
2043  
2044        $this->o_encryption($this->numObj, 'new', array('user'=>$userPass, 'owner'=>$ownerPass, 'p'=>$p));
2045      }
2046    }
2047  
2048  
2049    /**
2050     * should be used for internal checks, not implemented as yet
2051     */
2052    function  checkAllHere() {
2053    }
2054  
2055  
2056    /**
2057     * return the pdf stream as a string returned from the function
2058     */
2059    function  output($debug = 0) {
2060  
2061  
2062      if  ($debug) {
2063  
2064        // turn compression off
2065        $this->options['compression'] = 0;
2066      }
2067  
2068  
2069      if  ($this->arc4_objnum) {
2070  
2071        $this->ARC4_init($this->encryptionKey);
2072      }
2073  
2074  
2075      $this->checkAllHere();
2076  
2077  
2078      $xref = array();
2079  
2080      $content = "%PDF-1.3\n%\n";
2081  
2082      //  $content="%PDF-1.3\n";
2083      $pos = strlen($content);
2084  
2085      foreach($this->objects as  $k=>$v) {
2086  
2087        $tmp = 'o_'.$v['t'];
2088  
2089        $cont = $this->$tmp($k, 'out');
2090  
2091        $content.= $cont;
2092  
2093        $xref[] = $pos;
2094  
2095        $pos+= strlen($cont);
2096      }
2097  
2098      $content.= "\nxref\n0 ".(count($xref) +1) ."\n0000000000 65535 f \n";
2099  
2100      foreach($xref as  $p) {
2101  
2102        $content.= str_pad($p,  10,  "0",  STR_PAD_LEFT)  . " 00000 n \n";
2103      }
2104  
2105      $content.= "trailer\n<<\n/Size ".(count($xref) +1) ."\n/Root 1 0 R\n/Info ".$this->infoObject." 0 R\n";
2106  
2107      // if encryption has been applied to this document then add the marker for this dictionary
2108      if  ($this->arc4_objnum > 0) {
2109  
2110        $content.=  "/Encrypt ".$this->arc4_objnum." 0 R\n";
2111      }
2112  
2113      if  (strlen($this->fileIdentifier)) {
2114  
2115        $content.=  "/ID[<".$this->fileIdentifier."><".$this->fileIdentifier.">]\n";
2116      }
2117  
2118      $content.=  ">>\nstartxref\n".$pos."\n%%EOF\n";
2119  
2120      return  $content;
2121    }
2122  
2123  
2124    /**
2125     * intialize a new document
2126     * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
2127     * this function is called automatically by the constructor function
2128     *
2129     * @access private
2130     */
2131    function  newDocument($pageSize = array(0, 0, 612, 792)) {
2132  
2133      $this->numObj = 0;
2134  
2135      $this->objects =  array();
2136  
2137  
2138      $this->numObj++;
2139  
2140      $this->o_catalog($this->numObj, 'new');
2141  
2142  
2143      $this->numObj++;
2144  
2145      $this->o_outlines($this->numObj, 'new');
2146  
2147  
2148      $this->numObj++;
2149  
2150      $this->o_pages($this->numObj, 'new');
2151  
2152  
2153      $this->o_pages($this->numObj, 'mediaBox', $pageSize);
2154  
2155      $this->currentNode =  3;
2156  
2157  
2158      $this->numObj++;
2159  
2160      $this->o_procset($this->numObj, 'new');
2161  
2162  
2163      $this->numObj++;
2164  
2165      $this->o_info($this->numObj, 'new');
2166  
2167  
2168      $this->numObj++;
2169  
2170      $this->o_page($this->numObj, 'new');
2171  
2172  
2173      // need to store the first page id as there is no way to get it to the user during
2174      // startup
2175      $this->firstPageId =  $this->currentContents;
2176    }
2177  
2178  
2179    /**
2180     * open the font file and return a php structure containing it.
2181     * first check if this one has been done before and saved in a form more suited to php
2182     * note that if a php serialized version does not exist it will try and make one, but will
2183     * require write access to the directory to do it... it is MUCH faster to have these serialized
2184     * files.
2185     *
2186     * @access private
2187     */
2188    function  openFont($font) {
2189  
2190      // assume that $font contains both the path and perhaps the extension to the file, split them
2191      $pos = strrpos($font, '/');
2192  
2193      if  ($pos === false) {
2194  
2195        $dir =  './';
2196  
2197        $name =  $font;
2198      } else {
2199  
2200        $dir = substr($font, 0, $pos+1);
2201  
2202        $name = substr($font, $pos+1);
2203      }
2204  
2205  
2206      if  (substr($name, -4) == '.afm') {
2207  
2208        $name = substr($name, 0, strlen($name) -4);
2209      }
2210  
2211      $this->addMessage('openFont: '.$font.' - '.$name);
2212  
2213      if  (file_exists($dir . 'php_' . $name . '.afm')) {
2214  
2215        $this->addMessage('openFont: php file exists ' . $dir . 'php_' . $name.'.afm');
2216  
2217        $tmp =  file_get_contents($dir.'php_'.$name.'.afm');
2218  
2219        eval($tmp);
2220  
2221        if  (!isset($this->fonts[$font]['_version_']) ||  $this->fonts[$font]['_version_']<1) {
2222  
2223          // if the font file is old, then clear it out and prepare for re-creation
2224          $this->addMessage('openFont: clear out, make way for new version.');
2225  
2226          unset($this->fonts[$font]);
2227        }
2228      }
2229  
2230      if  (!isset($this->fonts[$font]) &&  file_exists($dir.$name.'.afm')) {
2231  
2232        // then rebuild the php_<font>.afm file from the <font>.afm file
2233        $this->addMessage('openFont: build php file from '.$dir.$name.'.afm');
2234  
2235        $data =  array();
2236  
2237        $file =  file($dir.$name.'.afm');
2238  
2239        foreach ($file as  $rowA) {
2240  
2241          $row = trim($rowA);
2242  
2243          $pos = strpos($row, ' ');
2244  
2245          if  ($pos) {
2246  
2247            // then there must be some keyword
2248            $key =  substr($row, 0, $pos);
2249  
2250            switch  ($key) {
2251  
2252            case  'FontName':
2253  
2254            case  'FullName':
2255  
2256            case  'FamilyName':
2257  
2258            case  'Weight':
2259  
2260            case  'ItalicAngle':
2261  
2262            case  'IsFixedPitch':
2263  
2264            case  'CharacterSet':
2265  
2266            case  'UnderlinePosition':
2267  
2268            case  'UnderlineThickness':
2269  
2270            case  'Version':
2271  
2272            case  'EncodingScheme':
2273  
2274            case  'CapHeight':
2275  
2276            case  'XHeight':
2277  
2278            case  'Ascender':
2279  
2280            case  'Descender':
2281  
2282            case  'StdHW':
2283  
2284            case  'StdVW':
2285  
2286            case  'StartCharMetrics':
2287  
2288              $data[$key] = trim(substr($row, $pos));
2289  
2290              break;
2291  
2292            case  'FontBBox':
2293  
2294              $data[$key] = explode(' ', trim(substr($row, $pos)));
2295  
2296              break;
2297  
2298            case  'C':
2299  
2300              //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
2301              $bits = explode(';', trim($row));
2302  
2303              $dtmp = array();
2304  
2305              foreach($bits as  $bit) {
2306  
2307                $bits2 =  explode(' ', trim($bit));
2308  
2309                if  (strlen($bits2[0])) {
2310  
2311                  if  (count($bits2) >2) {
2312  
2313                    $dtmp[$bits2[0]] = array();
2314  
2315                    for  ($i = 1;$i<count($bits2);$i++) {
2316  
2317                      $dtmp[$bits2[0]][] = $bits2[$i];
2318                    }
2319                  } else  if  (count($bits2) == 2) {
2320  
2321                    $dtmp[$bits2[0]] = $bits2[1];
2322                  }
2323                }
2324              }
2325  
2326              if  ($dtmp['C'] >= 0) {
2327  
2328                $data['C'][$dtmp['C']] = $dtmp;
2329  
2330                $data['C'][$dtmp['N']] = $dtmp;
2331              } else {
2332  
2333                $data['C'][$dtmp['N']] = $dtmp;
2334              }
2335  
2336              break;
2337  
2338            case  'KPX':
2339  
2340              //KPX Adieresis yacute -40
2341              $bits = explode(' ', trim($row));
2342  
2343              $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
2344  
2345              break;
2346            }
2347          }
2348        }
2349  
2350        $data['_version_'] = 1;
2351  
2352        $this->fonts[$font] = $data;
2353  
2354        file_put_contents($dir . 'php_' . $name . '.afm',  '$this->fonts[$font]=' . var_export($data,  true)  . ';');
2355      } else  if  (!isset($this->fonts[$font])) {
2356  
2357        $this->addMessage('openFont: no font file found');
2358  
2359        //    echo 'Font not Found '.$font;
2360  
2361      }
2362    }
2363  
2364  
2365    /**
2366     * if the font is not loaded then load it and make the required object
2367     * else just make it the current font
2368     * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
2369     * note that encoding='none' will need to be used for symbolic fonts
2370     * and 'differences' => an array of mappings between numbers 0->255 and character names.
2371     *
2372     */
2373    function  selectFont($fontName, $encoding =  '', $set =  1) {
2374  
2375      if  (!isset($this->fonts[$fontName])) {
2376  
2377        // load the file
2378        $this->openFont($fontName);
2379  
2380        if  (isset($this->fonts[$fontName])) {
2381  
2382          $this->numObj++;
2383  
2384          $this->numFonts++;
2385  
2386          //$this->numFonts = md5($fontName);
2387          $pos =  strrpos($fontName, '/');
2388  
2389          //      $dir = substr($fontName,0,$pos+1);
2390          $name =  substr($fontName, $pos+1);
2391  
2392          if  (substr($name, -4) ==  '.afm') {
2393  
2394            $name =  substr($name, 0, strlen($name) -4);
2395          }
2396  
2397  
2398          $options =  array('name' => $name);
2399  
2400          if  (is_array($encoding)) {
2401  
2402            // then encoding and differences might be set
2403            if  (isset($encoding['encoding'])) {
2404  
2405              $options['encoding'] =  $encoding['encoding'];
2406            }
2407  
2408            if  (isset($encoding['differences'])) {
2409  
2410              $options['differences'] =  $encoding['differences'];
2411            }
2412          } else  if  (strlen($encoding)) {
2413  
2414            // then perhaps only the encoding has been set
2415            $options['encoding'] =  $encoding;
2416          }
2417  
2418  
2419          $fontObj =  $this->numObj;
2420  
2421          $this->o_font($this->numObj, 'new', $options);
2422  
2423          $this->fonts[$fontName]['fontNum'] =  $this->numFonts;
2424  
2425  
2426          // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there
2427          // should be for all non-basic fonts), then load it into an object and put the
2428          // references into the font object
2429          $basefile =  substr($fontName, 0, strlen($fontName) -4);
2430  
2431          if  (file_exists($basefile.'.pfb')) {
2432  
2433            $fbtype =  'pfb';
2434          } else  if  (file_exists($basefile.'.ttf')) {
2435  
2436            $fbtype =  'ttf';
2437          } else {
2438  
2439            $fbtype =  '';
2440          }
2441  
2442          $fbfile =  $basefile.'.'.$fbtype;
2443  
2444  
2445          //      $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb';
2446          //      $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf';
2447          $this->addMessage('selectFont: checking for - '.$fbfile);
2448  
2449  
2450          if  (substr($fontName, -4) ==  '.afm' &&  strlen($fbtype)) {
2451  
2452            $adobeFontName =  $this->fonts[$fontName]['FontName'];
2453  
2454            //        $fontObj = $this->numObj;
2455            $this->addMessage('selectFont: adding font file - '.$fbfile.' - '.$adobeFontName);
2456  
2457            // find the array of fond widths, and put that into an object.
2458            $firstChar =  -1;
2459  
2460            $lastChar =  0;
2461  
2462            $widths =  array();
2463  
2464            foreach ($this->fonts[$fontName]['C'] as  $num => $d) {
2465  
2466              if  (intval($num) >0 ||  $num ==  '0') {
2467  
2468                if  ($lastChar>0 &&  $num>$lastChar+1) {
2469  
2470                  for ($i =  $lastChar+1;$i<$num;$i++) {
2471  
2472                    $widths[] =  0;
2473                  }
2474                }
2475  
2476                $widths[] =  $d['WX'];
2477  
2478                if  ($firstChar ==  -1) {
2479  
2480                  $firstChar =  $num;
2481                }
2482  
2483                $lastChar =  $num;
2484              }
2485            }
2486  
2487            // also need to adjust the widths for the differences array
2488            if  (isset($options['differences'])) {
2489  
2490              foreach($options['differences'] as  $charNum => $charName) {
2491  
2492                if  ($charNum > $lastChar) {
2493  
2494                  for ($i =  $lastChar + 1; $i <=  $charNum; $i++) {
2495  
2496                    $widths[] =  0;
2497                  }
2498  
2499                  $lastChar =  $charNum;
2500                }
2501  
2502                if  (isset($this->fonts[$fontName]['C'][$charName])) {
2503  
2504                  $widths[$charNum-$firstChar] =  $this->fonts[$fontName]['C'][$charName]['WX'];
2505                }
2506              }
2507            }
2508  
2509            $this->addMessage('selectFont: FirstChar = '.$firstChar);
2510  
2511            $this->addMessage('selectFont: LastChar = '.$lastChar);
2512  
2513            $this->numObj++;
2514  
2515            $this->o_contents($this->numObj, 'new', 'raw');
2516  
2517            $this->objects[$this->numObj]['c'].=  '[';
2518  
2519            foreach($widths as  $width) {
2520  
2521              $this->objects[$this->numObj]['c'].=  ' '.$width;
2522            }
2523  
2524            $this->objects[$this->numObj]['c'].=  ' ]';
2525  
2526            $widthid =  $this->numObj;
2527  
2528  
2529            // load the pfb file, and put that into an object too.
2530            // note that pdf supports only binary format type 1 font files, though there is a
2531            // simple utility to convert them from pfa to pfb.
2532            $tmp =  get_magic_quotes_runtime();
2533  
2534            set_magic_quotes_runtime(0);
2535  
2536            $data =  file_get_contents($fbfile);
2537  
2538            set_magic_quotes_runtime($tmp);
2539  
2540  
2541            // create the font descriptor
2542            $this->numObj++;
2543  
2544            $fontDescriptorId =  $this->numObj;
2545  
2546            $this->numObj++;
2547  
2548            $pfbid =  $this->numObj;
2549  
2550            // determine flags (more than a little flakey, hopefully will not matter much)
2551            $flags =  0;
2552  
2553            if  ($this->fonts[$fontName]['ItalicAngle'] !=  0) {
2554              $flags+=  pow(2, 6);
2555            }
2556  
2557            if  ($this->fonts[$fontName]['IsFixedPitch'] ==  'true') {
2558              $flags+=  1;
2559            }
2560  
2561            $flags+=  pow(2, 5);
2562            // assume non-sybolic
2563  
2564            $list =  array('Ascent' => 'Ascender', 'CapHeight' => 'CapHeight', 'Descent' => 'Descender', 'FontBBox' => 'FontBBox', 'ItalicAngle' => 'ItalicAngle');
2565  
2566            $fdopt =  array(
2567                            'Flags' => $flags, 'FontName' => $adobeFontName, 'StemV' => 100  // don't know what the value for this should be!
2568                            );
2569  
2570            foreach($list as  $k => $v) {
2571  
2572              if  (isset($this->fonts[$fontName][$v])) {
2573  
2574                $fdopt[$k] =  $this->fonts[$fontName][$v];
2575              }
2576            }
2577  
2578  
2579            if  ($fbtype ==  'pfb') {
2580  
2581              $fdopt['FontFile'] =  $pfbid;
2582            } else  if  ($fbtype ==  'ttf') {
2583  
2584              $fdopt['FontFile2'] =  $pfbid;
2585            }
2586  
2587            $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
2588  
2589  
2590            // embed the font program
2591            $this->o_contents($this->numObj, 'new');
2592  
2593            $this->objects[$pfbid]['c'].=  $data;
2594  
2595            // determine the cruicial lengths within this file
2596            if  ($fbtype ==  'pfb') {
2597  
2598              $l1 =  strpos($data, 'eexec') +6;
2599  
2600              $l2 =  strpos($data, '00000000') -$l1;
2601  
2602              $l3 =  strlen($data) -$l2-$l1;
2603  
2604              $this->o_contents($this->numObj, 'add', array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3));
2605            } else  if  ($fbtype ==  'ttf') {
2606  
2607              $l1 =  strlen($data);
2608  
2609              $this->o_contents($this->numObj, 'add', array('Length1' => $l1));
2610            }
2611  
2612  
2613  
2614            // tell the font object about all this new stuff
2615            $tmp =  array('BaseFont' => $adobeFontName, 'Widths' => $widthid, 'FirstChar' => $firstChar, 'LastChar' => $lastChar, 'FontDescriptor' => $fontDescriptorId);
2616  
2617            if  ($fbtype ==  'ttf') {
2618  
2619              $tmp['SubType'] =  'TrueType';
2620            }
2621  
2622            $this->addMessage('adding extra info to font.('.$fontObj.')');
2623  
2624            foreach($tmp as  $fk => $fv) {
2625  
2626              $this->addMessage($fk." : ".$fv);
2627            }
2628  
2629            $this->o_font($fontObj, 'add', $tmp);
2630          } else {
2631  
2632            $this->addMessage('selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts');
2633          }
2634  
2635  
2636  
2637          // also set the differences here, note that this means that these will take effect only the
2638          //first time that a font is selected, else they are ignored
2639          if  (isset($options['differences'])) {
2640  
2641            $this->fonts[$fontName]['differences'] =  $options['differences'];
2642          }
2643        }
2644      }
2645  
2646      if  ($set &&  isset($this->fonts[$fontName])) {
2647  
2648        // so if for some reason the font was not set in the last one then it will not be selected
2649        $this->currentBaseFont =  $fontName;
2650  
2651        // the next lines mean that if a new font is selected, then the current text state will be
2652        // applied to it as well.
2653        $this->currentFont =  $this->currentBaseFont;
2654  
2655        $this->currentFontNum =  $this->fonts[$this->currentFont]['fontNum'];
2656  
2657        //$this->setCurrentFont();
2658  
2659      }
2660  
2661      return  $this->currentFontNum;
2662  
2663      //return $this->numObj;
2664  
2665    }
2666  
2667  
2668    /**
2669     * sets up the current font, based on the font families, and the current text state
2670     * note that this system is quite flexible, a bold-italic font can be completely different to a
2671     * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
2672     * This function is to be called whenever the currentTextState is changed, it will update
2673     * the currentFont setting to whatever the appropriatte family one is.
2674     * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
2675     * This function will change the currentFont to whatever it should be, but will not change the
2676     * currentBaseFont.
2677     *
2678     * @access private
2679     */
2680    function  setCurrentFont() {
2681  
2682      //   if (strlen($this->currentBaseFont) == 0){
2683      //     // then assume an initial font
2684      //     $this->selectFont('./fonts/Helvetica.afm');
2685      //   }
2686      //   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
2687      //   if (strlen($this->currentTextState)
2688      //     && isset($this->fontFamilies[$cf])
2689      //       && isset($this->fontFamilies[$cf][$this->currentTextState])){
2690      //     // then we are in some state or another
2691      //     // and this font has a family, and the current setting exists within it
2692      //     // select the font, then return it
2693      //     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
2694      //     $this->selectFont($nf,'',0);
2695      //     $this->currentFont = $nf;
2696      //     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
2697      //   } else {
2698      //     // the this font must not have the right family member for the current state
2699      //     // simply assume the base font
2700      $this->currentFont =  $this->currentBaseFont;
2701  
2702      $this->currentFontNum =  $this->fonts[$this->currentFont]['fontNum'];
2703  
2704      //  }
2705  
2706    }
2707  
2708  
2709    /**
2710     * function for the user to find out what the ID is of the first page that was created during
2711     * startup - useful if they wish to add something to it later.
2712     */
2713    function  getFirstPageId() {
2714  
2715      return  $this->firstPageId;
2716    }
2717  
2718  
2719    /**
2720     * add content to the currently active object
2721     *
2722     * @access private
2723     */
2724    function  addContent($content) {
2725  
2726      $this->objects[$this->currentContents]['c'].=  $content;
2727    }
2728  
2729  
2730    /**
2731     * sets the colour for fill operations
2732     */
2733    function  setColor($r, $g, $b, $force =  0) {
2734  
2735      if  ($r >=  0 &&  ($force ||  $r !=  $this->currentColour['r'] ||  $g !=  $this->currentColour['g'] ||  $b !=  $this->currentColour['b'])) {
2736  
2737        $this->objects[$this->currentContents]['c'].=  "\n".sprintf('%.3f', $r) .' '.sprintf('%.3f', $g) .' '.sprintf('%.3f', $b) .' rg';
2738  
2739        $this->currentColour =  array('r' => $r, 'g' => $g, 'b' => $b);
2740      }
2741    }
2742  
2743  
2744    /**
2745     * sets the colour for stroke operations
2746     */
2747    function  setStrokeColor($r, $g, $b, $force =  0) {
2748  
2749      if  ($r >=  0 &&  ($force ||  $r !=  $this->currentStrokeColour['r'] ||  $g !=  $this->currentStrokeColour['g'] ||  $b !=  $this->currentStrokeColour['b'])) {
2750  
2751        $this->objects[$this->currentContents]['c'].=  "\n".sprintf('%.3f', $r) .' '.sprintf('%.3f', $g) .' '.sprintf('%.3f', $b) .' RG';
2752  
2753        $this->currentStrokeColour =  array('r' => $r, 'g' => $g, 'b' => $b);
2754      }
2755    }
2756  
2757  
2758    /**
2759     * Set the graphics state for compositions
2760     */
2761    function  setGraphicsState($parameters) {
2762  
2763      // Create a new graphics state object
2764      // FIXME: should actually keep track of states that have already been created...
2765      $this->numObj++;
2766  
2767      $this->o_extGState($this->numObj,  'new',  $parameters);
2768  
2769      $this->objects[ $this->currentContents ]['c'].=  "\n/GS" . $this->numStates . " gs";
2770    }
2771  
2772  
2773    /**
2774     * Set current blend mode & opacity for lines.
2775     *
2776     * Valid blend modes are:
2777     *
2778     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
2779     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
2780     * Exclusion
2781     *
2782     * @param string $mode the blend mode to use
2783     * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
2784     */
2785    function setLineTransparency($mode, $opacity) {
2786      static $blend_modes = array("Normal", "Multiply", "Screen",
2787                                  "Overlay", "Darken", "Lighten",
2788                                  "ColorDogde", "ColorBurn", "HardLight",
2789                                  "SoftLight", "Difference", "Exclusion");
2790  
2791      if ( !in_array($mode, $blend_modes) )
2792        $mode = "Normal";
2793      
2794      // Only create a new graphics state if required
2795      if ( $mode == $this->currentLineTransparency["mode"]  &&
2796           $opacity == $this->currentLineTransparency["opacity"] )
2797        return;
2798  
2799      $options = array("BM" => "/$mode",
2800                       "CA" => (float)$opacity);
2801  
2802      $this->setGraphicsState($options);
2803    }
2804    
2805    /**
2806     * Set current blend mode & opacity for filled objects.
2807     *
2808     * Valid blend modes are:
2809     *
2810     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
2811     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
2812     * Exclusion
2813     *
2814     * @param string $mode the blend mode to use
2815     * @param float $opacity 0.0 fully transparent, 1.0 fully opaque   
2816     */
2817    function setFillTransparency($mode, $opacity) {
2818      static $blend_modes = array("Normal", "Multiply", "Screen",
2819                                  "Overlay", "Darken", "Lighten",
2820                                  "ColorDogde", "ColorBurn", "HardLight",
2821                                  "SoftLight", "Difference", "Exclusion");
2822  
2823      if ( !in_array($mode, $blend_modes) )
2824        $mode = "Normal";
2825  
2826      if ( $mode == $this->currentFillTransparency["mode"]  &&
2827           $opacity == $this->currentFillTransparency["opacity"] )
2828        return;
2829  
2830      $options = array("BM" => "/$mode",
2831                       "ca" => (float)$opacity);
2832      
2833      $this->setGraphicsState($options);
2834    }
2835  
2836    /**
2837     * draw a line from one set of coordinates to another
2838     */
2839    function  line($x1, $y1, $x2, $y2) {
2840  
2841      $this->objects[$this->currentContents]['c'] .=
2842        "\n".sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1) .' m '.sprintf('%.3f', $x2) .' '.sprintf('%.3f', $y2) .' l S';
2843    }
2844  
2845  
2846    /**
2847     * draw a bezier curve based on 4 control points
2848     */
2849    function  curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) {
2850  
2851      // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
2852      // as the control points for the curve.
2853      $this->objects[$this->currentContents]['c'] .=
2854        "\n".sprintf('%.3f', $x0) .' '.sprintf('%.3f', $y0) .' m '.sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1);
2855  
2856      $this->objects[$this->currentContents]['c'] .=
2857        ' '.sprintf('%.3f', $x2) .' '.sprintf('%.3f', $y2) .' '.sprintf('%.3f', $x3) .' '.sprintf('%.3f', $y3) .' c S';
2858    }
2859  
2860  
2861    /**
2862     * draw a part of an ellipse
2863     */
2864    function  partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 =  0, $angle =  0, $nSeg =  8) {
2865  
2866      $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, 0);
2867    }
2868  
2869  
2870    /**
2871     * draw a filled ellipse
2872     */
2873    function  filledEllipse($x0, $y0, $r1, $r2 =  0, $angle =  0, $nSeg =  8, $astart =  0, $afinish =  360) {
2874  
2875      return  $this->ellipse($x0, $y0, $r1, $r2 =  0, $angle, $nSeg, $astart, $afinish, 1, 1);
2876    }
2877  
2878  
2879    /**
2880     * draw an ellipse
2881     * note that the part and filled ellipse are just special cases of this function
2882     *
2883     * draws an ellipse in the current line style
2884     * centered at $x0,$y0, radii $r1,$r2
2885     * if $r2 is not set, then a circle is drawn
2886     * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
2887     * pretty crappy shape at 2, as we are approximating with bezier curves.
2888     */
2889    function  ellipse($x0, $y0, $r1, $r2 =  0, $angle =  0, $nSeg =  8, $astart =  0, $afinish =  360, $close =  1, $fill =  0) {
2890  
2891      if  ($r1 ==  0) {
2892        return;
2893      }
2894  
2895      if  ($r2 ==  0) {
2896        $r2 =  $r1;
2897      }
2898  
2899      if  ($nSeg < 2) {
2900        $nSeg =  2;
2901      }
2902  
2903  
2904      $astart =  deg2rad((float)$astart);
2905  
2906      $afinish =  deg2rad((float)$afinish);
2907  
2908      $totalAngle = $afinish-$astart;
2909  
2910  
2911      $dt =  $totalAngle/$nSeg;
2912  
2913      $dtm =  $dt/3;
2914  
2915  
2916      if  ($angle !=  0) {
2917  
2918        $a =  -1*deg2rad((float)$angle);
2919  
2920        $tmp  =  "\n q ";
2921        $tmp .=  sprintf('%.3f', cos($a)) .' '.sprintf('%.3f', (-1.0*sin($a))) .' '.sprintf('%.3f', sin($a)) .' '.sprintf('%.3f', cos($a)) .' ';
2922        $tmp .=  sprintf('%.3f', $x0) .' '.sprintf('%.3f', $y0) .' cm';
2923  
2924        $this->objects[$this->currentContents]['c'].=  $tmp;
2925  
2926        $x0 =  0;
2927        $y0 =  0;
2928      }
2929  
2930  
2931      $t1 =  $astart;
2932      $a0 =  $x0 + $r1*cos($t1);
2933      $b0 =  $y0 + $r2*sin($t1);
2934      $c0 =  -$r1 * sin($t1);
2935      $d0 =  $r2 * cos($t1);
2936  
2937  
2938      $this->objects[$this->currentContents]['c'] .=  "\n".sprintf('%.3f', $a0) .' '.sprintf('%.3f', $b0) .' m ';
2939  
2940      for  ($i = 1; $i <=  $nSeg; $i++) {
2941  
2942        // draw this bit of the total curve
2943        $t1 =  $i * $dt + $astart;
2944  
2945        $a1 =  $x0 + $r1 * cos($t1);
2946  
2947        $b1 =  $y0 + $r2 * sin($t1);
2948  
2949        $c1 = -$r1 * sin($t1);
2950  
2951        $d1 =  $r2 * cos($t1);
2952  
2953        $this->objects[$this->currentContents]['c']
2954          .=  "\n".sprintf('%.3f', ($a0+$c0*$dtm)) .' '.sprintf('%.3f', ($b0 + $d0 * $dtm));
2955  
2956        $this->objects[$this->currentContents]['c'] .=
2957          ' '.sprintf('%.3f', ($a1-$c1*$dtm)) .' '.sprintf('%.3f', ($b1-$d1*$dtm)) .' '.sprintf('%.3f', $a1) .' '.sprintf('%.3f', $b1) .' c';
2958  
2959        $a0 =  $a1;
2960  
2961        $b0 =  $b1;
2962  
2963        $c0 =  $c1;
2964  
2965        $d0 =  $d1;
2966      }
2967  
2968      if  ($fill) {
2969        $this->objects[$this->currentContents]['c'].=  ' f';
2970  
2971      } else if ($close) {
2972  
2973          $this->objects[$this->currentContents]['c'].=  ' s';
2974          // small 's' signifies closing the path as well
2975  
2976      } else {
2977  
2978        $this->objects[$this->currentContents]['c'].=  ' S';
2979  
2980      }
2981  
2982      if  ($angle !=  0) {
2983        $this->objects[$this->currentContents]['c'].=  ' Q';
2984      }
2985  
2986    }
2987  
2988  
2989    /**
2990     * this sets the line drawing style.
2991     * width, is the thickness of the line in user units
2992     * cap is the type of cap to put on the line, values can be 'butt','round','square'
2993     *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
2994     *    end of the line.
2995     * join can be 'miter', 'round', 'bevel'
2996     * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
2997     *   on and off dashes.
2998     *   (2) represents 2 on, 2 off, 2 on , 2 off ...
2999     *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
3000     * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
3001     */
3002    function  setLineStyle($width =  1, $cap =  '', $join =  '', $dash =  '', $phase =  0) {
3003  
3004  
3005      // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
3006      $string =  '';
3007  
3008      if  ($width>0) {
3009  
3010        $string.=  $width.' w';
3011      }
3012  
3013      $ca =  array('butt' => 0, 'round' => 1, 'square' => 2);
3014  
3015      if  (isset($ca[$cap])) {
3016  
3017        $string.=  ' '.$ca[$cap].' J';
3018      }
3019  
3020      $ja =  array('miter' => 0, 'round' => 1, 'bevel' => 2);
3021  
3022      if  (isset($ja[$join])) {
3023  
3024        $string.=  ' '.$ja[$join].' j';
3025      }
3026  
3027      if  (is_array($dash)) {
3028  
3029        $string.=  ' [';
3030  
3031        foreach ($dash as  $len) {
3032  
3033          $string.=  ' '.$len;
3034        }
3035  
3036        $string.=  ' ] '.$phase.' d';
3037      }
3038  
3039      $this->currentLineStyle =  $string;
3040  
3041      $this->objects[$this->currentContents]['c'].=  "\n".$string;
3042    }
3043  
3044  
3045  
3046    /**
3047     * draw a polygon, the syntax for this is similar to the GD polygon command
3048     */
3049    function  polygon($p, $np, $f =  0) {
3050  
3051      $this->objects[$this->currentContents]['c'].=  "\n";
3052  
3053      $this->objects[$this->currentContents]['c'].=  sprintf('%.3f', $p[0]) .' '.sprintf('%.3f', $p[1]) .' m ';
3054  
3055      for  ($i =  2; $i < $np * 2; $i =  $i + 2) {
3056  
3057        $this->objects[$this->currentContents]['c'].=  sprintf('%.3f', $p[$i]) .' '.sprintf('%.3f', $p[$i+1]) .' l ';
3058      }
3059  
3060      if  ($f ==  1) {
3061  
3062        $this->objects[$this->currentContents]['c'].=  ' f';
3063      } else {
3064  
3065        $this->objects[$this->currentContents]['c'].=  ' S';
3066      }
3067    }
3068  
3069  
3070    /**
3071     * a filled rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
3072     * the coordinates of the upper-right corner
3073     */
3074    function  filledRectangle($x1, $y1, $width, $height) {
3075  
3076      $this->objects[$this->currentContents]['c'].=  "\n".sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1) .' '.sprintf('%.3f', $width) .' '.sprintf('%.3f', $height) .' re f';
3077    }
3078  
3079  
3080    /**
3081     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary paramaters, not
3082     * the coordinates of the upper-right corner
3083     */
3084    function  rectangle($x1, $y1, $width, $height) {
3085  
3086      $this->objects[$this->currentContents]['c'].=  "\n".sprintf('%.3f', $x1) .' '.sprintf('%.3f', $y1) .' '.sprintf('%.3f', $width) .' '.sprintf('%.3f', $height) .' re S';
3087    }
3088  
3089  
3090    /**
3091     * add a new page to the document
3092     * this also makes the new page the current active object
3093     */
3094    function  newPage($insert =  0, $id =  0, $pos =  'after') {
3095  
3096  
3097      // if there is a state saved, then go up the stack closing them
3098      // then on the new page, re-open them with the right setings
3099  
3100      if  ($this->nStateStack) {
3101  
3102        for  ($i =  $this->nStateStack;$i >=  1;$i--) {
3103  
3104          $this->restoreState($i);
3105        }
3106      }
3107  
3108  
3109      $this->numObj++;
3110  
3111      if  ($insert) {
3112  
3113        // the id from the ezPdf class is the od of the contents of the page, not the page object itself
3114        // query that object to find the parent
3115        $rid =  $this->objects[$id]['onPage'];
3116  
3117        $opt =  array('rid' => $rid, 'pos' => $pos);
3118  
3119        $this->o_page($this->numObj, 'new', $opt);
3120      } else {
3121  
3122        $this->o_page($this->numObj, 'new');
3123      }
3124  
3125      // if there is a stack saved, then put that onto the page
3126      if  ($this->nStateStack) {
3127  
3128        for  ($i =  1;$i <=  $this->nStateStack;$i++) {
3129  
3130          $this->saveState($i);
3131        }
3132      }
3133  
3134      // and if there has been a stroke or fill colour set, then transfer them
3135      if  ($this->currentColour['r'] >=  0) {
3136  
3137        $this->setColor($this->currentColour['r'], $this->currentColour['g'], $this->currentColour['b'], 1);
3138      }
3139  
3140      if  ($this->currentStrokeColour['r'] >=  0) {
3141  
3142        $this->setStrokeColor($this->currentStrokeColour['r'], $this->currentStrokeColour['g'], $this->currentStrokeColour['b'], 1);
3143      }
3144  
3145  
3146      // if there is a line style set, then put this in too
3147      if  (strlen($this->currentLineStyle)) {
3148  
3149        $this->objects[$this->currentContents]['c'].=  "\n".$this->currentLineStyle;
3150      }
3151  
3152  
3153      // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
3154      return  $this->currentContents;
3155    }
3156  
3157  
3158    /**
3159     * output the pdf code, streaming it to the browser
3160     * the relevant headers are set so that hopefully the browser will recognise it
3161     */
3162    function  stream($options =  '') {
3163  
3164      // setting the options allows the adjustment of the headers
3165      // values at the moment are:
3166      // 'Content-Disposition' => 'filename'  - sets the filename, though not too sure how well this will
3167      //        work as in my trial the browser seems to use the filename of the php file with .pdf on the end
3168      // 'Accept-Ranges' => 1 or 0 - if this is not set to 1, then this header is not included, off by default
3169      //    this header seems to have caused some problems despite tha fact that it is supposed to solve
3170      //    them, so I am leaving it off by default.
3171      // 'compress' = > 1 or 0 - apply content stream compression, this is on (1) by default
3172      // 'Attachment' => 1 or 0 - if 1, force the browser to open a download dialog
3173      if  (!is_array($options)) {
3174  
3175        $options =  array();
3176      }
3177  
3178      if  ( headers_sent())
3179        die("Unable to stream pdf: headers already sent");
3180  
3181  
3182      if  ( isset($options['compress']) &&  $options['compress'] ==  0) {
3183  
3184        $tmp =  ltrim($this->output(1));
3185      } else {
3186  
3187        $tmp =  ltrim($this->output());
3188      }
3189  
3190  
3191      header("Cache-Control: private");
3192  
3193      header("Content-type: application/pdf");
3194  
3195      //header("Content-Length: " . strlen($tmp));
3196      $fileName =  (isset($options['Content-Disposition']) ?  $options['Content-Disposition'] :  'file.pdf');
3197  
3198      if  ( !isset($options["Attachment"]))
3199        $options["Attachment"] =  true;
3200  
3201  
3202      $attachment =  $options["Attachment"] ?  "attachment" :  "inline";
3203  
3204  
3205      header('Content-Disposition: $attachment; filename="'.$fileName.'"');
3206  
3207  
3208      if  (isset($options['Accept-Ranges']) &&  $options['Accept-Ranges'] ==  1) {
3209  
3210        header("Accept-Ranges: " . strlen($tmp));
3211      }
3212  
3213      echo  $tmp;
3214  
3215      flush();
3216    }
3217  
3218  
3219    /**
3220     * return the height in units of the current font in the given size
3221     */
3222    function  getFontHeight($size) {
3223  
3224      if  (!$this->numFonts) {
3225  
3226        $this->selectFont('./fonts/Helvetica');
3227      }
3228  
3229      // for the current font, and the given size, what is the height of the font in user units
3230      $h =  $this->fonts[$this->currentFont]['FontBBox'][3]-$this->fonts[$this->currentFont]['FontBBox'][1];
3231  
3232      return  $size*$h/1000;
3233    }
3234  
3235  
3236    /**
3237     * return the font decender, this will normally return a negative number
3238     * if you add this number to the baseline, you get the level of the bottom of the font
3239     * it is in the pdf user units
3240     */
3241    function  getFontDecender($size) {
3242  
3243      // note that this will most likely return a negative value
3244      if  (!$this->numFonts) {
3245  
3246        $this->selectFont('./fonts/Helvetica');
3247      }
3248  
3249      $h =  $this->fonts[$this->currentFont]['FontBBox'][1];
3250  
3251      return  $size*$h/1000;
3252    }
3253  
3254  
3255    /**
3256     * filter the text, this is applied to all text just before being inserted into the pdf document
3257     * it escapes the various things that need to be escaped, and so on
3258     *
3259     * @access private
3260     */
3261    function  filterText($text) {
3262  
3263      $search =  array("\\",  "(",  ")",  "&lt;",  "&gt;",  "&#039;",  "&quot;",  "&amp;");
3264  
3265      $replace =  array("\\\\",  "\(",  "\)",  "<",  ">",  "\'",  '"',  "&");
3266  
3267      $text =  str_replace($search,  $replace,  $text);
3268  
3269  
3270      return  $text;
3271    }
3272  
3273  
3274    /**
3275     * given a start position and information about how text is to be laid out, calculate where
3276     * on the page the text will end
3277     *
3278     * @access private
3279     */
3280    function  PRVTgetTextPosition($x, $y, $angle, $size, $wa, $text) {
3281  
3282      // given this information return an array containing x and y for the end position as elements 0 and 1
3283      $w =  $this->getTextWidth($size, $text);
3284  
3285      // need to adjust for the number of spaces in this text
3286      $words =  explode(' ', $text);
3287  
3288      $nspaces =  count($words) -1;
3289  
3290      $w+=  $wa*$nspaces;
3291  
3292      $a =  deg2rad((float)$angle);
3293  
3294      return  array(cos($a) *$w+$x, -sin($a) *$w+$y);
3295    }
3296  
3297  
3298    /**
3299     * wrapper function for PRVTcheckTextDirective1
3300     *
3301     * @access private
3302     */
3303    function  PRVTcheckTextDirective(&$text, $i, &$f) {
3304  
3305      return  0;
3306  
3307      $x =  0;
3308  
3309      $y =  0;
3310  
3311      return  $this->PRVTcheckTextDirective1($text, $i, $f, 0, $x, $y);
3312    }
3313  
3314  
3315    /**
3316     * checks if the text stream contains a control directive
3317     * if so then makes some changes and returns the number of characters involved in the directive
3318     * this has been re-worked to include everything neccesary to fins the current writing point, so that
3319     * the location can be sent to the callback function if required
3320     * if the directive does not require a font change, then $f should be set to 0
3321     *
3322     * @access private
3323     */
3324    function  PRVTcheckTextDirective1(&$text, $i, &$f, $final, &$x, &$y, $size =  0, $angle =  0, $wordSpaceAdjust =  0) {
3325  
3326      return  0;
3327  
3328      $directive =  0;
3329  
3330      $j =  $i;
3331  
3332      if  ($text[$j] ==  '<') {
3333  
3334        $j++;
3335  
3336        switch ($text[$j]) {
3337  
3338        case  '/':
3339  
3340          $j++;
3341  
3342          if  (strlen($text) <=  $j) {
3343  
3344            return  $directive;
3345          }
3346  
3347          switch ($text[$j]) {
3348  
3349          case  'b':
3350  
3351          case  'i':
3352  
3353            $j++;
3354  
3355            if  ($text[$j] ==  '>') {
3356  
3357              $p =  strrpos($this->currentTextState, $text[$j-1]);
3358  
3359              if  ($p !==  false) {
3360  
3361                // then there is one to remove
3362                $this->currentTextState =  substr($this->currentTextState, 0, $p) .substr($this->currentTextState, $p+1);
3363              }
3364  
3365              $directive =  $j-$i+1;
3366            }
3367  
3368            break;
3369  
3370          case  'c':
3371  
3372            // this this might be a callback function
3373            $j++;
3374  
3375            $k =  strpos($text, '>', $j);
3376  
3377            if  ($k !==  false &&  $text[$j] ==  ':') {
3378  
3379              // then this will be treated as a callback directive
3380              $directive =  $k-$i+1;
3381  
3382              $f =  0;
3383  
3384              // split the remainder on colons to get the function name and the paramater
3385              $tmp =  substr($text, $j+1, $k-$j-1);
3386  
3387              $b1 =  strpos($tmp, ':');
3388  
3389              if  ($b1 !==  false) {
3390  
3391                $func =  substr($tmp, 0, $b1);
3392  
3393                $parm =  substr($tmp, $b1+1);
3394              } else {
3395  
3396                $func =  $tmp;
3397  
3398                $parm =  '';
3399              }
3400  
3401              if  (!isset($func) ||  !strlen(trim($func))) {
3402  
3403                $directive =  0;
3404              } else {
3405  
3406                // only call the function if this is the final call
3407                if  ($final) {
3408  
3409                  // need to assess the text position, calculate the text width to this point
3410                  // can use getTextWidth to find the text width I think
3411                  $tmp =  $this->PRVTgetTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, substr($text, 0, $i));
3412  
3413                  $info =  array('x' => $tmp[0], 'y' => $tmp[1], 'angle' => $angle, 'status' => 'end', 'p' => $parm, 'nCallback' => $this->nCallback);
3414  
3415                  $x =  $tmp[0];
3416  
3417                  $y =  $tmp[1];
3418  
3419                  $ret =  $this->$func($info);
3420  
3421                  if  (is_array($ret)) {
3422  
3423                    // then the return from the callback function could set the position, to start with, later will do font colour, and font
3424                    foreach($ret as  $rk => $rv) {
3425  
3426                      switch ($rk) {
3427  
3428                      case  'x':
3429  
3430                      case  'y':
3431  
3432                        $$rk =  $rv;
3433  
3434                        break;
3435                      }
3436                    }
3437                  }
3438  
3439                  // also remove from to the stack
3440                  // for simplicity, just take from the end, fix this another day
3441                  $this->nCallback--;
3442  
3443                  if  ($this->nCallback<0) {
3444  
3445                    $this->nCallBack =  0;
3446                  }
3447                }
3448              }
3449            }
3450  
3451            break;
3452          }
3453  
3454          break;
3455  
3456        case  'b':
3457  
3458        case  'i':
3459  
3460          $j++;
3461  
3462          if  ($text[$j] ==  '>') {
3463  
3464            $this->currentTextState.=  $text[$j-1];
3465  
3466            $directive =  $j-$i+1;
3467          }
3468  
3469          break;
3470  
3471        case  'C':
3472  
3473          $noClose =  1;
3474  
3475        case  'c':
3476  
3477          // this this might be a callback function
3478          $j++;
3479  
3480          $k =  strpos($text, '>', $j);
3481  
3482          if  ($k !==  false &&  $text[$j] ==  ':') {
3483  
3484            // then this will be treated as a callback directive
3485            $directive =  $k-$i+1;
3486  
3487            $f =  0;
3488  
3489            // split the remainder on colons to get the function name and the paramater
3490            //          $bits = explode(':',substr($text,$j+1,$k-$j-1));
3491            $tmp =  substr($text, $j+1, $k-$j-1);
3492  
3493            $b1 =  strpos($tmp, ':');
3494  
3495            if  ($b1 !==  false) {
3496  
3497              $func =  substr($tmp, 0, $b1);
3498  
3499              $parm =  substr($tmp, $b1+1);
3500            } else {
3501  
3502              $func =  $tmp;
3503  
3504              $parm =  '';
3505            }
3506  
3507            if  (!isset($func) ||  !strlen(trim($func))) {
3508  
3509              $directive =  0;
3510            } else {
3511  
3512              // only call the function if this is the final call, ie, the one actually doing printing, not measurement
3513              if  ($final) {
3514  
3515                // need to assess the text position, calculate the text width to this point
3516                // can use getTextWidth to find the text width I think
3517                // also add the text height and decender
3518                $tmp =  $this->PRVTgetTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, substr($text, 0, $i));
3519  
3520                $info =  array('x' => $tmp[0], 'y' => $tmp[1], 'angle' => $angle, 'status' => 'start', 'p' => $parm, 'f' => $func, 'height' => $this->getFontHeight($size), 'decender' => $this->getFontDecender($size));
3521  
3522                $x =  $tmp[0];
3523  
3524                $y =  $tmp[1];
3525  
3526                if  (!isset($noClose) ||  !$noClose) {
3527  
3528                  // only add to the stack if this is a small 'c', therefore is a start-stop pair
3529                  $this->nCallback++;
3530  
3531                  $info['nCallback'] =  $this->nCallback;
3532  
3533                  $this->callback[$this->nCallback] =  $info;
3534                }
3535  
3536                $ret =  $this->$func($info);
3537  
3538                if  (is_array($ret)) {
3539  
3540                  // then the return from the callback function could set the position, to start with, later will do font colour, and font
3541                  foreach($ret as  $rk => $rv) {
3542  
3543                    switch ($rk) {
3544  
3545                    case  'x':
3546  
3547                    case  'y':
3548  
3549                      $$rk =  $rv;
3550  
3551                      break;
3552                    }
3553                  }
3554                }
3555              }
3556            }
3557          }
3558  
3559          break;
3560        }
3561      }
3562  
3563      return  $directive;
3564    }
3565  
3566  
3567    /**
3568     * add text to the document, at a specified location, size and angle on the page
3569     */
3570    function  addText($x, $y, $size, $text, $angle =  0, $wordSpaceAdjust =  0) {
3571  
3572      if  (!$this->numFonts) {
3573        $this->selectFont('./fonts/Helvetica');
3574      }
3575  
3576  
3577      // if there are any open callbacks, then they should be called, to show the start of the line
3578      if  ($this->nCallback>0) {
3579  
3580        for  ($i =  $this->nCallback;$i>0;$i--) {
3581  
3582          // call each function
3583          $info =  array('x' => $x,
3584                         'y' => $y,
3585                         'angle' => $angle,
3586                         'status' => 'sol',
3587                         'p' => $this->callback[$i]['p'],
3588                         'nCallback' => $this->callback[$i]['nCallback'],
3589                         'height' => $this->callback[$i]['height'],
3590                         'decender' => $this->callback[$i]['decender']);
3591  
3592          $func =  $this->callback[$i]['f'];
3593  
3594          $this->$func($info);
3595        }
3596      }
3597  
3598      if  ($angle ==  0) {
3599  
3600        $this->objects[$this->currentContents]['c'].=  "\n".'BT '.sprintf('%.3f', $x) .' '.sprintf('%.3f', $y) .' Td';
3601  
3602      } else {
3603  
3604        $a =  deg2rad((float)$angle);
3605  
3606        $tmp =  "\n".'BT ';
3607  
3608        $tmp.=  sprintf('%.3f', cos($a)) .' '.sprintf('%.3f', (-1.0*sin($a))) .' '.sprintf('%.3f', sin($a)) .' '.sprintf('%.3f', cos($a)) .' ';
3609  
3610        $tmp.=  sprintf('%.3f', $x) .' '.sprintf('%.3f', $y) .' Tm';
3611  
3612        $this->objects[$this->currentContents]['c'].=  $tmp;
3613      }
3614  
3615      if  ($wordSpaceAdjust !=  0 ||  $wordSpaceAdjust !=  $this->wordSpaceAdjust) {
3616  
3617        $this->wordSpaceAdjust =  $wordSpaceAdjust;
3618  
3619        $this->objects[$this->currentContents]['c'].=  ' '.sprintf('%.3f', $wordSpaceAdjust) .' Tw';
3620      }
3621  
3622      $len =  strlen($text);
3623  
3624      $start =  0;
3625  
3626      /*
3627       for ($i = 0;$i<$len;$i++){
3628       $f = 1;
3629       $directive = 0; //$this->PRVTcheckTextDirective($text,$i,$f);
3630       if ($directive){
3631       // then we should write what we need to
3632       if ($i>$start){
3633       $part = substr($text,$start,$i-$start);
3634       $this->objects[$this->currentContents]['c'] .= ' /F'.$this->currentFontNum.' '.sprintf('%.1f',$size).' Tf ';
3635       $this->objects[$this->currentContents]['c'] .= ' ('.$this->filterText($part).') Tj';
3636       }
3637       if ($f){
3638       // then there was nothing drastic done here, restore the contents
3639       $this->setCurrentFont();
3640       } else {
3641       $this->objects[$this->currentContents]['c'] .= ' ET';
3642       $f = 1;
3643       $xp = $x;
3644       $yp = $y;
3645       $directive = 0; //$this->PRVTcheckTextDirective1($text,$i,$f,1,$xp,$yp,$size,$angle,$wordSpaceAdjust);
3646  
3647       // restart the text object
3648       if ($angle == 0){
3649       $this->objects[$this->currentContents]['c'] .= "\n".'BT '.sprintf('%.3f',$xp).' '.sprintf('%.3f',$yp).' Td';
3650       } else {
3651       $a = deg2rad((float)$angle);
3652       $tmp = "\n".'BT ';
3653       $tmp .= sprintf('%.3f',cos($a)).' '.sprintf('%.3f',(-1.0*sin($a))).' '.sprintf('%.3f',sin($a)).' '.sprintf('%.3f',cos($a)).' ';
3654       $tmp .= sprintf('%.3f',$xp).' '.sprintf('%.3f',$yp).' Tm';
3655       $this->objects[$this->currentContents]['c'] .= $tmp;
3656       }
3657       if ($wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust){
3658       $this->wordSpaceAdjust = $wordSpaceAdjust;
3659       $this->objects[$this->currentContents]['c'] .= ' '.sprintf('%.3f',$wordSpaceAdjust).' Tw';
3660       }
3661       }
3662       // and move the writing point to the next piece of text
3663       $i = $i+$directive-1;
3664       $start = $i+1;
3665       }
3666  
3667       }
3668      */
3669      if  ($start < $len) {
3670  
3671        $part =  substr($text, $start);
3672  
3673        $this->objects[$this->currentContents]['c'].=  ' /F'.$this->currentFontNum.' '.sprintf('%.1f', $size) .' Tf ';
3674  
3675        $this->objects[$this->currentContents]['c'].=  ' ('.$this->filterText($part) .') Tj';
3676      }
3677  
3678      $this->objects[$this->currentContents]['c'].=  ' ET';
3679  
3680  
3681      // if there are any open callbacks, then they should be called, to show the end of the line
3682      if  ($this->nCallback>0) {
3683  
3684        for  ($i =  $this->nCallback;$i>0;$i--) {
3685  
3686          // call each function
3687          $tmp =  $this->PRVTgetTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
3688  
3689          $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']);
3690  
3691          $func =  $this->callback[$i]['f'];
3692  
3693          $this->$func($info);
3694        }
3695      }
3696    }
3697  
3698  
3699    /**
3700     * calculate how wide a given text string will be on a page, at a given size.
3701     * this can be called externally, but is alse used by the other class functions
3702     */
3703    function  getTextWidth($size, $text, $spacing =  0) {
3704  
3705      // this function should not change any of the settings, though it will need to
3706      // track any directives which change during calculation, so copy them at the start
3707      // and put them back at the end.
3708      $store_currentTextState =  $this->currentTextState;
3709  
3710  
3711      if  (!$this->numFonts) {
3712  
3713        $this->selectFont('./fonts/Helvetica');
3714      }
3715  
3716  
3717      // converts a number or a float to a string so it can get the width
3718      $text =  "$text";
3719  
3720  
3721      // hmm, this is where it all starts to get tricky - use the font information to
3722      // calculate the width of each character, add them up and convert to user units
3723      $w =  0;
3724  
3725      $len =  strlen($text);
3726  
3727      $cf =  $this->currentFont;
3728  
3729      $space_scale =  1000 / $size;
3730  
3731      for  ($i =  0; $i < $len; $i++) {
3732  
3733        //     $f = 1;
3734        //     $directive = 0; //$this->PRVTcheckTextDirective($text,$i,$f);
3735        //     if ($directive){
3736        //       if ($f){
3737        //         $this->setCurrentFont();
3738        //         $cf = $this->currentFont;
3739        //       }
3740        //       $i = $i+$directive-1;
3741        //     } else {
3742        $char =  ord($text{$i});
3743  
3744        if  ( isset($this->fonts[$cf]['differences'][$char])) {
3745  
3746  
3747          // then this character is being replaced by another
3748          $name =  $this->fonts[$cf]['differences'][$char];
3749  
3750  
3751          if  ( isset($this->fonts[$cf]['C'][$name]['WX']))
3752            $w+=  $this->fonts[$cf]['C'][$name]['WX'];
3753        } else  if  (isset($this->fonts[$cf]['C'][$char]['WX']))
3754          $w+=  $this->fonts[$cf]['C'][$char]['WX'];
3755  
3756  
3757        if  ( $char ==  32)  // Space
3758          $w+=  $spacing * $space_scale;
3759      }
3760  
3761  
3762  
3763      $this->currentTextState =  $store_currentTextState;
3764  
3765      $this->setCurrentFont();
3766  
3767  
3768      return  $w*$size/1000;
3769    }
3770  
3771  
3772    /**
3773     * do a part of the calculation for sorting out the justification of the text
3774     *
3775     * @access private
3776     */
3777    function  PRVTadjustWrapText($text, $actual, $width, &$x, &$adjust, $justification) {
3778  
3779      switch  ($justification) {
3780  
3781      case  'left':
3782  
3783        return;
3784  
3785        break;
3786  
3787      case  'right':
3788  
3789        $x+=  $width-$actual;
3790  
3791        break;
3792  
3793      case  'center':
3794  
3795      case  'centre':
3796  
3797        $x+=  ($width-$actual) /2;
3798  
3799        break;
3800  
3801      case  'full':
3802  
3803        // count the number of words
3804        $words =  explode(' ', $text);
3805  
3806        $nspaces =  count($words) -1;
3807  
3808        if  ($nspaces>0) {
3809  
3810          $adjust =  ($width-$actual) /$nspaces;
3811        } else {
3812  
3813          $adjust =  0;
3814        }
3815  
3816        break;
3817      }
3818    }
3819  
3820  
3821    /**
3822     * add text to the page, but ensure that it fits within a certain width
3823     * if it does not fit then put in as much as possible, splitting at word boundaries
3824     * and return the remainder.
3825     * justification and angle can also be specified for the text
3826     */
3827    function  addTextWrap($x, $y, $width, $size, $text, $justification =  'left', $angle =  0, $test =  0) {
3828  
3829      // this will display the text, and if it goes beyond the width $width, will backtrack to the
3830      // previous space or hyphen, and return the remainder of the text.
3831  
3832      // $justification can be set to 'left','right','center','centre','full'
3833  
3834      // need to store the initial text state, as this will change during the width calculation
3835      // but will need to be re-set before printing, so that the chars work out right
3836      $store_currentTextState =  $this->currentTextState;
3837  
3838  
3839      if  (!$this->numFonts) {
3840        $this->selectFont('./fonts/Helvetica');
3841      }
3842  
3843      if  ($width <=  0) {
3844  
3845        // error, pretend it printed ok, otherwise risking a loop
3846        return  '';
3847      }
3848  
3849      $w =  0;
3850  
3851      $break =  0;
3852  
3853      $breakWidth =  0;
3854  
3855      $len =  strlen($text);
3856  
3857      $cf =  $this->currentFont;
3858  
3859      $tw =  $width/$size*1000;
3860  
3861      for  ($i =  0;$i<$len;$i++) {
3862  
3863        $f =  1;
3864  
3865        $directive =  0;
3866        //$this->PRVTcheckTextDirective($text,$i,$f);
3867        if  ($directive) {
3868  
3869          if  ($f) {
3870  
3871            $this->setCurrentFont();
3872  
3873            $cf =  $this->currentFont;
3874          }
3875  
3876          $i =  $i+$directive-1;
3877        } else {
3878  
3879          $cOrd =  ord($text[$i]);
3880  
3881          if  (isset($this->fonts[$cf]['differences'][$cOrd])) {
3882  
3883            // then this character is being replaced by another
3884            $cOrd2 =  $this->fonts[$cf]['differences'][$cOrd];
3885          } else {
3886  
3887            $cOrd2 =  $cOrd;
3888          }
3889  
3890  
3891          if  (isset($this->fonts[$cf]['C'][$cOrd2]['WX'])) {
3892  
3893            $w+=  $this->fonts[$cf]['C'][$cOrd2]['WX'];
3894          }
3895  
3896          if  ($w>$tw) {
3897  
3898            // then we need to truncate this line
3899            if  ($break>0) {
3900  
3901              // then we have somewhere that we can split :)
3902              if  ($text[$break] ==  ' ') {
3903  
3904                $tmp =  substr($text, 0, $break);
3905              } else {
3906  
3907                $tmp =  substr($text, 0, $break+1);
3908              }
3909  
3910              $adjust =  0;
3911  
3912              $this->PRVTadjustWrapText($tmp, $breakWidth, $width, $x, $adjust, $justification);
3913  
3914  
3915              // reset the text state
3916              $this->currentTextState =  $store_currentTextState;
3917  
3918              $this->setCurrentFont();
3919  
3920              if  (!$test) {
3921  
3922                $this->addText($x, $y, $size, $tmp, $angle, $adjust);
3923              }
3924  
3925              return  substr($text, $break+1);
3926            } else {
3927  
3928              // just split before the current character
3929              $tmp =  substr($text, 0, $i);
3930  
3931              $adjust =  0;
3932  
3933              $ctmp =  ord($text[$i]);
3934  
3935              if  (isset($this->fonts[$cf]['differences'][$ctmp])) {
3936  
3937                $ctmp =  $this->fonts[$cf]['differences'][$ctmp];
3938              }
3939  
3940              $tmpw =  ($w-$this->fonts[$cf]['C'][$ctmp]['WX']) *$size/1000;
3941  
3942              $this->PRVTadjustWrapText($tmp, $tmpw, $width, $x, $adjust, $justification);
3943  
3944              // reset the text state
3945              $this->currentTextState =  $store_currentTextState;
3946  
3947              $this->setCurrentFont();
3948  
3949              if  (!$test) {
3950  
3951                $this->addText($x, $y, $size, $tmp, $angle, $adjust);
3952              }
3953  
3954              return  substr($text, $i);
3955            }
3956          }
3957  
3958          if  ($text[$i] ==  '-') {
3959  
3960            $break =  $i;
3961  
3962            $breakWidth =  $w*$size/1000;
3963          }
3964  
3965          if  ($text[$i] ==  ' ') {
3966  
3967            $break =  $i;
3968  
3969            $ctmp =  ord($text[$i]);
3970  
3971            if  (isset($this->fonts[$cf]['differences'][$ctmp])) {
3972  
3973              $ctmp =  $this->fonts[$cf]['differences'][$ctmp];
3974            }
3975  
3976            $breakWidth =  ($w-$this->fonts[$cf]['C'][$ctmp]['WX']) *$size/1000;
3977          }
3978        }
3979      }
3980  
3981      // then there was no need to break this line
3982      if  ($justification ==  'full') {
3983  
3984        $justification =  'left';
3985      }
3986  
3987      $adjust =  0;
3988  
3989      $tmpw =  $w*$size/1000;
3990  
3991      $this->PRVTadjustWrapText($text, $tmpw, $width, $x, $adjust, $justification);
3992  
3993      // reset the text state
3994      $this->currentTextState =  $store_currentTextState;
3995  
3996      $this->setCurrentFont();
3997  
3998      if  (!$test) {
3999  
4000        $this->addText($x, $y, $size, $text, $angle, $adjust, $angle);
4001      }
4002  
4003      return  '';
4004    }
4005  
4006  
4007    /**
4008     * this will be called at a new page to return the state to what it was on the
4009     * end of the previous page, before the stack was closed down
4010     * This is to get around not being able to have open 'q' across pages
4011     *
4012     */
4013    function  saveState($pageEnd =  0) {
4014  
4015      if  ($pageEnd) {
4016  
4017        // this will be called at a new page to return the state to what it was on the
4018        // end of the previous page, before the stack was closed down
4019        // This is to get around not being able to have open 'q' across pages
4020        $opt =  $this->stateStack[$pageEnd];
4021        // ok to use this as stack starts numbering at 1
4022        $this->setColor($opt['col']['r'], $opt['col']['g'], $opt['col']['b'], 1);
4023  
4024        $this->setStrokeColor($opt['str']['r'], $opt['str']['g'], $opt['str']['b'], 1);
4025  
4026        $this->objects[$this->currentContents]['c'].=  "\n".$opt['lin'];
4027  
4028        //    $this->currentLineStyle = $opt['lin'];
4029  
4030      } else {
4031  
4032        $this->nStateStack++;
4033  
4034        $this->stateStack[$this->nStateStack] =  array(
4035                                                       'col' => $this->currentColour, 'str' => $this->currentStrokeColour, 'lin' => $this->currentLineStyle);
4036      }
4037  
4038      $this->objects[$this->currentContents]['c'].=  "\nq";
4039    }
4040  
4041  
4042    /**
4043     * restore a previously saved state
4044     */
4045    function  restoreState($pageEnd =  0) {
4046  
4047      if  (!$pageEnd) {
4048  
4049        $n =  $this->nStateStack;
4050  
4051        $this->currentColour =  $this->stateStack[$n]['col'];
4052  
4053        $this->currentStrokeColour =  $this->stateStack[$n]['str'];
4054  
4055        $this->objects[$this->currentContents]['c'].=  "\n".$this->stateStack[$n]['lin'];
4056  
4057        $this->currentLineStyle =  $this->stateStack[$n]['lin'];
4058  
4059        unset($this->stateStack[$n]);
4060  
4061        $this->nStateStack--;
4062      }
4063  
4064      $this->objects[$this->currentContents]['c'].=  "\nQ";
4065    }
4066  
4067  
4068    /**
4069     * make a loose object, the output will go into this object, until it is closed, then will revert to
4070     * the current one.
4071     * this object will not appear until it is included within a page.
4072     * the function will return the object number
4073     */
4074    function  openObject() {
4075  
4076      $this->nStack++;
4077  
4078      $this->stack[$this->nStack] =  array('c' => $this->currentContents, 'p' => $this->currentPage);
4079  
4080      // add a new object of the content type, to hold the data flow
4081      $this->numObj++;
4082  
4083      $this->o_contents($this->numObj, 'new');
4084  
4085      $this->currentContents =  $this->numObj;
4086  
4087      $this->looseObjects[$this->numObj] =  1;
4088  
4089  
4090      return  $this->numObj;
4091    }
4092  
4093  
4094    /**
4095     * open an existing object for editing
4096     */
4097    function  reopenObject($id) {
4098  
4099      $this->nStack++;
4100  
4101      $this->stack[$this->nStack] =  array('c' => $this->currentContents, 'p' => $this->currentPage);
4102  
4103      $this->currentContents =  $id;
4104  
4105      // also if this object is the primary contents for a page, then set the current page to its parent
4106      if  (isset($this->objects[$id]['onPage'])) {
4107  
4108        $this->currentPage =  $this->objects[$id]['onPage'];
4109      }
4110    }
4111  
4112  
4113    /**
4114     * close an object
4115     */
4116    function  closeObject() {
4117  
4118      // close the object, as long as there was one open in the first place, which will be indicated by
4119      // an objectId on the stack.
4120      if  ($this->nStack>0) {
4121  
4122        $this->currentContents =  $this->stack[$this->nStack]['c'];
4123  
4124        $this->currentPage =  $this->stack[$this->nStack]['p'];
4125  
4126        $this->nStack--;
4127  
4128        // easier to probably not worry about removing the old entries, they will be overwritten
4129        // if there are new ones.
4130  
4131      }
4132    }
4133  
4134  
4135    /**
4136     * stop an object from appearing on pages from this point on
4137     */
4138    function  stopObject($id) {
4139  
4140      // if an object has been appearing on pages up to now, then stop it, this page will
4141      // be the last one that could contian it.
4142      if  (isset($this->addLooseObjects[$id])) {
4143  
4144        $this->addLooseObjects[$id] =  '';
4145      }
4146    }
4147  
4148  
4149    /**
4150     * after an object has been created, it wil only show if it has been added, using this function.
4151     */
4152    function  addObject($id, $options =  'add') {
4153  
4154      // add the specified object to the page
4155      if  (isset($this->looseObjects[$id]) &&  $this->currentContents !=  $id) {
4156  
4157        // then it is a valid object, and it is not being added to itself
4158        switch ($options) {
4159  
4160        case  'all':
4161  
4162          // then this object is to be added to this page (done in the next block) and
4163          // all future new pages.
4164          $this->addLooseObjects[$id] =  'all';
4165  
4166        case  'add':
4167  
4168          if  (isset($this->objects[$this->currentContents]['onPage'])) {
4169  
4170            // then the destination contents is the primary for the page
4171            // (though this object is actually added to that page)
4172            $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
4173          }
4174  
4175          break;
4176  
4177        case  'even':
4178  
4179          $this->addLooseObjects[$id] =  'even';
4180  
4181          $pageObjectId =  $this->objects[$this->currentContents]['onPage'];
4182  
4183          if  ($this->objects[$pageObjectId]['info']['pageNum']%2 ==  0) {
4184  
4185            $this->addObject($id);
4186            // hacky huh :)
4187  
4188          }
4189  
4190          break;
4191  
4192        case  'odd':
4193  
4194          $this->addLooseObjects[$id] =  'odd';
4195  
4196          $pageObjectId =  $this->objects[$this->currentContents]['onPage'];
4197  
4198          if  ($this->objects[$pageObjectId]['info']['pageNum']%2 ==  1) {
4199  
4200            $this->addObject($id);
4201            // hacky huh :)
4202  
4203          }
4204  
4205          break;
4206  
4207        case  'next':
4208  
4209          $this->addLooseObjects[$id] =  'all';
4210  
4211          break;
4212  
4213        case  'nexteven':
4214  
4215          $this->addLooseObjects[$id] =  'even';
4216  
4217          break;
4218  
4219        case  'nextodd':
4220  
4221          $this->addLooseObjects[$id] =  'odd';
4222  
4223          break;
4224        }
4225      }
4226    }
4227  
4228  
4229    /**
4230     * return a storable representation of a specific object
4231     */
4232    function  serializeObject($id) {
4233  
4234      if  ( array_key_exists($id,  $this->objects))
4235        return  var_export($this->objects[$id],  true);
4236  
4237  
4238      return  null;
4239    }
4240  
4241  
4242    /**
4243     * restore an object from its stored representation.  returns its new object id.
4244     */
4245    function  restoreSerializedObject($obj) {
4246  
4247  
4248      $obj_id =  $this->openObject();
4249  
4250      eval('$this->objects[$obj_id] = ' . $obj . ';');
4251  
4252      $this->closeObject();
4253  
4254      return  $obj_id;
4255    }
4256  
4257  
4258  
4259    /**
4260     * add content to the documents info object
4261     */
4262    function  addInfo($label, $value =  0) {
4263  
4264      // this will only work if the label is one of the valid ones.
4265      // modify this so that arrays can be passed as well.
4266      // if $label is an array then assume that it is key => value pairs
4267      // else assume that they are both scalar, anything else will probably error
4268      if  (is_array($label)) {
4269  
4270        foreach ($label as  $l => $v) {
4271  
4272          $this->o_info($this->infoObject, $l, $v);
4273        }
4274      } else {
4275  
4276        $this->o_info($this->infoObject, $label, $value);
4277      }
4278    }
4279  
4280  
4281    /**
4282     * set the viewer preferences of the document, it is up to the browser to obey these.
4283     */
4284    function  setPreferences($label, $value =  0) {
4285  
4286      // this will only work if the label is one of the valid ones.
4287      if  (is_array($label)) {
4288  
4289        foreach ($label as  $l => $v) {
4290  
4291          $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v));
4292        }
4293      } else {
4294  
4295        $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value));
4296      }
4297    }
4298  
4299  
4300    /**
4301     * extract an integer from a position in a byte stream
4302     *
4303     * @access private
4304     */
4305    function  PRVT_getBytes(&$data, $pos, $num) {
4306  
4307      // return the integer represented by $num bytes from $pos within $data
4308      $ret =  0;
4309  
4310      for  ($i =  0;$i<$num;$i++) {
4311  
4312        $ret =  $ret*256;
4313  
4314        $ret+=  ord($data[$pos+$i]);
4315      }
4316  
4317      return  $ret;
4318    }
4319  
4320  
4321    /**
4322     * add a PNG image into the document, from a file
4323     * this should work with remote files
4324     */
4325    function  addPngFromFile($file, $x, $y, $w =  0, $h =  0) {
4326  
4327      // read in a png file, interpret it, then add to the system
4328      $error =  0;
4329  
4330      $tmp =  get_magic_quotes_runtime();
4331  
4332      set_magic_quotes_runtime(0);
4333  
4334      if  ( ($data =  file_get_contents($file)) ===  false) {
4335  
4336        //   $fp = @fopen($file,'rb');
4337        //   if ($fp){
4338        //     $data = '';
4339        //     while(!feof($fp)){
4340        //       $data .= fread($fp,1024);
4341        //     }
4342        //     fclose($fp);
4343        $error =  1;
4344  
4345        $errormsg =  'trouble opening file: '.$file;
4346      }
4347  
4348      set_magic_quotes_runtime($tmp);
4349  
4350  
4351      if  (!$error) {
4352  
4353        $header =  chr(137) .chr(80) .chr(78) .chr(71) .chr(13) .chr(10) .chr(26) .chr(10);
4354  
4355        if  (substr($data, 0, 8) !=  $header) {
4356  
4357          $error =  1;
4358  
4359          $errormsg =  'this file does not have a valid header';
4360        }
4361      }
4362  
4363  
4364      if  (!$error) {
4365  
4366        // set pointer
4367        $p =  8;
4368  
4369        $len =  strlen($data);
4370  
4371        // cycle through the file, identifying chunks
4372        $haveHeader =  0;
4373  
4374        $info =  array();
4375  
4376        $idata =  '';
4377  
4378        $pdata =  '';
4379  
4380        while  ($p < $len) {
4381  
4382          $chunkLen =  $this->PRVT_getBytes($data, $p, 4);
4383  
4384          $chunkType =  substr($data, $p+4, 4);
4385  
4386          //      echo $chunkType.' - '.$chunkLen.'<br>';
4387  
4388          switch ($chunkType) {
4389  
4390          case  'IHDR':
4391  
4392            // this is where all the file information comes from
4393            $info['width'] =  $this->PRVT_getBytes($data, $p+8, 4);
4394  
4395            $info['height'] =  $this->PRVT_getBytes($data, $p+12, 4);
4396  
4397            $info['bitDepth'] =  ord($data[$p+16]);
4398  
4399            $info['colorType'] =  ord($data[$p+17]);
4400  
4401            $info['compressionMethod'] =  ord($data[$p+18]);
4402  
4403            $info['filterMethod'] =  ord($data[$p+19]);
4404  
4405            $info['interlaceMethod'] =  ord($data[$p+20]);
4406  
4407            //print_r($info);
4408            $haveHeader =  1;
4409  
4410            if  ($info['compressionMethod'] !=  0) {
4411  
4412              $error =  1;
4413  
4414              $errormsg =  'unsupported compression method';
4415            }
4416  
4417            if  ($info['filterMethod'] !=  0) {
4418  
4419              $error =  1;
4420  
4421              $errormsg =  'unsupported filter method';
4422            }
4423  
4424            break;
4425  
4426          case  'PLTE':
4427  
4428            $pdata.=  substr($data, $p+8, $chunkLen);
4429  
4430            break;
4431  
4432          case  'IDAT':
4433  
4434            $idata.=  substr($data, $p+8, $chunkLen);
4435  
4436            break;
4437  
4438          case  'tRNS':
4439  
4440            //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
4441            //print "tRNS found, color type = ".$info['colorType']."\n";
4442            $transparency =  array();
4443  
4444            if  ($info['colorType'] ==  3) {
4445              // indexed color, rbg
4446              /* corresponding to entries in the plte chunk
4447               Alpha for palette index 0: 1 byte
4448               Alpha for palette index 1: 1 byte
4449               ...etc...
4450              */
4451              // there will be one entry for each palette entry. up until the last non-opaque entry.
4452              // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
4453              $transparency['type'] =  'indexed';
4454  
4455              $numPalette =  strlen($pdata) /3;
4456  
4457              $trans =  0;
4458  
4459              for  ($i =  $chunkLen;$i >=  0;$i--) {
4460  
4461                if  (ord($data[$p+8+$i]) ==  0) {
4462  
4463                  $trans =  $i;
4464                }
4465              }
4466  
4467              $transparency['data'] =  $trans;
4468            } elseif ($info['colorType'] ==  0) {
4469              // grayscale
4470              /* corresponding to entries in the plte chunk
4471               Gray: 2 bytes, range 0 .. (2^bitdepth)-1
4472              */
4473              //            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
4474              $transparency['type'] =  'indexed';
4475  
4476              $transparency['data'] =  ord($data[$p+8+1]);
4477            } elseif ($info['colorType'] ==  2) {
4478              // truecolor
4479              /* corresponding to entries in the plte chunk
4480               Red: 2 bytes, range 0 .. (2^bitdepth)-1
4481               Green: 2 bytes, range 0 .. (2^bitdepth)-1
4482               Blue: 2 bytes, range 0 .. (2^bitdepth)-1
4483              */
4484              $transparency['r'] =  $this->PRVT_getBytes($data, $p+8, 2);
4485              // r from truecolor
4486              $transparency['g'] =  $this->PRVT_getBytes($data, $p+10, 2);
4487              // g from truecolor
4488              $transparency['b'] =  $this->PRVT_getBytes($data, $p+12, 2);
4489              // b from truecolor
4490  
4491              $transparency['type'] = 'color-key';
4492              
4493            } else {
4494  
4495              //unsupported transparency type
4496  
4497            }
4498  
4499            // KS End new code
4500            break;
4501  
4502          default:
4503  
4504            break;
4505          }
4506  
4507  
4508          $p+=  $chunkLen+12;
4509        }
4510  
4511  
4512        if (!$haveHeader) {
4513  
4514          $error =  1;
4515  
4516          $errormsg =  'information header is missing';
4517        }
4518  
4519        if  (isset($info['interlaceMethod']) &&  $info['interlaceMethod']) {
4520  
4521          $error =  1;
4522  
4523          $errormsg =  'There appears to be no support for interlaced images in pdf.';
4524        }
4525      }
4526  
4527  
4528      if  (!$error &&  $info['bitDepth'] > 8) {
4529  
4530        $error =  1;
4531  
4532        $errormsg =  'only bit depth of 8 or less is supported';
4533      }
4534  
4535  
4536      if  (!$error) {
4537  
4538        if  ($info['colorType'] !=  2 &&  $info['colorType'] !=  0 &&  $info['colorType'] !=  3) {
4539  
4540          $error =  1;
4541  
4542          $errormsg =  'transparancey alpha channel not supported, transparency only supported for palette images.';
4543        } else {
4544  
4545          switch  ($info['colorType']) {
4546  
4547          case  3:
4548  
4549            $color =  'DeviceRGB';
4550  
4551            $ncolor =  1;
4552  
4553            break;
4554  
4555          case  2:
4556  
4557            $color =  'DeviceRGB';
4558  
4559            $ncolor =  3;
4560  
4561            break;
4562  
4563          case  0:
4564  
4565            $color =  'DeviceGray';
4566  
4567            $ncolor =  1;
4568  
4569            break;
4570          }
4571        }
4572      }
4573  
4574      if  ($error) {
4575  
4576        $this->addMessage('PNG error - ('.$file.') '.$errormsg);
4577  
4578        return;
4579      }
4580  
4581      if  ($w ==  0) {
4582  
4583        $w =  $h/$info['height']*$info['width'];
4584      }
4585  
4586      if  ($h ==  0) {
4587  
4588        $h =  $w*$info['height']/$info['width'];
4589      }
4590  
4591      //print_r($info);
4592      // so this image is ok... add it in.
4593      $this->numImages++;
4594  
4595      $im =  $this->numImages;
4596  
4597      $label =  'I'.$im;
4598  
4599      $this->numObj++;
4600  
4601      //  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
4602      $options =  array('label' => $label, 'data' => $idata, 'bitsPerComponent' => $info['bitDepth'], 'pdata' => $pdata, 'iw' => $info['width'], 'ih' => $info['height'], 'type' => 'png', 'color' => $color, 'ncolor' => $ncolor);
4603  
4604      if  (isset($transparency)) {
4605  
4606        $options['transparency'] =  $transparency;
4607      }
4608  
4609      $this->o_image($this->numObj, 'new', $options);
4610  
4611  
4612      $this->objects[$this->currentContents]['c'].=  "\nq";
4613  
4614      $this->objects[$this->currentContents]['c'].=  "\n".sprintf('%.3f', $w) ." 0 0 ".sprintf('%.3f', $h) ." ".sprintf('%.3f', $x) ." ".sprintf('%.3f', $y) ." cm";
4615  
4616      $this->objects[$this->currentContents]['c'].=  "\n/".$label.' Do';
4617  
4618      $this->objects[$this->currentContents]['c'].=  "\nQ";
4619    }
4620  
4621  
4622    /**
4623     * add a JPEG image into the document, from a file
4624     */
4625    function  addJpegFromFile($img, $x, $y, $w =  0, $h =  0) {
4626  
4627      // attempt to add a jpeg image straight from a file, using no GD commands
4628      // note that this function is unable to operate on a remote file.
4629  
4630      if  (!file_exists($img)) {
4631  
4632        return;
4633      }
4634  
4635  
4636      $tmp =  getimagesize($img);
4637  
4638      $imageWidth =  $tmp[0];
4639  
4640      $imageHeight =  $tmp[1];
4641  
4642  
4643      if  (isset($tmp['channels'])) {
4644  
4645        $channels =  $tmp['channels'];
4646      } else {
4647  
4648        $channels =  3;
4649      }
4650  
4651  
4652      if  ($w <=  0 &&  $h <=  0) {
4653  
4654        $w =  $imageWidth;
4655      }
4656  
4657      if  ($w ==  0) {
4658  
4659        $w =  $h/$imageHeight*$imageWidth;
4660      }
4661  
4662      if  ($h ==  0) {
4663  
4664        $h =  $w*$imageHeight/$imageWidth;
4665      }
4666  
4667  
4668      //$fp = fopen($img,'rb');
4669  
4670      $tmp =  get_magic_quotes_runtime();
4671  
4672      set_magic_quotes_runtime(0);
4673  
4674      $data =  file_get_contents($img);
4675  
4676      //fread($fp,filesize($img));
4677      set_magic_quotes_runtime($tmp);
4678  
4679  
4680      //fclose($fp);
4681  
4682      $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels);
4683    }
4684  
4685  
4686    /**
4687     * add an image into the document, from a GD object
4688     * this function is not all that reliable, and I would probably encourage people to use
4689     * the file based functions
4690     */
4691    function  addImage(&$img, $x, $y, $w =  0, $h =  0, $quality =  75) {
4692  
4693      // add a new image into the current location, as an external object
4694      // add the image at $x,$y, and with width and height as defined by $w & $h
4695  
4696      // note that this will only work with full colour images and makes them jpg images for display
4697      // later versions could present lossless image formats if there is interest.
4698  
4699      // there seems to be some problem here in that images that have quality set above 75 do not appear
4700      // not too sure why this is, but in the meantime I have restricted this to 75.
4701      if  ($quality>75) {
4702  
4703        $quality =  75;
4704      }
4705  
4706  
4707      // if the width or height are set to zero, then set the other one based on keeping the image
4708      // height/width ratio the same, if they are both zero, then give up :)
4709      $imageWidth =  imagesx($img);
4710  
4711      $imageHeight =  imagesy($img);
4712  
4713  
4714      if  ($w <=  0 &&  $h <=  0) {
4715  
4716        return;
4717      }
4718  
4719      if  ($w ==  0) {
4720  
4721        $w =  $h/$imageHeight*$imageWidth;
4722      }
4723  
4724      if  ($h ==  0) {
4725  
4726        $h =  $w*$imageHeight/$imageWidth;
4727      }
4728  
4729  
4730      // gotta get the data out of the img..
4731  
4732      // so I write to a temp file, and then read it back.. soo ugly, my apologies.
4733      $tmpDir =  '/tmp';
4734  
4735      $tmpName =  tempnam($tmpDir, 'img');
4736  
4737      imagejpeg($img, $tmpName, $quality);
4738  
4739      //$fp = fopen($tmpName,'rb');
4740  
4741      $tmp =  get_magic_quotes_runtime();
4742  
4743      set_magic_quotes_runtime(0);
4744  
4745      if  ( ($data =  file_get_contents($tmpName)) ===  false) {
4746  
4747        //   $fp = @fopen($tmpName,'rb');
4748        //   if ($fp){
4749        //     $data = '';
4750        //     while(!feof($fp)){
4751        //       $data .= fread($fp,1024);
4752        //     }
4753        //     fclose($fp);
4754        $error =  1;
4755  
4756        $errormsg =  'trouble opening file';
4757      }
4758  
4759      //  $data = fread($fp,filesize($tmpName));
4760      set_magic_quotes_runtime($tmp);
4761  
4762      //  fclose($fp);
4763      unlink($tmpName);
4764  
4765      $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight);
4766    }
4767  
4768  
4769    /**
4770     * common code used by the two JPEG adding functions
4771     *
4772     * @access private
4773     */
4774    function  addJpegImage_common(&$data, $x, $y, $w =  0, $h =  0, $imageWidth, $imageHeight, $channels =  3) {
4775  
4776      // note that this function is not to be called externally
4777      // it is just the common code between the GD and the file options
4778      $this->numImages++;
4779  
4780      $im =  $this->numImages;
4781  
4782      $label =  'I'.$im;
4783  
4784      $this->numObj++;
4785  
4786      $this->o_image($this->numObj, 'new', array('label' => $label, 'data' => &$data, 'iw' => $imageWidth, 'ih' => $imageHeight, 'channels' => $channels));
4787  
4788  
4789      $this->objects[$this->currentContents]['c'].=  "\nq";
4790  
4791      $this->objects[$this->currentContents]['c'].=  "\n".sprintf('%.3f', $w) ." 0 0 ".sprintf('%.3f', $h) ." ".sprintf('%.3f', $x) ." ".sprintf('%.3f', $y) ." cm";
4792  
4793      $this->objects[$this->currentContents]['c'].=  "\n/".$label.' Do';
4794  
4795      $this->objects[$this->currentContents]['c'].=  "\nQ";
4796    }
4797  
4798  
4799    /**
4800     * specify where the document should open when it first starts
4801     */
4802    function  openHere($style, $a =  0, $b =  0, $c =  0) {
4803  
4804      // this function will open the document at a specified page, in a specified style
4805      // the values for style, and the required paramters are:
4806      // 'XYZ'  left, top, zoom
4807      // 'Fit'
4808      // 'FitH' top
4809      // 'FitV' left
4810      // 'FitR' left,bottom,right
4811      // 'FitB'
4812      // 'FitBH' top
4813      // 'FitBV' left
4814      $this->numObj++;
4815  
4816      $this->o_destination($this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c));
4817  
4818      $id =  $this->catalogId;
4819  
4820      $this->o_catalog($id, 'openHere', $this->numObj);
4821    }
4822  
4823  
4824    /**
4825     * create a labelled destination within the document
4826     */
4827    function  addDestination($label, $style, $a =  0, $b =  0, $c =  0) {
4828  
4829      // associates the given label with the destination, it is done this way so that a destination can be specified after
4830      // it has been linked to
4831      // styles are the same as the 'openHere' function
4832      $this->numObj++;
4833  
4834      $this->o_destination($this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c));
4835  
4836      $id =  $this->numObj;
4837  
4838      // store the label->idf relationship, note that this means that labels can be used only once
4839      $this->destinations["$label"] =  $id;
4840    }
4841  
4842  
4843    /**
4844     * define font families, this is used to initialize the font families for the default fonts
4845     * and for the user to add new ones for their fonts. The default bahavious can be overridden should
4846     * that be desired.
4847     */
4848    function  setFontFamily($family, $options =  '') {
4849  
4850      if  (!is_array($options)) {
4851  
4852        if  ($family ==  'init') {
4853  
4854          // set the known family groups
4855          // these font families will be used to enable bold and italic markers to be included
4856          // within text streams. html forms will be used... <b></b> <i></i>
4857          $this->fontFamilies['Helvetica.afm'] =
4858            array('b' => 'Helvetica-Bold.afm',
4859                  'i' => 'Helvetica-Oblique.afm',
4860                  'bi' => 'Helvetica-BoldOblique.afm',
4861                  'ib' => 'Helvetica-BoldOblique.afm');
4862  
4863          $this->fontFamilies['Courier.afm'] =
4864            array('b' => 'Courier-Bold.afm',
4865                  'i' => 'Courier-Oblique.afm',
4866                  'bi' => 'Courier-BoldOblique.afm',
4867                  'ib' => 'Courier-BoldOblique.afm');
4868  
4869          $this->fontFamilies['Times-Roman.afm'] =
4870            array('b' => 'Times-Bold.afm',
4871                  'i' => 'Times-Italic.afm',
4872                  'bi' => 'Times-BoldItalic.afm',
4873                  'ib' => 'Times-BoldItalic.afm');
4874        }
4875      } else {
4876  
4877        // the user is trying to set a font family
4878        // note that this can also be used to set the base ones to something else
4879        if  (strlen($family)) {
4880  
4881          $this->fontFamilies[$family] =  $options;
4882        }
4883      }
4884    }
4885  
4886  
4887    /**
4888     * used to add messages for use in debugging
4889     */
4890    function  addMessage($message) {
4891  
4892      $this->messages.=  $message."\n";
4893    }
4894  
4895  
4896    /**
4897     * a few functions which should allow the document to be treated transactionally.
4898     */
4899    function  transaction($action) {
4900  
4901      switch  ($action) {
4902  
4903      case  'start':
4904  
4905        // store all the data away into the checkpoint variable
4906        $data =  get_object_vars($this);
4907  
4908        $this->checkpoint =  $data;
4909  
4910        unset($data);
4911  
4912        break;
4913  
4914      case  'commit':
4915  
4916        if  (is_array($this->checkpoint) &&  isset($this->checkpoint['checkpoint'])) {
4917  
4918          $tmp =  $this->checkpoint['checkpoint'];
4919  
4920          $this->checkpoint =  $tmp;
4921  
4922          unset($tmp);
4923        } else {
4924  
4925          $this->checkpoint =  '';
4926        }
4927  
4928        break;
4929  
4930      case  'rewind':
4931  
4932        // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
4933        if  (is_array($this->checkpoint)) {
4934  
4935          // can only abort if were inside a checkpoint
4936          $tmp =  $this->checkpoint;
4937  
4938          foreach ($tmp as  $k => $v) {
4939  
4940            if  ($k !=  'checkpoint') {
4941  
4942              $this->$k =  $v;
4943            }
4944          }
4945  
4946          unset($tmp);
4947        }
4948  
4949        break;
4950  
4951      case  'abort':
4952  
4953        if  (is_array($this->checkpoint)) {
4954  
4955          // can only abort if were inside a checkpoint
4956          $tmp =  $this->checkpoint;
4957  
4958          foreach ($tmp as  $k => $v) {
4959  
4960            $this->$k =  $v;
4961          }
4962  
4963          unset($tmp);
4964        }
4965  
4966        break;
4967      }
4968    }
4969  }
4970  // end of class
4971  
4972  ?>