source: sandbox/filemanager/tp/dompdf/lib/class.pdf.php @ 1575

Revision 1575, 115.0 KB checked in by amuller, 14 years ago (diff)

Ticket #597 - Implentação, melhorias do modulo gerenciador de arquivos

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