PageRenderTime 568ms CodeModel.GetById 64ms app.highlight 334ms RepoModel.GetById 16ms app.codeStats 9ms

/filemanager/tp/dompdf/lib/class.pdf.php

https://github.com/muchael/expressolivre
PHP | 5635 lines | 2624 code | 1946 blank | 1065 comment | 608 complexity | 070711c7aa2ed1ae6fc7a337afeaaab9 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   * Extended by Orion Richardson to support Unicode / UTF-8 characters using
  14   * TCPDF and others as a guide.
  15   *
  16   * IMPORTANT NOTE
  17   * there is no warranty, implied or otherwise with this software.
  18   *
  19   * LICENCE
  20   * This code has been placed in the Public Domain for all to enjoy.
  21   *
  22   * @author       Wayne Munro <pdf@ros.co.nz>
  23   * @contributor  Orion Richardson <orionr@yahoo.com>
  24   * @contributor  Helmut Tischer <htischer@weihenstephan.org>
  25   * @version  009
  26   * @package  Cpdf
  27   *
  28   * Changes
  29   * @contributor Helmut Tischer <htischer@weihenstephan.org>
  30   * @version 0.5.1.htischer.20090507
  31   * - On multiple identical png and jpg images, put only one copy into the pdf file and refer to it.
  32   *   This reduces file size and rendering time.
  33   * - Allow font metrics cache to be a different folder as the font metrics. This allows a read only installation.
  34   * - Allow adding images directly from a gd object. This increases performance by avoiding temporary files.
  35   * - On png image files remove alpa channel to allow display of typical png files in pdf.
  36   * - On addImage avoid temporary file. Todo: Duplicate Image (currently not used)
  37   * - Add a check function, whether image is already cached, This avoids double creation by caller which saves
  38   *   CPU time and memory.
  39   * @contributor Helmut Tischer <htischer@weihenstephan.org>
  40   * @version dompdf_trunk_with_helmut_mods.20090524
  41   * - Allow temp and fontcache folders to be passed in by class creator
  42   * @version dompdf_trunk_with_helmut_mods.20090528
  43   * - typo 'decent' instead of 'descent' at various locations made getFontDescender worthless
  44   */
  45class  Cpdf {
  46
  47
  48  /**
  49   * the current number of pdf objects in the document
  50   */
  51  public  $numObj = 0;
  52
  53  /**
  54   * this array contains all of the pdf objects, ready for final assembly
  55   */
  56  public  $objects =  array();
  57
  58  /**
  59   * the objectId (number within the objects array) of the document catalog
  60   */
  61  public  $catalogId;
  62
  63  /**
  64   * array carrying information about the fonts that the system currently knows about
  65   * used to ensure that a font is not loaded twice, among other things
  66   */
  67  public  $fonts = array();
  68
  69  /**
  70   * a record of the current font
  71   */
  72  public  $currentFont = '';
  73
  74  /**
  75   * the current base font
  76   */
  77  public  $currentBaseFont = '';
  78
  79  /**
  80   * the number of the current font within the font array
  81   */
  82  public  $currentFontNum = 0;
  83
  84  /**
  85   *
  86   */
  87  public  $currentNode;
  88
  89  /**
  90   * object number of the current page
  91   */
  92  public  $currentPage;
  93
  94  /**
  95   * object number of the currently active contents block
  96   */
  97  public  $currentContents;
  98
  99  /**
 100   * number of fonts within the system
 101   */
 102  public  $numFonts = 0;
 103
 104  /**
 105   * Number of graphic state resources used
 106   */
 107  private  $numStates =  0;
 108
 109
 110  /**
 111   * current colour for fill operations, defaults to inactive value, all three components should be between 0 and 1 inclusive when active
 112   */
 113  public  $currentColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
 114
 115  /**
 116   * current colour for stroke operations (lines etc.)
 117   */
 118  public  $currentStrokeColour = array('r'=>-1, 'g'=>-1, 'b'=>-1);
 119
 120  /**
 121   * current style that lines are drawn in
 122   */
 123  public  $currentLineStyle = '';
 124
 125  /**
 126   * current line transparency (partial graphics state)
 127   */
 128  public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0);
 129  
 130  /**
 131   * current fill transparency (partial graphics state)
 132   */
 133  public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0);
 134  
 135  /**
 136   * an array which is used to save the state of the document, mainly the colours and styles
 137   * it is used to temporarily change to another state, the change back to what it was before
 138   */
 139  public  $stateStack =  array();
 140
 141  /**
 142   * number of elements within the state stack
 143   */
 144  public  $nStateStack =  0;
 145
 146  /**
 147   * number of page objects within the document
 148   */
 149  public  $numPages = 0;
 150
 151  /**
 152   * object Id storage stack
 153   */
 154  public  $stack = array();
 155
 156  /**
 157   * number of elements within the object Id storage stack
 158   */
 159  public  $nStack = 0;
 160
 161  /**
 162   * an array which contains information about the objects which are not firmly attached to pages
 163   * these have been added with the addObject function
 164   */
 165  public  $looseObjects = array();
 166
 167  /**
 168   * array contains infomation about how the loose objects are to be added to the document
 169   */
 170  public  $addLooseObjects = array();
 171
 172  /**
 173   * the objectId of the information object for the document
 174   * this contains authorship, title etc.
 175   */
 176  public  $infoObject = 0;
 177
 178  /**
 179   * number of images being tracked within the document
 180   */
 181  public  $numImages = 0;
 182
 183  /**
 184   * an array containing options about the document
 185   * it defaults to turning on the compression of the objects
 186   */
 187  public  $options = array('compression'=>1);
 188
 189  /**
 190   * the objectId of the first page of the document
 191   */
 192  public  $firstPageId;
 193
 194  /**
 195   * used to track the last used value of the inter-word spacing, this is so that it is known
 196   * when the spacing is changed.
 197   */
 198  public  $wordSpaceAdjust = 0;
 199
 200  /**
 201   * the object Id of the procset object
 202   */
 203  public  $procsetObjectId;
 204
 205  /**
 206   * store the information about the relationship between font families
 207   * this used so that the code knows which font is the bold version of another font, etc.
 208   * the value of this array is initialised in the constuctor function.
 209   */
 210  public  $fontFamilies =  array();
 211 
 212  /**
 213   * folder for php serialized formats of font metrics files.
 214   * If empty string, use same folder as original metrics files.
 215   * This can be passed in from class creator.
 216   * If this folder does not exist or is not writable, Cpdf will be **much** slower.
 217   * Because of potential trouble with php safe mode, folder cannot be created at runtime.
 218   */
 219  public  $fontcache = '';
 220  
 221  /**
 222   * temporary folder.
 223   * If empty string, will attempty system tmp folder.
 224   * This can be passed in from class creator.
 225   * Only used for conversion of gd images to jpeg images.
 226   */
 227  public  $tmp = '';
 228
 229  /**
 230   * track if the current font is bolded or italicised
 231   */
 232  public  $currentTextState =  '';
 233
 234  /**
 235   * messages are stored here during processing, these can be selected afterwards to give some useful debug information
 236   */
 237  public  $messages = '';
 238
 239  /**
 240   * the ancryption array for the document encryption is stored here
 241   */
 242  public  $arc4 = '';
 243
 244  /**
 245   * the object Id of the encryption information
 246   */
 247  public  $arc4_objnum = 0;
 248
 249  /**
 250   * the file identifier, used to uniquely identify a pdf document
 251   */
 252  public  $fileIdentifier = '';
 253
 254  /**
 255   * a flag to say if a document is to be encrypted or not
 256   */
 257  public  $encrypted = 0;
 258
 259  /**
 260   * the ancryption key for the encryption of all the document content (structure is not encrypted)
 261   */
 262  public  $encryptionKey = '';
 263
 264  /**
 265   * array which forms a stack to keep track of nested callback functions
 266   */
 267  public  $callback =  array();
 268
 269  /**
 270   * the number of callback functions in the callback array
 271   */
 272  public  $nCallback =  0;
 273
 274  /**
 275   * store label->id pairs for named destinations, these will be used to replace internal links
 276   * done this way so that destinations can be defined after the location that links to them
 277   */
 278  public  $destinations =  array();
 279
 280  /**
 281   * store the stack for the transaction commands, each item in here is a record of the values of all the
 282   * publiciables within the class, so that the user can rollback at will (from each 'start' command)
 283   * note that this includes the objects array, so these can be large.
 284   */
 285  public  $checkpoint =  '';
 286
 287  /* Table of Image origin filenames and image labels which were already added with o_image().
 288   * Allows to merge identical images
 289   */
 290  public  $imagelist = array();
 291
 292  /**
 293   * whether the text passed in should be treated as Unicode or just local character set.
 294   */
 295  public  $isUnicode = false;
 296
 297  /**
 298   * class constructor
 299   * this will start a new document
 300   * @var array array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
 301   * @var boolean whether text will be treated as Unicode or not.
 302   */
 303  function  Cpdf ($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') {
 304
 305    $this->isUnicode = $isUnicode;
 306
 307    $this->fontcache = $fontcache;
 308
 309    $this->tmp = $tmp;
 310
 311    $this->newDocument($pageSize);
 312
 313
 314    // also initialize the font families that are known about already
 315    $this->setFontFamily('init');
 316
 317    //  $this->fileIdentifier = md5('xxxxxxxx'.time());
 318
 319
 320  }
 321
 322
 323  /**
 324   * Document object methods (internal use only)
 325   *
 326   * There is about one object method for each type of object in the pdf document
 327   * Each function has the same call list ($id,$action,$options).
 328   * $id = the object ID of the object, or what it is to be if it is being created
 329   * $action = a string specifying the action to be performed, though ALL must support:
 330   *           'new' - create the object with the id $id
 331   *           'out' - produce the output for the pdf object
 332   * $options = optional, a string or array containing the various parameters for the object
 333   *
 334   * These, in conjunction with the output function are the ONLY way for output to be produced
 335   * within the pdf 'file'.
 336   */
 337
 338  /**
 339   *destination object, used to specify the location for the user to jump to, presently on opening
 340   */
 341  function  o_destination($id, $action, $options = '') {
 342
 343    if  ($action != 'new') {
 344
 345      $o = & $this->objects[$id];
 346    }
 347
 348    switch ($action) {
 349
 350    case  'new':
 351
 352      $this->objects[$id] = array('t'=>'destination', 'info'=>array());
 353
 354      $tmp =  '';
 355
 356      switch  ($options['type']) {
 357
 358      case  'XYZ':
 359
 360      case  'FitR':
 361
 362        $tmp =   ' '.$options['p3'].$tmp;
 363
 364      case  'FitH':
 365
 366      case  'FitV':
 367
 368      case  'FitBH':
 369
 370      case  'FitBV':
 371
 372        $tmp =   ' '.$options['p1'].' '.$options['p2'].$tmp;
 373
 374      case  'Fit':
 375
 376      case  'FitB':
 377
 378        $tmp =   $options['type'].$tmp;
 379
 380        $this->objects[$id]['info']['string'] = $tmp;
 381
 382        $this->objects[$id]['info']['page'] = $options['page'];
 383      }
 384
 385      break;
 386
 387    case  'out':
 388
 389      $tmp =  $o['info'];
 390
 391      $res = "\n".$id." 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
 392
 393      return  $res;
 394
 395      break;
 396    }
 397  }
 398
 399
 400  /**
 401   * set the viewer preferences
 402   */
 403  function  o_viewerPreferences($id, $action, $options = '') {
 404
 405    if  ($action != 'new') {
 406
 407      $o = & $this->objects[$id];
 408    }
 409
 410    switch  ($action) {
 411
 412    case  'new':
 413
 414      $this->objects[$id] = array('t'=>'viewerPreferences', 'info'=>array());
 415
 416      break;
 417
 418    case  'add':
 419
 420      foreach($options as  $k=>$v) {
 421
 422        switch  ($k) {
 423
 424        case  'HideToolbar':
 425
 426        case  'HideMenubar':
 427
 428        case  'HideWindowUI':
 429
 430        case  'FitWindow':
 431
 432        case  'CenterWindow':
 433
 434        case  'NonFullScreenPageMode':
 435
 436        case  'Direction':
 437
 438          $o['info'][$k] = $v;
 439
 440          break;
 441        }
 442      }
 443
 444      break;
 445
 446    case  'out':
 447
 448
 449      $res = "\n".$id." 0 obj\n".'<< ';
 450
 451      foreach($o['info'] as  $k=>$v) {
 452
 453        $res.= "\n/".$k.' '.$v;
 454      }
 455
 456      $res.= "\n>>\n";
 457
 458      return  $res;
 459
 460      break;
 461    }
 462  }
 463
 464
 465  /**
 466   * define the document catalog, the overall controller for the document
 467   */
 468  function  o_catalog($id, $action, $options = '') {
 469
 470    if  ($action != 'new') {
 471
 472      $o = & $this->objects[$id];
 473    }
 474
 475    switch  ($action) {
 476
 477    case  'new':
 478
 479      $this->objects[$id] = array('t'=>'catalog', 'info'=>array());
 480
 481      $this->catalogId = $id;
 482
 483      break;
 484
 485    case  'outlines':
 486
 487    case  'pages':
 488
 489    case  'openHere':
 490
 491      $o['info'][$action] = $options;
 492
 493      break;
 494
 495    case  'viewerPreferences':
 496
 497      if  (!isset($o['info']['viewerPreferences'])) {
 498
 499        $this->numObj++;
 500
 501        $this->o_viewerPreferences($this->numObj, 'new');
 502
 503        $o['info']['viewerPreferences'] = $this->numObj;
 504      }
 505
 506      $vp =  $o['info']['viewerPreferences'];
 507
 508      $this->o_viewerPreferences($vp, 'add', $options);
 509
 510      break;
 511
 512    case  'out':
 513
 514      $res = "\n".$id." 0 obj\n".'<< /Type /Catalog';
 515
 516      foreach($o['info'] as  $k=>$v) {
 517
 518        switch ($k) {
 519
 520        case  'outlines':
 521
 522          $res.= "\n".'/Outlines '.$v.' 0 R';
 523
 524          break;
 525
 526        case  'pages':
 527
 528          $res.= "\n".'/Pages '.$v.' 0 R';
 529
 530          break;
 531
 532        case  'viewerPreferences':
 533
 534          $res.= "\n".'/ViewerPreferences '.$o['info']['viewerPreferences'].' 0 R';
 535
 536          break;
 537
 538        case  'openHere':
 539
 540          $res.= "\n".'/OpenAction '.$o['info']['openHere'].' 0 R';
 541
 542          break;
 543        }
 544      }
 545
 546      $res.= " >>\nendobj";
 547
 548      return  $res;
 549
 550      break;
 551    }
 552  }
 553
 554
 555  /**
 556   * object which is a parent to the pages in the document
 557   */
 558  function  o_pages($id, $action, $options = '') {
 559
 560    if  ($action != 'new') {
 561
 562      $o = & $this->objects[$id];
 563    }
 564
 565    switch  ($action) {
 566
 567    case  'new':
 568
 569      $this->objects[$id] = array('t'=>'pages', 'info'=>array());
 570
 571      $this->o_catalog($this->catalogId, 'pages', $id);
 572
 573      break;
 574
 575    case  'page':
 576
 577      if  (!is_array($options)) {
 578
 579        // then it will just be the id of the new page
 580        $o['info']['pages'][] = $options;
 581      } else {
 582
 583        // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
 584        // and pos is either 'before' or 'after', saying where this page will fit.
 585        if  (isset($options['id']) &&  isset($options['rid']) &&  isset($options['pos'])) {
 586
 587          $i =  array_search($options['rid'], $o['info']['pages']);
 588
 589          if  (isset($o['info']['pages'][$i]) &&  $o['info']['pages'][$i] == $options['rid']) {
 590
 591            // then there is a match
 592            // make a space
 593            switch  ($options['pos']) {
 594
 595            case  'before':
 596
 597              $k =  $i;
 598
 599              break;
 600
 601            case  'after':
 602
 603              $k = $i+1;
 604
 605              break;
 606
 607            default:
 608
 609              $k = -1;
 610
 611              break;
 612            }
 613
 614            if  ($k >= 0) {
 615
 616              for  ($j = count($o['info']['pages']) -1;$j >= $k;$j--) {
 617
 618                $o['info']['pages'][$j+1] = $o['info']['pages'][$j];
 619              }
 620
 621              $o['info']['pages'][$k] = $options['id'];
 622            }
 623          }
 624        }
 625      }
 626
 627      break;
 628
 629    case  'procset':
 630
 631      $o['info']['procset'] = $options;
 632
 633      break;
 634
 635    case  'mediaBox':
 636
 637      $o['info']['mediaBox'] = $options;
 638      // which should be an array of 4 numbers
 639      break;
 640
 641    case  'font':
 642
 643      $o['info']['fonts'][] = array('objNum'=>$options['objNum'], 'fontNum'=>$options['fontNum']);
 644
 645      break;
 646
 647
 648    case  'extGState':
 649
 650      $o['info']['extGStates'][] =  array('objNum' => $options['objNum'],  'stateNum' => $options['stateNum']);
 651
 652      break;
 653
 654
 655    case  'xObject':
 656
 657      $o['info']['xObjects'][] = array('objNum'=>$options['objNum'], 'label'=>$options['label']);
 658
 659      break;
 660
 661    case  'out':
 662
 663      if  (count($o['info']['pages'])) {
 664
 665        $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Kids [";
 666
 667        foreach($o['info']['pages'] as  $k=>$v) {
 668
 669          $res.= $v." 0 R\n";
 670        }
 671
 672        $res.= "]\n/Count ".count($this->objects[$id]['info']['pages']);
 673
 674
 675        if  ( (isset($o['info']['fonts']) &&  count($o['info']['fonts'])) ||
 676              isset($o['info']['procset']) ||
 677              (isset($o['info']['extGStates']) &&  count($o['info']['extGStates']))) {
 678
 679
 680          $res.= "\n/Resources <<";
 681
 682          if  (isset($o['info']['procset'])) {
 683
 684            $res.= "\n/ProcSet ".$o['info']['procset']." 0 R";
 685          }
 686
 687          if  (isset($o['info']['fonts']) &&  count($o['info']['fonts'])) {
 688
 689            $res.= "\n/Font << ";
 690
 691            foreach($o['info']['fonts'] as  $finfo) {
 692
 693              $res.= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
 694            }
 695
 696            $res.= "\n>>";
 697          }
 698
 699          if  (isset($o['info']['xObjects']) &&  count($o['info']['xObjects'])) {
 700
 701            $res.= "\n/XObject << ";
 702
 703            foreach($o['info']['xObjects'] as  $finfo) {
 704
 705              $res.= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
 706            }
 707
 708            $res.= "\n>>";
 709          }
 710
 711          if  ( isset($o['info']['extGStates']) &&  count($o['info']['extGStates'])) {
 712
 713            $res.=  "\n/ExtGState << ";
 714
 715            foreach ($o['info']['extGStates'] as  $gstate) {
 716
 717              $res.=  "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
 718            }
 719
 720            $res.=  "\n>>";
 721          }
 722
 723
 724          $res.= "\n>>";
 725
 726          if  (isset($o['info']['mediaBox'])) {
 727
 728            $tmp = $o['info']['mediaBox'];
 729
 730            $res.= "\n/MediaBox [".sprintf('%.3F', $tmp[0]) .' '.sprintf('%.3F', $tmp[1]) .' '.sprintf('%.3F', $tmp[2]) .' '.sprintf('%.3F', $tmp[3]) .']';
 731          }
 732        }
 733
 734        $res.= "\n >>\nendobj";
 735      } else {
 736
 737        $res = "\n".$id." 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
 738      }
 739
 740      return  $res;
 741
 742      break;
 743    }
 744  }
 745
 746
 747  /**
 748   * define the outlines in the doc, empty for now
 749   */
 750  function  o_outlines($id, $action, $options = '') {
 751
 752    if  ($action != 'new') {
 753
 754      $o = & $this->objects[$id];
 755    }
 756
 757    switch  ($action) {
 758
 759    case  'new':
 760
 761      $this->objects[$id] = array('t'=>'outlines', 'info'=>array('outlines'=>array()));
 762
 763      $this->o_catalog($this->catalogId, 'outlines', $id);
 764
 765      break;
 766
 767    case  'outline':
 768
 769      $o['info']['outlines'][] = $options;
 770
 771      break;
 772
 773    case  'out':
 774
 775      if  (count($o['info']['outlines'])) {
 776
 777        $res = "\n".$id." 0 obj\n<< /Type /Outlines /Kids [";
 778
 779        foreach($o['info']['outlines'] as  $k=>$v) {
 780
 781          $res.= $v." 0 R ";
 782        }
 783
 784        $res.= "] /Count ".count($o['info']['outlines']) ." >>\nendobj";
 785      } else {
 786
 787        $res = "\n".$id." 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
 788      }
 789
 790      return  $res;
 791
 792      break;
 793    }
 794  }
 795
 796
 797  /**
 798   * an object to hold the font description
 799   */
 800  function  o_font($id, $action, $options = '') {
 801
 802    if  ($action != 'new') {
 803
 804      $o = & $this->objects[$id];
 805    }
 806
 807    switch  ($action) {
 808
 809    case  'new':
 810
 811      $this->objects[$id] =  array('t' => 'font', 'info' => array('name' => $options['name'], 'fontFileName' => $options['fontFileName'], 'SubType' => 'Type1'));
 812
 813      $fontNum =  $this->numFonts;
 814
 815      $this->objects[$id]['info']['fontNum'] =  $fontNum;
 816
 817      // deal with the encoding and the differences
 818      if  (isset($options['differences'])) {
 819
 820        // then we'll need an encoding dictionary
 821        $this->numObj++;
 822
 823        $this->o_fontEncoding($this->numObj, 'new', $options);
 824
 825        $this->objects[$id]['info']['encodingDictionary'] =  $this->numObj;
 826      } else  if  (isset($options['encoding'])) {
 827
 828        // we can specify encoding here
 829        switch ($options['encoding']) {
 830
 831        case  'WinAnsiEncoding':
 832
 833        case  'MacRomanEncoding':
 834
 835        case  'MacExpertEncoding':
 836
 837          $this->objects[$id]['info']['encoding'] =  $options['encoding'];
 838
 839          break;
 840
 841        case  'none':
 842
 843          break;
 844
 845        default:
 846
 847          $this->objects[$id]['info']['encoding'] =  'WinAnsiEncoding';
 848
 849          break;
 850        }
 851      } else {
 852
 853        $this->objects[$id]['info']['encoding'] =  'WinAnsiEncoding';
 854      }
 855
 856      if ($this->isUnicode) {
 857
 858        // For Unicode fonts, we need to incorporate font data into
 859        // sub-sections that are linked from the primary font section.
 860        // Look at o_fontGIDtoCID and o_fontDescendentCID functions
 861        // for more informaiton.
 862        //
 863        // All of this code is adapted from the excellent changes made to
 864        // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
 865
 866        $toUnicodeId = ++$this->numObj;
 867        $this->o_contents($toUnicodeId, 'new', 'raw');
 868        $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
 869        
 870        $stream =  "/CIDInit /ProcSet findresource begin\n";
 871        $stream.=  "12 dict begin\n";
 872        $stream.=  "begincmap\n";
 873        $stream.=  "/CIDSystemInfo\n";
 874        $stream.=  "<</Registry (Adobe)\n";
 875        $stream.=  "/Ordering (UCS)\n";
 876        $stream.=  "/Supplement 0\n";
 877        $stream.=  ">> def\n";
 878        $stream.=  "/CMapName /Adobe-Identity-UCS def\n";
 879        $stream.=  "/CMapType 2 def\n";
 880        $stream.=  "1 begincodespacerange\n";
 881        $stream.=  "<0000> <FFFF>\n";
 882        $stream.=  "endcodespacerange\n";
 883        $stream.=  "1 beginbfrange\n";
 884        $stream.=  "<0000> <FFFF> <0000>\n";
 885        $stream.=  "endbfrange\n";
 886        $stream.=  "endcmap\n";
 887        $stream.=  "CMapName currentdict /CMap defineresource pop\n";
 888        $stream.=  "end\n";
 889        $stream.=  "end\n";
 890
 891        $res =   "<</Length " . mb_strlen($stream) . " >>\n";
 892        $res .=  "stream\n" . $stream . "endstream";
 893
 894        $this->objects[$toUnicodeId]['c'] = $res;
 895
 896        $cidFontId = ++$this->numObj;
 897        $this->o_fontDescendentCID($cidFontId, 'new', $options);
 898        $this->objects[$id]['info']['cidFont'] = $cidFontId;
 899      }
 900      
 901      // also tell the pages node about the new font
 902      $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id));
 903
 904      break;
 905
 906
 907    case  'add':
 908
 909      foreach ($options as  $k => $v) {
 910
 911        switch  ($k) {
 912
 913        case  'BaseFont':
 914
 915          $o['info']['name'] =  $v;
 916
 917          break;
 918
 919        case  'FirstChar':
 920
 921        case  'LastChar':
 922
 923        case  'Widths':
 924
 925        case  'FontDescriptor':
 926
 927        case  'SubType':
 928
 929          $this->addMessage('o_font '.$k." : ".$v);
 930
 931          $o['info'][$k] =  $v;
 932
 933          break;
 934        }
 935      }
 936
 937      // pass values down to descendent font
 938      if (isset($o['info']['cidFont'])) {
 939
 940        $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options);
 941      }
 942        
 943      break;
 944
 945
 946    case  'out':
 947
 948      if ($this->isUnicode) {
 949
 950        // For Unicode fonts, we need to incorporate font data into
 951        // sub-sections that are linked from the primary font section.
 952        // Look at o_fontGIDtoCID and o_fontDescendentCID functions
 953        // for more informaiton.
 954        //
 955        // All of this code is adapted from the excellent changes made to
 956        // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
 957
 958        $res =  "\n".$id." 0 obj\n<</Type /Font\n/Subtype /Type0\n";
 959        $res.=  "/BaseFont /".$o['info']['name']."\n";
 960
 961        // The horizontal identity mapping for 2-byte CIDs; may be used
 962        // with CIDFonts using any Registry, Ordering, and Supplement values.
 963        $res.=  "/Encoding /Identity-H\n";
 964        $res.=  "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n";
 965        $res.=  "/ToUnicode ".$o['info']['toUnicode']." 0 R\n";
 966        $res.=  ">>\n";
 967        $res.=  "endobj";
 968
 969      } else {
 970      $res =  "\n".$id." 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
 971
 972      $res.=  "/Name /F".$o['info']['fontNum']."\n";
 973
 974      $res.=  "/BaseFont /".$o['info']['name']."\n";
 975
 976      if  (isset($o['info']['encodingDictionary'])) {
 977
 978        // then place a reference to the dictionary
 979        $res.=  "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
 980      } else  if  (isset($o['info']['encoding'])) {
 981
 982        // use the specified encoding
 983        $res.=  "/Encoding /".$o['info']['encoding']."\n";
 984      }
 985
 986      if  (isset($o['info']['FirstChar'])) {
 987
 988        $res.=  "/FirstChar ".$o['info']['FirstChar']."\n";
 989      }
 990
 991      if  (isset($o['info']['LastChar'])) {
 992
 993        $res.=  "/LastChar ".$o['info']['LastChar']."\n";
 994      }
 995
 996      if  (isset($o['info']['Widths'])) {
 997
 998        $res.=  "/Widths ".$o['info']['Widths']." 0 R\n";
 999      }
1000
1001      if  (isset($o['info']['FontDescriptor'])) {
1002
1003        $res.=  "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1004      }
1005
1006        $res.=  ">>\n";
1007        $res.=  "endobj";
1008        
1009      }
1010
1011      return  $res;
1012
1013      break;
1014    }
1015  }
1016
1017
1018  /**
1019   * a font descriptor, needed for including additional fonts
1020   */
1021  function  o_fontDescriptor($id, $action, $options = '') {
1022
1023    if  ($action != 'new') {
1024
1025      $o = & $this->objects[$id];
1026    }
1027
1028    switch  ($action) {
1029
1030    case  'new':
1031
1032      $this->objects[$id] = array('t'=>'fontDescriptor', 'info'=>$options);
1033
1034      break;
1035
1036    case  'out':
1037
1038      $res = "\n".$id." 0 obj\n<< /Type /FontDescriptor\n";
1039
1040      foreach ($o['info'] as  $label => $value) {
1041
1042        switch  ($label) {
1043
1044        case  'Ascent':
1045
1046        case  'CapHeight':
1047
1048        case  'Descent':
1049
1050        case  'Flags':
1051
1052        case  'ItalicAngle':
1053
1054        case  'StemV':
1055
1056        case  'AvgWidth':
1057
1058        case  'Leading':
1059
1060        case  'MaxWidth':
1061
1062        case  'MissingWidth':
1063
1064        case  'StemH':
1065
1066        case  'XHeight':
1067
1068        case  'CharSet':
1069
1070          if  (mb_strlen($value)) {
1071
1072            $res.= '/'.$label.' '.$value."\n";
1073          }
1074
1075          break;
1076
1077        case  'FontFile':
1078
1079        case  'FontFile2':
1080
1081        case  'FontFile3':
1082
1083          $res.= '/'.$label.' '.$value." 0 R\n";
1084
1085          break;
1086
1087        case  'FontBBox':
1088
1089          $res.= '/'.$label.' ['.$value[0].' '.$value[1].' '.$value[2].' '.$value[3]."]\n";
1090
1091          break;
1092
1093        case  'FontName':
1094
1095          $res.= '/'.$label.' /'.$value."\n";
1096
1097          break;
1098        }
1099      }
1100
1101      $res.= ">>\nendobj";
1102
1103      return  $res;
1104
1105      break;
1106    }
1107  }
1108
1109
1110  /**
1111   * the font encoding
1112   */
1113  function  o_fontEncoding($id, $action, $options = '') {
1114
1115    if  ($action != 'new') {
1116
1117      $o = & $this->objects[$id];
1118    }
1119
1120    switch  ($action) {
1121
1122    case  'new':
1123
1124      // the options array should contain 'differences' and maybe 'encoding'
1125      $this->objects[$id] = array('t'=>'fontEncoding', 'info'=>$options);
1126
1127      break;
1128
1129    case  'out':
1130
1131      $res = "\n".$id." 0 obj\n<< /Type /Encoding\n";
1132
1133      if  (!isset($o['info']['encoding'])) {
1134
1135        $o['info']['encoding'] = 'WinAnsiEncoding';
1136      }
1137
1138      if  ($o['info']['encoding'] != 'none') {
1139
1140        $res.= "/BaseEncoding /".$o['info']['encoding']."\n";
1141      }
1142
1143      $res.= "/Differences \n[";
1144
1145      $onum = -100;
1146
1147      foreach($o['info']['differences'] as  $num=>$label) {
1148
1149        if  ($num != $onum+1) {
1150
1151          // we cannot make use of consecutive numbering
1152          $res.=  "\n".$num." /".$label;
1153        } else {
1154
1155          $res.=  " /".$label;
1156        }
1157
1158        $onum = $num;
1159      }
1160
1161      $res.= "\n]\n>>\nendobj";
1162
1163      return  $res;
1164
1165      break;
1166    }
1167  }
1168
1169
1170  /**
1171   * a descendent cid font,  needed for unicode fonts
1172   */
1173  function  o_fontDescendentCID($id, $action, $options = '') {
1174
1175    if  ($action != 'new') {
1176
1177      $o = & $this->objects[$id];
1178    }
1179
1180    switch  ($action) {
1181
1182    case  'new':
1183
1184      $this->objects[$id] =  array('t'=>'fontDescendentCID', 'info'=>$options);
1185
1186      // we need a CID system info section
1187      $cidSystemInfoId = ++$this->numObj;
1188      $this->o_contents($cidSystemInfoId, 'new', 'raw');
1189      $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1190      $res=   "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections
1191      $res.=  "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry
1192      $res.=  "/Supplement 0\n"; // The supplement number of the character collection.
1193      $res.=  ">>";
1194      $this->objects[$cidSystemInfoId]['c'] = $res;
1195
1196      // and a CID to GID map
1197      $cidToGidMapId = ++$this->numObj;
1198      $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1199      $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1200      
1201      break;
1202
1203    case  'add':
1204
1205      foreach ($options as  $k => $v) {
1206        switch  ($k) {
1207        case  'BaseFont':
1208          $o['info']['name'] =  $v;
1209          break;
1210
1211        case  'FirstChar':
1212        case  'LastChar':
1213        case  'MissingWidth':
1214        case  'FontDescriptor':
1215        case  'SubType':
1216          $this->addMessage('o_fontDescendentCID '.$k." : ".$v);
1217          $o['info'][$k] =  $v;
1218          break;
1219        }
1220      }
1221
1222      // pass values down to cid to gid map
1223      $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1224      
1225      break;
1226
1227    case  'out':
1228
1229      $res =  "\n".$id." 0 obj\n";
1230      $res.=  "<</Type /Font\n";
1231      $res.=  "/Subtype /CIDFontType2\n";
1232      $res.=  "/BaseFont /".$o['info']['name']."\n";
1233      $res.=  "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n";
1234//      if  (isset($o['info']['FirstChar'])) {
1235//
1236//        $res.=  "/FirstChar ".$o['info']['FirstChar']."\n";
1237//      }
1238
1239//      if  (isset($o['info']['LastChar'])) {
1240//
1241//        $res.=  "/LastChar ".$o['info']['LastChar']."\n";
1242//      }
1243      if  (isset($o['info']['FontDescriptor'])) {
1244
1245        $res.=  "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1246      }
1247
1248      if  (isset($o['info']['MissingWidth'])) {
1249        $res.=  "/DW ".$o['info']['MissingWidth']."\n";
1250      }
1251
1252      if  (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1253        $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1254        $w = '';
1255        foreach ($cid_widths as $cid => $width) {
1256          $w .= $cid.' ['.$width.'] ';
1257        }
1258        $res.=  "/W [".$w."]\n";
1259      }
1260
1261      $res.=  "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n";
1262      $res.=  ">>\n";
1263      $res.=  "endobj";
1264
1265      return  $res;
1266
1267      break;
1268    }
1269  }
1270  
1271
1272  /**
1273   * a font glyph to character map,  needed for unicode fonts
1274   */
1275  function  o_fontGIDtoCIDMap($id, $action, $options = '') {
1276
1277    if  ($action != 'new') {
1278
1279      $o = & $this->objects[$id];
1280    }
1281
1282    switch  ($action) {
1283
1284    case  'new':
1285
1286      $this->objects[$id] =  array('t'=>'fontGIDtoCIDMap', 'info'=>$options);
1287
1288      break;
1289
1290    case  'out':
1291       
1292      $res = "\n".$id." 0 obj\n";
1293      $tmp = $this->fonts[$o['info']['fontFileName']]['CIDtoGID'] = base64_decode($this->fonts[$o['info']['fontFileName']]['CIDtoGID']);
1294      $compressed = isset($this->fonts[$o['info']['fontFileName']]['CIDtoGID_Compressed']) &&
1295                    $this->fonts[$o['info']['fontFileName']]['CIDtoGID_Compressed'];
1296
1297      if  (!$compressed && isset($o['raw'])) {
1298
1299        $res.= $tmp;
1300      } else {
1301
1302        $res.=  "<<";
1303
1304        if  (!$compressed && function_exists('gzcompress') &&  $this->options['compression']) {
1305
1306          // then implement ZLIB based compression on this content stream
1307          $compressed = true;
1308
1309          $tmp =  gzcompress($tmp,  6);
1310        }
1311        if ($compressed) {
1312
1313          $res.= "\n/Filter /FlateDecode";
1314        }
1315
1316        $res.= "\n/Length ".mb_strlen($tmp) .">>\nstream\n".$tmp."\nendstream";
1317      }
1318
1319      $res.= "\nendobj";
1320
1321      return  $res;
1322
1323      break;
1324    }
1325  }
1326  
1327
1328  /**
1329   * the document procset, solves some problems with printing to old PS printers
1330   */
1331  function  o_procset($id, $action, $options = '') {
1332
1333    if  ($action != 'new') {
1334
1335      $o = & $this->objects[$id];
1336    }
1337
1338    switch  ($action) {
1339
1340    case  'new':
1341
1342      $this->objects[$id] = array('t'=>'procset', 'info'=>array('PDF'=>1, 'Text'=>1));
1343
1344      $this->o_pages($this->currentNode, 'procset', $id);
1345
1346      $this->procsetObjectId = $id;
1347
1348      break;
1349
1350    case  'add':
1351
1352      // this is to add new items to the procset list, despite the fact that this is considered
1353      // obselete, the items are required for printing to some postscript printers
1354      switch  ($options) {
1355
1356      case  'ImageB':
1357
1358      case  'ImageC':
1359
1360      case  'ImageI':
1361
1362        $o['info'][$options] = 1;
1363
1364        break;
1365      }
1366
1367      break;
1368
1369    case  'out':
1370
1371      $res = "\n".$id." 0 obj\n[";
1372
1373      foreach ($o['info'] as  $label=>$val) {
1374
1375        $res.= '/'.$label.' ';
1376      }
1377
1378      $res.= "]\nendobj";
1379
1380      return  $res;
1381
1382      break;
1383    }
1384  }
1385
1386
1387  /**
1388   * define the document information
1389   */
1390  function  o_info($id, $action, $options = '') {
1391
1392    if  ($action != 'new') {
1393
1394      $o = & $this->objects[$id];
1395    }
1396
1397    switch  ($action) {
1398
1399    case  'new':
1400
1401      $this->infoObject = $id;
1402
1403      $date = 'D:'.@date('Ymd');
1404
1405      $this->objects[$id] = array('t'=>'info', 'info'=>array('Creator'=>'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate'=>$date));
1406
1407      break;
1408
1409    case  'Title':
1410
1411    case  'Author':
1412
1413    case  'Subject':
1414
1415    case  'Keywords':
1416
1417    case  'Creator':
1418
1419    case  'Producer':
1420
1421    case  'CreationDate':
1422
1423    case  'ModDate':
1424
1425    case  'Trapped':
1426
1427      $o['info'][$action] = $options;
1428
1429      break;
1430
1431    case  'out':
1432
1433      if  ($this->encrypted) {
1434
1435        $this->encryptInit($id);
1436      }
1437
1438      $res = "\n".$id." 0 obj\n<<\n";
1439
1440      foreach ($o['info'] as  $k=>$v) {
1441
1442        $res.= '/'.$k.' (';
1443
1444        // dates must be outputted as-is, without Unicode transformations
1445        $raw = ($k == 'CreationDate' || $k == 'ModDate');
1446        $c = $v;
1447
1448        if  ($this->encrypted) {
1449
1450          $c = $this->ARC4($c);
1451        }
1452
1453        $res.= ($raw) ? $c : $this->filterText($c);
1454
1455        $res.= ")\n";
1456      }
1457
1458      $res.= ">>\nendobj";
1459
1460      return  $res;
1461
1462      break;
1463    }
1464  }
1465
1466
1467  /**
1468   * an action object, used to link to URLS initially
1469   */
1470  function  o_action($id, $action, $options = '') {
1471
1472    if  ($action != 'new') {
1473
1474      $o = & $this->objects[$id];
1475    }
1476
1477    switch  ($action) {
1478
1479    case  'new':
1480
1481      if  (is_array($options)) {
1482
1483        $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>$options['type']);
1484      } else {
1485
1486        // then assume a URI action
1487        $this->objects[$id] = array('t'=>'action', 'info'=>$options, 'type'=>'URI');
1488      }
1489
1490      break;
1491
1492    case  'out':
1493
1494      if  ($this->encrypted) {
1495
1496        $this->encryptInit($id);
1497      }
1498
1499      $res = "\n".$id." 0 obj\n<< /Type /Action";
1500
1501      switch ($o['type']) {
1502
1503      case  'ilink':
1504
1505        // there will be an 'label' setting, this is the name of the destination
1506        $res.= "\n/S /GoTo\n/D ".$this->destinations[(string)$o['info']['label']]." 0 R";
1507
1508        break;
1509
1510      case  'URI':
1511
1512        $res.= "\n/S /URI\n/URI (";
1513
1514        if  ($this->encrypted) {
1515
1516          $res.= $this->filterText($this->ARC4($o['info']));
1517        } else {
1518
1519          $res.= $this->filterText($o['info']);
1520        }
1521
1522        $res.= ")";
1523
1524        break;
1525      }
1526
1527      $res.= "\n>>\nendobj";
1528
1529      return  $res;
1530
1531      break;
1532    }
1533  }
1534
1535
1536  /**
1537   * an annotation object, this will add an annotation to the current page.
1538   * initially will support just link annotations
1539   */
1540  function  o_annotation($id, $action, $options = '') {
1541
1542    if  ($action != 'new') {
1543
1544      $o = & $this->objects[$id];
1545    }
1546
1547    switch  ($action) {
1548
1549    case  'new':
1550
1551      // add the annotation to the current page
1552      $pageId =  $this->currentPage;
1553
1554      $this->o_page($pageId, 'annot', $id);
1555
1556      // and add the action object which is going to be required
1557      switch ($options['type']) {
1558
1559      case  'link':
1560
1561        $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
1562
1563        $this->numObj++;
1564
1565        $this->o_action($this->numObj, 'new', $options['url']);
1566
1567        $this->objects[$id]['info']['actionId'] = $this->numObj;
1568
1569        break;
1570
1571      case  'ilink':
1572
1573        // this is to a named internal link
1574        $label =  $options['label'];
1575
1576        $this->objects[$id] = array('t'=>'annotation', 'info'=>$options);
1577
1578        $this->numObj++;
1579
1580        $this->o_action($this->numObj, 'new', array('type'=>'ilink', 'label'=>$label));
1581
1582        $this->objects[$id]['info']['actionId'] = $this->numObj;
1583
1584        break;
1585      }
1586
1587      break;
1588
1589    case  'out':
1590
1591      $res = "\n".$id." 0 obj\n<< /Type /Annot";
1592
1593      switch ($o['info']['type']) {
1594
1595      case  'link':
1596
1597      case  'ilink':
1598
1599        $res.=  "\n/Subtype /Link";
1600
1601        break;
1602      }
1603
1604      $res.= "\n/A ".$o['info']['actionId']." 0 R";
1605
1606      $res.= "\n/Border [0 0 0]";
1607
1608      $res.= "\n/H /I";
1609
1610      $res.= "\n/Rect [ ";
1611
1612      foreach($o['info']['rect'] as  $v) {
1613
1614        $res.=  sprintf("%.4F ", $v);
1615      }
1616
1617      $res.= "]";
1618
1619      $res.= "\n>>\nendobj";
1620
1621      return  $res;
1622
1623      break;
1624    }
1625  }
1626
1627
1628  /**
1629   * a page object, it also creates a contents object to hold its contents
1630   */
1631  function  o_page($id, $action, $options = '') {
1632
1633    if  ($action != 'new') {
1634
1635      $o = & $this->objects[$id];
1636    }
1637
1638    switch  ($action) {
1639
1640    case  'new':
1641
1642      $this->numPages++;
1643
1644      $this->objects[$id] = array('t'=>'page', 'info'=>array('parent'=>$this->currentNode, 'pageNum'=>$this->numPages));
1645
1646      if  (is_array($options)) {
1647
1648        // then this must be a page insertion, array shoudl contain 'rid','pos'=[before|after]
1649        $options['id'] = $id;
1650
1651        $this->o_pages($this->currentNode, 'page', $options);
1652      } else {
1653
1654        $this->o_pages($this->currentNode, 'page', $id);
1655      }
1656
1657      $this->currentPage = $id;
1658
1659      //make a contents object to go with this page
1660      $this->numObj++;
1661
1662      $this->o_contents($this->numObj, 'new', $id);
1663
1664      $this->currentContents = $this->numObj;
1665
1666      $this->objects[$id]['info']['contents'] = array();
1667
1668      $this->objects[$id]['info']['contents'][] = $this->numObj;
1669
1670      $match =  ($this->numPages%2 ?  'odd' :  'even');
1671
1672      foreach($this->addLooseObjects as  $oId=>$target) {
1673
1674        if  ($target == 'all' ||  $match == $target) {
1675
1676          $this->objects[$id]['info']['contents'][] = $oId;
1677        }
1678      }
1679
1680      break;
1681
1682    case  'content':
1683
1684      $o['info']['contents'][] = $options;
1685
1686      break;
1687
1688    case  'annot':
1689
1690      // add an annotation to this page
1691      if  (!isset($o['info']['annot'])) {
1692
1693        $o['info']['annot'] = array();
1694      }
1695
1696      // $options should contain the id of the annotation dictionary
1697      $o['info']['annot'][] = $options;
1698
1699      break;
1700
1701    case  'out':
1702
1703      $res = "\n".$id." 0 obj\n<< /Type /Page";
1704
1705      $res.= "\n/Parent ".$o['info']['parent']." 0 R";
1706
1707      if  (isset($o['info']['annot'])) {
1708
1709        $res.= "\n/Annots [";
1710
1711        foreach($o['info']['annot'] as  $aId) {
1712
1713          $res.= " ".$aId." 0 R";
1714        }
1715
1716        $res.= " ]";
1717      }
1718
1719      $count =  count($o['info']['contents']);
1720
1721      if  ($count == 1) {
1722
1723        $res.= "\n/Contents ".$o['info']['contents'][0]." 0 R";
1724      } else  if  ($count>1) {
1725
1726        $res.= "\n/Contents [\n";
1727
1728        // reverse the page contents so added objects are below normal content
1729        //foreach (array_reverse($o['info']['contents']) as  $cId) {
1730
1731        // Back to normal now that I've got transparency working --Benj
1732        foreach ($o['info']['contents'] as  $cId) {
1733          $res.= $cId." 0 R\n";
1734        }
1735
1736        $res.= "]";
1737      }
1738
1739      $res.= "\n>>\nendobj";
1740
1741      return  $res;
1742
1743      break;
1744    }
1745  }
1746
1747
1748  /**
1749   * the contents objects hold all of the content which appears on pages
1750   */
1751  function  o_contents($id, $action, $options = '') {
1752
1753    if  ($action != 'new') {
1754
1755      $o = & $this->objects[$id];
1756    }
1757
1758    switch  ($action) {
1759
1760    case  'new':
1761
1762      $this->objects[$id] = array('t'=>'contents', 'c'=>'', 'info'=>array());
1763
1764      if  (mb_strlen($options) &&  intval($options)) {
1765
1766        // then this contents is the primary for a page
1767        $this->objects[$id]['onPage'] = $options;
1768      } else  if  ($options == 'raw') {
1769
1770        // then this page contains some other type of system object
1771        $this->objects[$id]['raw'] = 1;
1772      }
1773
1774      break;
1775
1776    case  'add':
1777
1778      // add more options to the decleration
1779      foreach ($options as  $k=>$v) {
1780
1781        $o['info'][$k] = $v;
1782      }
1783
1784    case  'out':
1785      $tmp = $o['c'];
1786
1787      $res =  "\n".$id." 0 obj\n";
1788
1789      if  (isset($this->objects[$id]['raw'])) {
1790
1791        $res.= $tmp;
1792      } else {
1793
1794        $res.=  "<<";
1795
1796        if  (function_exists('gzcompress') &&  $this->options['compression']) {
1797
1798          // then implement ZLIB based compression on this content stream
1799          $res.= " /Filter /FlateDecode";
1800
1801          $tmp =  gzcompress($tmp,  6);
1802        }
1803
1804        if  ($this->encrypted) {
1805
1806          $this->encryptInit($id);
1807
1808          $tmp =  $this->ARC4($tmp);
1809        }
1810
1811        foreach($o['info'] as  $k=>$v) {
1812
1813          $res.=  "\n/".$k.' '.$v;
1814        }
1815
1816        $res.= "\n/Length ".mb_strlen($tmp) ." >>\nstream\n".$tmp."\nendstream";
1817      }
1818
1819      $res.= "\nendobj";
1820
1821      return  $res;
1822
1823      break;
1824    }
1825  }
1826
1827
1828  /**
1829   * an image object, will be an XObject in the document, includes description and data
1830   */
1831  function  o_image($id, $action, $options = '') {
1832
1833    if  ($action != 'new') {
1834      $o = & $this->objects[$id];
1835    }
1836
1837    switch ($action) {
1838
1839    case  'new':
1840
1841      // make the new object
1842      $this->objects[$id] = array('t'=>'image', 'data'=>&$options['data'], 'info'=>array());
1843
1844      $this->objects[$id]['info']['Type'] = '/XObject';
1845
1846      $this->objects[$id]['info']['Subtype'] = '/Image';
1847
1848      $this->objects[$id]['info']['Width'] = $options['iw'];
1849
1850      $this->objects[$id]['info']['Height'] = $options['ih'];
1851
1852      if  (!isset($options['type']) ||  $options['type'] == 'jpg') {
1853
1854        if  (!isset($options['channels'])) {
1855
1856          $options['channels'] = 3;
1857        }
1858
1859        switch ($options['channels']) {
1860
1861        case  1:
1862
1863          $this->objects[$id]['info']['ColorSpace'] = '/DeviceGray';
1864
1865          break;
1866
1867        default:
1868
1869          $this->objects[$id]['info']['ColorSpace'] = '/DeviceRGB';
1870
1871          break;
1872        }
1873
1874        $this->objects[$id]['info']['Filter'] = '/DCTDecode';
1875
1876        $this->objects[$id]['info']['BitsPerComponent'] = 8;
1877      } else  if  ($options['type'] == 'png') {
1878        
1879        $this->objects[$id]['info']['Filter'] = '/FlateDecode';
1880
1881        $this->objects[$id]['info']['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
1882        if  (mb_strlen($options['pdata'])) {
1883
1884          $tmp =  ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata']) /3-1) .' ';
1885
1886          $this->numObj++;
1887
1888          $this->o_contents($this->numObj, 'new');
1889
1890          $this->objects[$this->numObj]['c'] = $options['pdata'];
1891
1892          $tmp.= $this->numObj.' 0 R';
1893
1894          $tmp.= ' ]';
1895
1896          $this->objects[$id]['info']['ColorSpace'] =  $tmp;
1897
1898          if  (isset($options['transparency'])) {
1899
1900            switch ($options['transparency']['type']) {
1901
1902            case  'indexed':
1903
1904              $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
1905
1906              $this->objects[$id]['info']['Mask'] =  $tmp;
1907
1908              break;
1909
1910            case 'color-key':
1911              $tmp = ' [ '.
1912                $options['transparency']['r'] . ' ' . $options['transparency']['r'] .
1913                $options['transparency']['g'] . ' ' . $options['transparency']['g'] .
1914                $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
1915                ' ] ';
1916              $this->objects[$id]['info']['Mask'] = $tmp;
1917              pre_r($tmp);
1918              break;
1919              
1920            }
1921          }
1922        } else {
1923
1924          if  (isset($options['transparency'])) {
1925
1926            switch ($options['transparency']['type']) {
1927
1928            case  'indexed':
1929
1930              $tmp = ' [ '.$options['transparency']['data'].' '.$options['transparency']['data'].'] ';
1931
1932              $this->objects[$id]['info']['Mask'] =  $tmp;
1933
1934              break;
1935
1936            case 'color-key':
1937              $tmp = ' [ '.
1938                $options['transparency']['r'] . ' ' . $options['transparency']['r'] . ' ' .
1939                $options['transparency']['g'] . ' ' . $options['transparency']['g'] . ' ' .
1940                $options['transparency']['b'] . ' ' . $options['transparency']['b'] .
1941                ' ] ';
1942              $this->objects[$id]['info']['Mask'] = $tmp;
1943              break;
1944              
1945            }
1946          }
1947          $this->objects[$id]['info']['ColorSpace'] = '/'.$options['color'];
1948        }
1949
1950        $this->objects[$id]['info']['BitsPerComponent'] = $options['bitsPerComponent'];
1951      }
1952
1953      // assign it a place in the named resource dictionary as an external object, according to
1954      // the label passed in with it.
1955      $this->o_pages($this->currentNode, 'xObject', array('label'=>$options['label'], 'objNum'=>$id));
1956
1957      // also make sure that we have the right procset object for it.
1958      $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
1959
1960      break;
1961
1962    case  'out':
1963
1964      $tmp = &$o['data'];
1965
1966      $res =  "\n".$id." 0 obj\n<<";
1967
1968      foreach($o['info'] as  $k=>$v) {
1969
1970        $res.= "\n/".$k.' '.$v;
1971      }
1972
1973      if  ($this->encrypted) {
1974
1975        $this->encryptInit($id);
1976
1977        $tmp =  $this->ARC4($tmp);
1978      }
1979
1980      $res.= "\n/Length ".mb_strlen($tmp, '8bit') .">>\nstream\n".$tmp."\nendstream\nendobj";
1981
1982      return  $res;
1983
1984      break;
1985    }
1986  }
1987
1988
1989  /**
1990   * graphics state object
1991   */
1992  function  o_extGState($id,  $action,  $options = "") {
1993
1994    static  $valid_params =  array("LW",  "LC",  "LC",  "LJ",  "ML",
1995                                   "D",  "RI",  "OP",  "op",  "OPM",
1996                                   "Font",  "BG",  "BG2",  "UCR",
1997                                   "TR",  "TR2",  "HT",  "FL",
1998                                   "SM",  "SA",  "BM",  "SMask",
1999                                   "CA",  "ca",  "AIS",  "TK");
2000
2001    if  ( $action !=  "new") {
2002      $o = & $this->objects[$id];
2003    }
2004
2005    switch  ($action) {
2006
2007    case  "new":
2008      $this->objects[$id] =  array('t' => 'extGState',  'info' => $options);
2009
2010      // Tell the pages about the new resource
2011      $this->numStates++;
2012      $this->o_pages($this->currentNode,  'extGState',  array("objNum" => $id,  "stateNum" => $this->numStates));
2013      break;
2014
2015
2016    case  "out":
2017      $res =
2018        "\n" . $id . " 0 obj\n".
2019        "<< /Type /ExtGState\n";
2020
2021      foreach ($o["info"] as  $parameter => $value) {
2022        if  ( !in_array($parameter,  $valid_params))
2023          continue;
2024        $res.=  "/$parameter $value\n";
2025      }
2026
2027      $res.=
2028        ">>\n".
2029        "endobj";
2030
2031      return  $res;
2032    }
2033  }
2034
2035
2036  /**
2037   * encryption object.
2038   */
2039  function  o_encryption($id, $action, $options = '') {
2040
2041    if  ($action != 'new') {
2042
2043      $o = & $this->objects[$id];
2044    }
2045
2046    switch ($action) {
2047
2048    case  'new':
2049
2050      // make the new object
2051      $this->objects[$id] = array('t'=>'encryption', 'info'=>$options);
2052
2053      $this->arc4_objnum = $id;
2054
2055      // figure out the additional paramaters required
2056      $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);
2057
2058      $len =  mb_strlen($options['owner']);
2059
2060      if  ($len>32) {
2061
2062        $owner =  substr($options['owner'], 0, 32);
2063      } else  if  ($len<32) {
2064
2065        $owner =  $options['owner'].substr($pad, 0, 32-$len);
2066      } else {
2067
2068        $owner =  $options['owner'];
2069      }
2070
2071      $len =  mb_strlen($options['user']);
2072
2073      if  ($len>32) {
2074
2075        $user =  substr($options['user'], 0, 32);
2076      } else  if  ($len<32) {
2077
2078        $user =  $options['user'].substr($pad, 0, 32-$len);
2079      } else {
2080
2081        $user =  $options['user'];
2082      }
2083
2084      $tmp =  $this->md5_16($owner);
2085
2086      $okeā€¦

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