PageRenderTime 76ms CodeModel.GetById 10ms app.highlight 49ms RepoModel.GetById 2ms app.codeStats 0ms

/library/classes/class.pdf.php

https://bitbucket.org/DenizYldrm/openemr
PHP | 3088 lines | 2179 code | 170 blank | 739 comment | 394 complexity | 4d3201419558d62a7a758d609f573604 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file