1<?php
2/*******************************************************************************
3 * Software: FPDF                                                               *
4 * Version:  1.53                                                               *
5 * Date:     2004-12-31                                                         *
6 * Author:   Olivier PLATHEY                                                    *
7 * License:  Freeware                                                           *
8 *                                                                              *
9 * You may use, modify and redistribute this software as you wish.              *
10 *******************************************************************************/
11
12/**
13 * Heavily patched to adapt to the HTML2PS/HTML2PDF script requirements by
14 * Konstantin Bournayev (bkon@bkon.ru)
15 *
16 * Note: this FPDF variant assumes that magic_quotes_runtime are disabled;
17 * the reason is that HTML2PS/PDF explicitly disables them during pipeline
18 * processing, thus all calls to FPDF API are "safe"
19 */
20
21if (!class_exists('FPDF')) {
22  define('FPDF_VERSION','1.53');
23
24  /**
25   * FPDF state flags
26   */
27  define('FPDF_STATE_UNINITIALIZED',    0);
28  define('FPDF_STATE_DOCUMENT_STARTED', 1);
29  define('FPDF_STATE_PAGE_STARTED',     2);
30  define('FPDF_STATE_COMPLETED',        3);
31
32  /**
33   * See PDF Reference 1.6 p.664 for explanation of flags specific to submit form action
34   */
35  define('PDF_SUBMIT_FORM_HTML',        1 << 2); // 1 - HTML, 0 - FDF
36  define('PDF_SUBMIT_FORM_COORDINATES', 1 << 4);
37
38  /**
39   * See PDF Reference 1.6 p.656 for explanation of flags specific to choice fields
40   */
41  define('PDF_FIELD_CHOICE_COMBO', 1 << 17);
42
43  /**
44   * See PDF Reference 1.6 p.573 for explanation of flag specific to annotations
45   */
46  define('PDF_ANNOTATION_INVISIBLE',    1 << 0);
47  define('PDF_ANNOTATION_HIDDEN',       1 << 1);
48  define('PDF_ANNOTATION_PRINTABLE',    1 << 2);
49  define('PDF_ANNOTATION_NOZOOM',       1 << 3);
50  define('PDF_ANNOTATION_NOROTATE',     1 << 4);
51  define('PDF_ANNOTATION_NOVIEW',       1 << 5);
52  define('PDF_ANNOTATION_READONLY',     1 << 6);
53  define('PDF_ANNOTATION_LOCKED',       1 << 7);
54  define('PDF_ANNOTATION_TOGGLENOVIEW', 1 << 8);
55
56  /**
57   * See PDF Reference 1.6 p.653 for explanation of flags specific to text fields
58   */
59  define('PDF_FIELD_TEXT_MULTILINE',1 << 12);
60  define('PDF_FIELD_TEXT_PASSWORD', 1 << 13);
61  define('PDF_FIELD_TEXT_FILE',     1 << 20);
62
63  /**
64   * See PDF Reference 1.6 p.663 for examplanation of flags specific to for submit actions
65   */
66  define("PDF_FORM_SUBMIT_EXCLUDE", 1 << 0);
67  define("PDF_FORM_SUBMIT_NOVALUE", 1 << 1);
68  define("PDF_FORM_SUBMIT_EFORMAT", 1 << 2);
69  define("PDF_FORM_SUBMIT_GET",     1 << 3);
70
71  class PDFIndirectObject {
72    var $object_id;
73    var $generation_id;
74
75    function get_object_id() {
76      return $this->object_id;
77    }
78
79    function get_generation_id() {
80      return $this->generation_id;
81    }
82
83    /**
84     * Outputs the PDF indirect object to PDF file.
85     *
86     * To pervent infinite loop on circular references, this method checks
87     * if current object have been already written to the file.
88     *
89     * Note that, in general, nested objects should be written to PDF file
90     * here too; this task is accomplished by calling _out_nested method,
91     * which should be overridden by children classes.
92     *
93     * @param FPDF $handler PDF file wrapper (FPDF object)
94     *
95     * @final
96     *
97     * @see FPDF::is_object_written
98     * @see PDFIndirectObject::_out_nested
99     */
100    function out(&$handler) {
101      if (!$handler->is_object_written($this->get_object_id())) {
102        $handler->offsets[$this->get_object_id()] = strlen($handler->buffer);
103        $handler->_out($handler->_indirect_object($this));
104
105        $this->_out_nested($handler);
106      };
107    }
108
109    /**
110     * Writes all nested objects to the PDF file. Should be overridden by
111     * PDFIndirectObject descendants.
112     *
113     * @param FPDF $handler PDF file wrapper (FPDF object)
114     *
115     * @see PDFIndirectObject::out
116     */
117    function _out_nested(&$handler) {
118      return true;
119    }
120
121    function PDFIndirectObject(&$handler,
122                               $object_id,
123                               $generation_id) {
124      $this->object_id = $object_id;
125      $this->generation_id = $generation_id;
126    }
127
128    function pdf(&$handler) {
129      return $handler->_dictionary($this->_dict($handler));
130    }
131
132    function _dict() {
133      return array();
134    }
135  }
136
137  class PDFCMap extends PDFIndirectObject {
138    var $_content;
139
140    function PDFCMap($mapping, &$handler, $object_id, $generation_id) {
141      $this->PDFIndirectObject($handler,
142                               $object_id,
143                               $generation_id);
144
145      $num_chars = count($mapping);
146
147      $chars     = "";
148      foreach ($mapping as $code => $utf) {
149        $chars .= sprintf("<%02X> <%04X> \n", $code, $utf);
150      };
151
152      $this->_content = <<<EOF
153/CIDInit /ProcSet findresource begin
15412 dict begin
155begincmap
156CIDSystemInfo
157<< /Registry (Adobe)
158/Ordering (UCS) /Supplement 0 >> def
159/CMapName /Adobe-Identity-UCS def
160/CMapType 2 def
1611 begincodespacerange
162<0000> <FFFF>
163endcodespacerange
164${num_chars} beginbfchar
165${chars}
166endbfchar
167endcmap CMapName currentdict /CMap defineresource pop end end
168EOF
169;
170    }
171
172    function pdf(&$handler) {
173      $dict_content   = array(
174                              'Length'   => strlen($this->_content)
175                              );
176
177      $content = $handler->_dictionary($dict_content);
178      $content .= "\n";
179      $content .= $handler->_stream($this->_content);
180
181      return $content;
182    }
183  }
184
185  class PDFPage extends PDFIndirectObject {
186    var $annotations;
187    var $_width;
188    var $_height;
189
190    function PDFPage(&$handler,
191                     $width,
192                     $height,
193                     $object_id,
194                     $generation_id) {
195      $this->PDFIndirectObject($handler,
196                               $object_id,
197                               $generation_id);
198
199      $this->set_width($width);
200      $this->set_height($height);
201    }
202
203    function add_annotation(&$annotation) {
204      $this->annotations[] =& $annotation;
205    }
206
207    function _annotations(&$handler) {
208      return $handler->_reference_array($this->annotations);
209    }
210
211    function get_height() {
212      return $this->_height;
213    }
214
215    function get_width() {
216      return $this->_width;
217    }
218
219    function set_height($height) {
220      $this->_height = $height;
221    }
222
223    function set_width($width) {
224      $this->_width = $width;
225    }
226  }
227
228  class PDFAppearanceStream extends PDFIndirectObject {
229    var $_content;
230
231    function PDFAppearanceStream(&$handler,
232                                 $object_id,
233                                 $generation_id,
234                                 $content) {
235      $this->PDFIndirectObject($handler,
236                               $object_id,
237                               $generation_id);
238
239      $this->_content = $content;
240    }
241
242    function pdf(&$handler) {
243      $dict_content   = array(
244                              'Type'     => "/XObject",
245                              'Subtype'  => "/Form",
246                              'FormType' => "1",
247                              'BBox'     => "[0 0 100 100]",
248                              'Matrix'   => "[1 0 0 1 0 0]",
249                              'Resources'=> "2 0 R",
250                              'Length'   => strlen($this->_content)
251                              );
252
253      $content = $handler->_dictionary($dict_content);
254      $content .= "\n";
255      $content .= $handler->_stream($this->_content);
256
257      return $content;
258    }
259  }
260
261  class PDFAnnotation extends PDFIndirectObject {
262    function PDFAnnotation(&$handler,
263                           $object_id,
264                           $generation_id) {
265      $this->PDFIndirectObject($handler,
266                               $object_id,
267                               $generation_id);
268    }
269
270    function _dict(&$handler) {
271      return array_merge(parent::_dict($handler),
272                         array("Type" => $handler->_name("Annot")));
273    }
274  }
275
276  class PDFRect {
277    var $x;
278    var $y;
279    var $w;
280    var $h;
281
282    function PDFRect($x,$y,$w,$h) {
283      $this->x = $x;
284      $this->y = $y;
285      $this->w = $w;
286      $this->h = $h;
287    }
288
289    function left(&$handler) {
290      return $handler->x_coord($this->x);
291    }
292
293    function right(&$handler) {
294      return $handler->x_coord($this->x+$this->w);
295    }
296
297    function top(&$handler) {
298      return $handler->y_coord($this->y);
299    }
300
301    function bottom(&$handler) {
302      return $handler->y_coord($this->y+$this->h);
303    }
304
305    function pdf(&$handler) {
306      return $handler->_array(sprintf("%.2f %.2f %.2f %.2f",
307                                      $this->left($handler),
308                                      $this->top($handler),
309                                      $this->right($handler),
310                                      $this->bottom($handler)));
311    }
312  }
313
314  class PDFAnnotationExternalLink extends PDFAnnotation {
315    var $rect;
316    var $link;
317
318    function PDFAnnotationExternalLink(&$handler,
319                                       $object_id,
320                                       $generation_id,
321                                       $rect,
322                                       $link) {
323      $this->PDFAnnotation($handler,
324                           $object_id,
325                           $generation_id);
326
327      $this->rect = $rect;
328      $this->link = $link;
329    }
330
331    function _dict(&$handler) {
332      return array_merge(parent::_dict($handler),
333                         array(
334                               'Subtype' => "/Link",
335                               'Rect'    => $this->rect->pdf($handler),
336                               'Border'  => "[0 0 0]",
337                               'A'       => "<</S /URI /URI ".$handler->_textstring($this->link).">>"
338                               ));
339    }
340  }
341
342  class PDFAnnotationInternalLink extends PDFAnnotation {
343    var $rect;
344    var $link;
345
346    function PDFAnnotationInternalLink(&$handler,
347                                       $object_id,
348                                       $generation_id,
349                                       $rect,
350                                       $link) {
351      $this->PDFAnnotation($handler,
352                           $object_id,
353                           $generation_id);
354
355      $this->rect = $rect;
356      $this->link = $link;
357    }
358
359    function pdf(&$handler) {
360      if ($handler->DefOrientation=='P') {
361        $wPt=$handler->fwPt;
362        $hPt=$handler->fhPt;
363      } else {
364        $wPt=$handler->fhPt;
365        $hPt=$handler->fwPt;
366      };
367      $l = $handler->links[$this->link];
368      $h = $hPt;
369
370      /**
371       * Sometimes hyperlinks may refer to pages NOT present in PDF document
372       * Example: a very long frame content; it it trimmed to one page, as
373       * framesets newer take more than one frame. A link targe which should be rendered
374       * on third page without frames will be never rendered at all.
375       *
376       * In this case we should disable link at all to prevent error from appearing
377       */
378
379      if (!isset($handler->_pages[$l[0]-1])) {
380        return "";
381      }
382
383      $content = $handler->_dictionary(array(
384                                             'Type'    => "/Annot",
385                                             'Subtype' => "/Link",
386                                             'Rect'    => $this->rect->pdf($handler),
387                                             'Border'  => "[0 0 0]",
388                                             'Dest'    => sprintf("[%s /XYZ 0 %.2f null]",
389                                                                  $handler->_reference($handler->_pages[$l[0]-1]),
390                                                                  $h-$l[1]*$handler->k)
391                                             ));
392      return $content;
393    }
394  }
395
396  class PDFAnnotationWidget extends PDFAnnotation {
397    var $_rect;
398
399    function PDFAnnotationWidget(&$handler,
400                                 $object_id,
401                                 $generation_id,
402                                 $rect) {
403      $this->PDFAnnotation($handler,
404                           $object_id,
405                           $generation_id);
406
407      $this->_rect = $rect;
408    }
409
410    function _dict(&$handler) {
411      return array_merge(parent::_dict($handler),
412                         array("Subtype" => $handler->_name("Widget"),
413                               'Rect'    => $this->_rect->pdf($handler)));
414    }
415  }
416
417  /**
418   * Generic PDF Form
419   */
420  class PDFFieldGroup extends PDFIndirectObject {
421    var $_kids;
422    var $_group_name;
423
424    function PDFFieldGroup(&$handler,
425                           $object_id,
426                           $generation_id,
427                           $group_name) {
428      $this->PDFIndirectObject($handler,
429                               $object_id,
430                               $generation_id);
431
432      /**
433       * Generate default group name, if needed
434       */
435      if (is_null($group_name) || $group_name == "") {
436        $group_name = sprintf("FieldGroup%d", $this->get_object_id());
437      };
438      $this->_group_name = $group_name;
439
440      $this->_kids = array();
441    }
442
443    function _check_field_name($field) {
444      /**
445       * Check if field name is empty
446       */
447      if (trim($field->get_field_name()) == "") {
448        error_log(sprintf("Found form field with empty name"));
449        return false;
450      };
451
452      /**
453       * Check if field name is unique inside this form! If we will not do it,
454       * some widgets may become inactive (ignored by PDF Reader)
455       */
456      foreach ($this->_kids as $kid) {
457        if ($kid->get_field_name() == $field->get_field_name()) {
458          error_log(sprintf("Interactive form '%s' already contains field named '%s'",
459                            $this->_group_name,
460                            $kid->get_field_name()));
461          return false;
462        }
463      };
464
465      return true;
466    }
467
468    function add_field(&$field) {
469      if (!$this->_check_field_name($field)) {
470        /**
471         * Field name is not unique; replace it with automatically-generated one
472         */
473        $field->set_field_name(sprintf("%s_FieldObject%d",
474                                       $field->get_field_name(),
475                                       $field->get_object_id()));
476      };
477
478      $this->_kids[] =& $field;
479      $field->set_parent($this);
480    }
481
482    function _dict(&$handler) {
483      return array_merge(parent::_dict($handler),
484                         array("Kids" => $handler->_reference_array($this->_kids),
485                               "T"    => $handler->_textstring($this->_group_name)));
486      return $content;
487    }
488
489    function _out_nested(&$handler) {
490      parent::_out_nested($handler);
491
492      foreach ($this->_kids as $field) {
493        $field->out($handler);
494      }
495    }
496  }
497
498  /**
499   * Generic superclass for all PDF interactive field widgets
500   */
501  class PDFField extends PDFAnnotationWidget {
502    /**
503     * @var string Partial field name (see PDF Specification 1.6 p.638 for explanation on "partial" and
504     * "fully qualified" field names
505     * @access private
506     */
507    var $_field_name;
508
509    /**
510     * @var PDFFieldGroup REference to a containing form object
511     * @access private
512     */
513    var $_parent;
514
515    function PDFField(&$handler,
516                      $object_id,
517                      $generation_id,
518                      $rect,
519                      $field_name) {
520      $this->PDFAnnotationWidget($handler,
521                                 $object_id,
522                                 $generation_id,
523                                 $rect);
524
525      /**
526       * Generate default field name, if needed
527       * @TODO: validate field_name contents
528       */
529      if (is_null($field_name) || $field_name == "") {
530        $field_name = sprintf("FieldObject%d", $this->get_object_id());
531      };
532
533      $this->_field_name = $field_name;
534    }
535
536    function get_field_name() {
537      if ($this->_field_name) {
538        return $this->_field_name;
539      } else {
540        return sprintf("FormObject%d", $this->get_object_id());
541      };
542    }
543
544    function _dict(&$handler) {
545      return array_merge(parent::_dict($handler),
546                         array("Parent" => $handler->_reference($this->_parent),
547                               "T"      => $handler->_textstring($this->get_field_name()),
548                               'F'      => PDF_ANNOTATION_PRINTABLE));
549    }
550
551    function pdf(&$handler) {
552      return $handler->_dictionary($this->_dict($handler));
553    }
554
555    function set_field_name($value) {
556      $this->_field_name = $value;
557    }
558
559    function set_parent(&$form) {
560      $this->_parent =& $form;
561    }
562
563    function get_parent() {
564      return $this->_parent;
565    }
566  }
567
568  /**
569   * Checkbox interactive form widget
570   */
571  class PDFFieldCheckBox extends PDFField {
572    var $_value;
573    var $_appearance_on;
574    var $_appearance_off;
575    var $_checked;
576
577    function PDFFieldCheckBox(&$handler,
578                              $object_id,
579                              $generation_id,
580                              $rect,
581                              $field_name,
582                              $value,
583                              $checked) {
584      $this->PDFField($handler,
585                      $object_id,
586                      $generation_id,
587                      $rect,
588                      $field_name);
589
590      $this->_value = $value;
591      $this->_checked = $checked;
592
593      $this->_appearance_on = new PDFAppearanceStream($handler,
594                                                      $handler->_generate_new_object_number(),
595                                                      $generation_id,
596                                                      "Q 0 0 1 rg BT /F1 10 Tf 0 0 Td (8) Tj ET q");
597
598      $this->_appearance_off = new PDFAppearanceStream($handler,
599                                                       $handler->_generate_new_object_number(),
600                                                       $generation_id,
601                                                       "Q 0 0 1 rg BT /F1 10 Tf 0 0 Td (8) Tj ET q");
602    }
603
604    function _dict(&$handler) {
605      return array_merge(parent::_dict($handler),
606                         array(
607                               'FT'      => '/Btn',
608                               'Ff'      => sprintf("%d", 0),
609                               'TU'      => "<FEFF>",
610                               'MK'      => "<< /CA (3) >>",
611                               'DV'      => $this->_checked ? $handler->_name($this->_value) : "/Off",
612                               'V'       => $this->_checked ? $handler->_name($this->_value) : "/Off",
613                               'AP'      => sprintf("<< /N << /%s %s /Off %s >> >>",
614                                                    $this->_value,
615                                                    $handler->_reference($this->_appearance_on),
616                                                    $handler->_reference($this->_appearance_off))
617                               )
618                         );
619    }
620
621    function _out_nested(&$handler) {
622      parent::_out_nested($handler);
623
624      $this->_appearance_on->out($handler);
625      $this->_appearance_off->out($handler);
626    }
627  }
628
629  class PDFFieldPushButton extends PDFField {
630    var $_appearance;
631    var $fontindex;
632    var $fontsize;
633
634    function _out_nested(&$handler) {
635      parent::_out_nested($handler);
636
637      $this->_appearance->out($handler);
638    }
639
640    function PDFFieldPushButton(&$handler,
641                                $object_id,
642                                $generation_id,
643                                $rect,
644                                $fontindex,
645                                $fontsize) {
646      $this->PDFField($handler,
647                      $object_id,
648                      $generation_id,
649                      $rect,
650                      null);
651      $this->fontindex = $fontindex;
652      $this->fontsize  = $fontsize;
653
654      $this->_appearance = new PDFAppearanceStream($handler,
655                                                   $handler->_generate_new_object_number(),
656                                                   $generation_id,
657                                                   "Q 0 0 1 rg BT /F1 10 Tf 0 0 Td (8) Tj ET q");
658    }
659
660    function _action(&$handler) {
661      return "<< >>";
662    }
663
664    function _dict(&$handler) {
665      return array_merge(parent::_dict($handler),
666                         array(
667                               'FT'      => '/Btn',
668                               'Ff'      => sprintf("%d", 1 << 16),
669                               'TU'      => "<FEFF>",
670                               'DR'      => "2 0 R",
671                               'DA'      => sprintf("(0 0 0 rg /F%d %.2f Tf)",
672                                                    $this->fontindex,
673                                                    $this->fontsize),
674                               'AP'      => "<< /N ".$handler->_reference($this->_appearance)." >>",
675                               'AA'      => $this->_action($handler)
676                               ));
677    }
678  }
679
680  class PDFFieldPushButtonImage extends PDFFieldPushButton {
681    var $_link;
682
683    function PDFFieldPushButtonImage(&$handler,
684                                      $object_id,
685                                      $generation_id,
686                                      $rect,
687                                      $fontindex,
688                                      $fontsize,
689                                      $field_name,
690                                      $value,
691                                      $link) {
692      $this->PDFFieldPushButton($handler,
693                                $object_id,
694                                $generation_id,
695                                $rect,
696                                $fontindex,
697                                $fontsize);
698
699      $this->_link  = $link;
700      $this->set_field_name($field_name);
701    }
702
703    function _action(&$handler) {
704      $action = $handler->_dictionary(array(
705                                            'S'     => "/SubmitForm",
706                                            'F'     => $handler->_textstring($this->_link),
707                                            'Fields'=> $handler->_reference_array(array($this->get_parent())),
708                                            'Flags' => PDF_SUBMIT_FORM_HTML | PDF_SUBMIT_FORM_COORDINATES
709                                            )
710                                      );
711      return $handler->_dictionary(array('U' => $action));
712    }
713  }
714
715  class PDFFieldPushButtonSubmit extends PDFFieldPushButton {
716    var $_link;
717    var $_caption;
718
719    function PDFFieldPushButtonSubmit(&$handler,
720                                      $object_id,
721                                      $generation_id,
722                                      $rect,
723                                      $fontindex,
724                                      $fontsize,
725                                      $field_name,
726                                      $value,
727                                      $link) {
728      $this->PDFFieldPushButton($handler,
729                                $object_id,
730                                $generation_id,
731                                $rect,
732                                $fontindex,
733                                $fontsize);
734
735      $this->_link    = $link;
736      $this->_caption = $value;
737      $this->set_field_name($field_name);
738    }
739
740    function _action(&$handler) {
741      $action = $handler->_dictionary(array(
742                                            'S'     => "/SubmitForm",
743                                            'F'     => $handler->_textstring($this->_link),
744                                            'Fields'=> $handler->_reference_array(array($this->get_parent())),
745                                            'Flags' => PDF_SUBMIT_FORM_HTML
746                                            )
747                                      );
748      return $handler->_dictionary(array('U' => $action));
749    }
750  }
751
752  class PDFFieldPushButtonReset extends PDFFieldPushButton {
753    function PDFFieldPushButtonReset(&$handler,
754                                     $object_id,
755                                     $generation_id,
756                                     $rect,
757                                     $fontindex,
758                                     $fontsize) {
759      $this->PDFFieldPushButton($handler,
760                                $object_id,
761                                $generation_id,
762                                $rect,
763                                $fontindex,
764                                $fontsize);
765    }
766
767    function _action(&$handler) {
768      $action = $handler->_dictionary(array('S' => "/ResetForm"));
769      return $handler->_dictionary(array('U' => $action));
770    }
771  }
772
773  /**
774   * Radio button inside the group.
775   *
776   * Note that radio button is not a field itself; only a group of radio buttons
777   * should have name.
778   */
779  class PDFFieldRadio extends PDFAnnotationWidget {
780    /**
781     * @var PDFFieldRadioGroup reference to a radio button group
782     * @access private
783     */
784    var $_parent;
785
786    /**
787     * @var String value of this radio button
788     * @access private
789     */
790    var $_value;
791
792    var $_appearance_on;
793    var $_appearance_off;
794
795    function PDFFieldRadio(&$handler,
796                           $object_id,
797                           $generation_id,
798                           $rect,
799                           $value) {
800      $this->PDFAnnotationWidget($handler,
801                                 $object_id,
802                                 $generation_id,
803                                 $rect);
804
805      $this->_value = $value;
806
807      $this->_appearance_on = new PDFAppearanceStream($handler,
808                                                      $handler->_generate_new_object_number(),
809                                                      $generation_id,
810                                                      "Q 0 0 1 rg BT /F1 10 Tf 0 0 Td (8) Tj ET q");
811
812      $this->_appearance_off = new PDFAppearanceStream($handler,
813                                                       $handler->_generate_new_object_number(),
814                                                       $generation_id,
815                                                       "Q 0 0 1 rg BT /F1 10 Tf 0 0 Td (8) Tj ET q");
816    }
817
818    function _dict(&$handler) {
819      return array_merge(parent::_dict($handler),
820                         array(
821                               'MK'      => "<< /CA (l) >>",
822                               'Parent'  => $handler->_reference($this->_parent),
823                               'AP'      => sprintf("<< /N << /%s %s /Off %s >> >>",
824                                                    $this->_value,
825                                                    $handler->_reference($this->_appearance_on),
826                                                    $handler->_reference($this->_appearance_off))
827                               ));
828    }
829
830    function _out_nested(&$handler) {
831      parent::_out_nested($handler);
832
833      $this->_appearance_on->out($handler);
834      $this->_appearance_off->out($handler);
835    }
836
837    /**
838     * Set a reference to the radio button group containing this group
839     *
840     * @param PDFFieldRadioGroup $parent reference to a group object
841     */
842    function set_parent(&$parent) {
843      $this->_parent =& $parent;
844    }
845  }
846
847  /**
848   * Create new group of radio buttons
849   */
850  class PDFFieldRadioGroup extends PDFFieldGroup {
851    var $_parent;
852    var $_checked;
853
854    function _dict($handler) {
855      return array_merge(parent::_dict($handler),
856                         array(
857                               'DV'      => $this->_checked ? $handler->_name($this->_checked) : "/Off",
858                               'V'       => $this->_checked ? $handler->_name($this->_checked) : "/Off",
859                               "FT"      => $handler->_name('Btn'),
860                               "Ff"      => sprintf("%d", 1 << 15),
861                               "Parent"  => $handler->_reference($this->_parent)
862                               ));
863    }
864
865    function _check_field_name($field) {
866      /**
867       * As radio buttons always have same field name, no checking should be made here
868       */
869
870      return true;
871    }
872
873    function PDFFieldRadioGroup(&$handler,
874                                $object_id,
875                                $generation_id,
876                                $group_name) {
877      $this->PDFFieldGroup($handler,
878                           $object_id,
879                           $generation_id,
880                           $group_name);
881
882      $this->_checked = null;
883    }
884
885    /**
886     * @return String name of the radio group
887     */
888    function get_field_name() {
889      return $this->_group_name;
890    }
891
892    function set_checked($value) {
893      $this->_checked = $value;
894    }
895
896    function set_parent(&$parent) {
897      $this->_parent =& $parent;
898    }
899  }
900
901  class PDFFieldSelect extends PDFField {
902    var $_options;
903    var $_value;
904
905    function _dict(&$handler) {
906      $options = array();
907      foreach ($this->_options as $arr) {
908        $options[] = $handler->_array(sprintf("%s %s",
909                                              $handler->_textstring($arr[0]),
910                                              $handler->_textstring($arr[1])));
911      };
912
913      $options_str = $handler->_array(implode(" ",$options));
914
915      return array_merge(parent::_dict($handler),
916                         array('FT'      => '/Ch',
917                               'Ff'      => PDF_FIELD_CHOICE_COMBO,
918                               'V'       => $handler->_textstring($this->_value), // Current value
919                               'DV'      => $handler->_textstring($this->_value), // Default value
920                               'DR'      => "2 0 R",
921                               'Opt'     => $options_str));
922    }
923
924    function PDFFieldSelect(&$handler,
925                            $object_id,
926                            $generation_id,
927                            $rect,
928                            $field_name,
929                            $value,
930                            $options) {
931      $this->PDFField($handler,
932                      $object_id,
933                      $generation_id,
934                      $rect,
935                      $field_name);
936
937      $this->_options = $options;
938      $this->_value   = $value;
939    }
940  }
941
942  /**
943   * Interactive text input
944   */
945  class PDFFieldText extends PDFField {
946    var $fontindex;
947    var $fontsize;
948
949    var $_appearance;
950
951    /**
952     * @var String contains the default value of this text field
953     * @access private
954     */
955    var $_value;
956
957    function _dict(&$handler) {
958      return array_merge(parent::_dict($handler),
959                         array(
960                               'FT'      => '/Tx',
961                               'V'       => $handler->_textstring($this->_value), // Current value
962                               'DV'      => $handler->_textstring($this->_value), // Default value
963                               'DR'      => "2 0 R",
964                               // @TODO fix font references
965                               'DA'      => sprintf("(0 0 0 rg /FF%d %.2f Tf)",
966                                                    $this->fontindex,
967                                                    $this->fontsize),
968//                                'AP'      => $handler->_dictionary(array("N" => $handler->_reference($this->_appearance))),
969                               ));
970    }
971
972    function _out_nested(&$handler) {
973      //      $this->_appearance->out($handler);
974    }
975
976    function PDFFieldText(&$handler,
977                          $object_id,
978                          $generation_id,
979                          $rect,
980                          $field_name,
981                          $value,
982                          $fontindex,
983                          $fontsize) {
984      $this->PDFField($handler,
985                      $object_id,
986                      $generation_id,
987                      $rect,
988                      $field_name);
989
990      $this->fontindex = $fontindex;
991      $this->fontsize  = $fontsize;
992      $this->_value = $value;
993
994//       $this->_appearance = new PDFAppearanceStream($handler,
995//                                                    $handler->_generate_new_object_number(),
996//                                                    $generation_id,
997//                                                    "/Tx BMC EMC");
998    }
999  }
1000
1001  class PDFFieldMultilineText extends PDFFieldText {
1002    function _dict(&$handler) {
1003      return array_merge(parent::_dict($handler),
1004                         array('Ff'      => PDF_FIELD_TEXT_MULTILINE));
1005    }
1006  }
1007
1008  /**
1009   * "Password" text input field
1010   */
1011  class PDFFieldPassword extends PDFFieldText {
1012    function PDFFieldPassword(&$handler,
1013                              $object_id,
1014                              $generation_id,
1015                              $rect,
1016                              $field_name,
1017                              $value,
1018                              $fontindex,
1019                              $fontsize) {
1020      $this->PDFFieldText($handler,
1021                          $object_id,
1022                          $generation_id,
1023                          $rect,
1024                          $field_name,
1025                          $value,
1026                          $fontindex,
1027                          $fontsize);
1028    }
1029
1030    function _dict(&$handler) {
1031      return array_merge(parent::_dict($handler),
1032                         array('Ff'      => PDF_FIELD_TEXT_PASSWORD));
1033    }
1034  }
1035
1036  class FPDF {
1037    //Private properties
1038
1039    var $page;               //current page number
1040    var $n;                  //current object number
1041    var $offsets;            //array of object offsets
1042    var $buffer;             //buffer holding in-memory PDF
1043    var $pages;              //array containing pages
1044    var $state;              //current document state
1045    var $compress;           //compression flag
1046    var $DefOrientation;     //default orientation
1047    var $k;                  //scale factor (number of points in user unit)
1048    var $fwPt,$fhPt;         //dimensions of page format in points
1049    var $fw,$fh;             //dimensions of page format in user unit
1050    var $wPt,$hPt;           //current dimensions of page in points
1051    var $w,$h;               //current dimensions of page in user unit
1052    var $x,$y;               //current position in user unit for cell positioning
1053    var $lasth;              //height of last cell printed
1054    var $LineWidth;          //line width in user unit
1055    var $fonts;              //array of used fonts
1056    var $FontFiles;          //array of font files
1057
1058    var $diffs;              //array of encoding differences
1059    var $cmaps;              // List of ToUnicode
1060
1061    var $images;             //array of used images
1062    //    var $PageLinks;          //array of links in pages
1063    var $links;              //array of internal links
1064    var $FontFamily;         //current font family
1065
1066    var $underline;          //underlining flag
1067    var $overline;
1068    var $strikeout;
1069
1070    var $CurrentFont;        //current font info
1071    var $FontSizePt;         //current font size in points
1072    var $FontSize;           //current font size in user unit
1073    var $DrawColor;          //commands for drawing color
1074    var $FillColor;          //commands for filling color
1075    var $TextColor;          //commands for text color
1076
1077    var $ColorFlag;          //indicates whether fill and text colors are different
1078
1079    var $ws;                 //word spacing
1080    var $ZoomMode;           //zoom display mode
1081    var $LayoutMode;         //layout display mode
1082    var $title;              //title
1083    var $subject;            //subject
1084    var $author;             //author
1085    var $keywords;           //keywords
1086    var $creator;            //creator
1087    var $PDFVersion;         //PDF version number
1088
1089    var $_forms;
1090    var $_form_radios;
1091    var $_pages;
1092
1093    function moveto($x, $y) {
1094      $this->_out(sprintf("%.2f %.2f m",
1095                          $this->x_coord($x),
1096                          $this->y_coord($y)));
1097    }
1098
1099    function lineto($x, $y) {
1100      $this->_out(sprintf("%.2f %.2f l",
1101                          $this->x_coord($x),
1102                          $this->y_coord($y)));
1103    }
1104
1105    function closepath() {
1106      $this->_out("h");
1107    }
1108
1109    function stroke() {
1110      $this->_out("S");
1111    }
1112
1113    function is_object_written($id) {
1114      return isset($this->offsets[$id]);
1115    }
1116
1117    function x_coord($x) {
1118      return $x * $this->k;
1119    }
1120
1121    function y_coord($y) {
1122      return ($this->h - $y)*$this->k;
1123    }
1124
1125    // PDF specs:
1126    // 3.2.9 Indirect Objects
1127    // Any object in a PDF file may be labeled as an indirect object. This gives the object
1128    // a unique object identifier by which other objects can refer to it (for example, as an
1129    // element of an array or as the value of a dictionary entry). The object identifier
1130    // consists of two parts:
1131    // * A positive integer object number. Indirect objects are often numbered sequentially
1132    //   within a PDF file, but this is not required; object numbers may be
1133    //   assigned in any arbitrary order.
1134    // * A non-negative integer generation number. In a newly created file, all indirect
1135    //   objects have generation numbers of 0. Nonzero generation numbers may be introduced
1136    //   when the file is later updated; see Sections 3.4.3, “Cross-Reference
1137    //   Table,” and 3.4.5, “Incremental Updates.”
1138    // Together, the combination of an object number and a generation number uniquely
1139    // identifies an indirect object. The object retains the same object number and
1140    // generation number throughout its existence, even if its value is modified.
1141    //
1142    function _indirect_object($object) {
1143      $object_number = $object->get_object_id();
1144      $generation_number = $object->get_generation_id();
1145      $object_string = $object->pdf($this);
1146
1147      $this->offsets[$object_number] = strlen($this->buffer);
1148
1149      return "$object_number $generation_number obj\n${object_string}\nendobj";
1150    }
1151
1152    function _stream($content) {
1153      return "stream\n".$content."\nendstream";
1154    }
1155
1156    /**
1157     * @TODO check name for validity
1158     */
1159    function _name($name) {
1160      return sprintf("/%s", $name);
1161    }
1162
1163    function _dictionary($dict) {
1164      $content = "";
1165      foreach ($dict as $key => $value) {
1166        $content .= "/$key $value\n";
1167      };
1168      return "<<\n".$content."\n>>";
1169    }
1170
1171    function _array($array_str) {
1172      return "[$array_str]";
1173    }
1174
1175    function _reference(&$object) {
1176      $object_id     = $object->get_object_id();
1177      $generation_id = $object->get_generation_id();
1178      return "$object_id $generation_id R";
1179    }
1180
1181    function _reference_array($object_array) {
1182      $array_str = "";
1183      for ($i=0; $i<count($object_array); $i++) {
1184        $array_str .= $this->_reference($object_array[$i])." ";
1185      };
1186      return $this->_array($array_str);
1187    }
1188
1189    function _generate_new_object_number() {
1190      $this->n++;
1191      return $this->n;
1192    }
1193
1194    function add_form($name) {
1195      $form = new PDFFieldGroup($this,
1196                                $this->_generate_new_object_number(),    // Object identifier
1197                                0,
1198                                $name);
1199      $this->_forms[] =& $form;
1200    }
1201
1202    function add_field_select($x, $y, $w, $h, $name, $value, $options) {
1203      $field =& new PDFFieldSelect($this,
1204                                   $this->_generate_new_object_number(),    // Object identifier
1205                                   0,                                       // Generation
1206                                   new PDFRect($x, $y, $w, $h),             // Annotation rectangle
1207                                   $name,                                   // Field name
1208                                   $value,
1209                                   $options);
1210
1211      $current_form =& $this->current_form();
1212      $current_form->add_field($field);
1213
1214      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1215    }
1216
1217    /**
1218     * Create new checkbox field object
1219     *
1220     * @param $x Integer Left coordinate of the widget bounding bog
1221     * @param $y Integer Upper coordinate of the widget bounding bog
1222     * @param $w Integer Widget width
1223     * @param $h Integer Widget height
1224     * @param $name String name of the field to be created
1225     * @param $value String value to be posted for this checkbox
1226     *
1227     * @TODO check if fully qualified field name will be unique in PDF file
1228     */
1229    function add_field_checkbox($x, $y, $w, $h, $name, $value, $checked) {
1230      $field =& new PDFFieldCheckBox($this,
1231                                     $this->_generate_new_object_number(),    // Object identifier
1232                                     0,                                       // Generation
1233                                     new PDFRect($x, $y, $w, $h),             // Annotation rectangle
1234                                     $name,                                   // Field name
1235                                     $value, $checked);                                 // Checkbox "on" value
1236
1237      $current_form =& $this->current_form();
1238      $current_form->add_field($field);
1239
1240      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1241    }
1242
1243    function &current_form() {
1244      if (count($this->_forms) == 0) {
1245        /**
1246         * Handle invalid HTML; if we've met an input control outside the form,
1247         * generate a new form with random name
1248         */
1249
1250        $id   = $this->_generate_new_object_number();
1251        $name = sprintf("AnonymousFormObject_%u", $id);
1252
1253        error_log(sprintf("Anonymous form generated with name %s; check your HTML for validity",
1254                          $name));
1255
1256        $form = new PDFFieldGroup($this,
1257                                  $id,    // Object identifier
1258                                  0,
1259                                  $name);
1260        $this->_forms[] =& $form;
1261      };
1262
1263      return $this->_forms[count($this->_forms)-1];
1264    }
1265
1266    function add_field_radio($x, $y, $w, $h, $group_name, $value, $checked) {
1267      if (isset($this->_form_radios[$group_name])) {
1268        $field =& $this->_form_radios[$group_name];
1269      } else {
1270        $field =& new PDFFieldRadioGroup($this,
1271                                         $this->_generate_new_object_number(),
1272                                         0,
1273                                         $group_name);
1274
1275        $current_form =& $this->current_form();
1276        $current_form->add_field($field);
1277
1278        $this->_form_radios[$group_name] =& $field;
1279      };
1280
1281      $radio =& new PDFFieldRadio($this,
1282                                  $this->_generate_new_object_number(),
1283                                  0,
1284                                  new PDFRect($x, $y, $w, $h),
1285                                  $value);
1286      $field->add_field($radio);
1287      if ($checked) { $field->set_checked($value); };
1288
1289      $this->_pages[count($this->_pages)-1]->add_annotation($radio);
1290    }
1291
1292    /**
1293     * Create a new interactive text form
1294     *
1295     * @param $x Left coordinate of the widget bounding box
1296     * @param $y Top coordinate of the widget bounding box
1297     * @param $w Widget width
1298     * @param $h Widget height
1299     * @param $value Default widget value
1300     * @param $field_name Field name
1301     *
1302     * @return Field number
1303     */
1304    function add_field_text($x, $y, $w, $h, $value, $field_name) {
1305      $field =& new PDFFieldText($this,
1306                                 $this->_generate_new_object_number(),
1307                                 0,
1308                                 new PDFRect($x, $y, $w, $h),
1309                                 $field_name,
1310                                 $value,
1311                                 $this->CurrentFont['i'],
1312                                 $this->FontSizePt);
1313
1314      $current_form =& $this->current_form();
1315      $current_form->add_field($field);
1316
1317      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1318    }
1319
1320    function add_field_multiline_text($x, $y, $w, $h, $value, $field_name) {
1321      $field =& new PDFFieldMultilineText($this,
1322                                          $this->_generate_new_object_number(),
1323                                          0,
1324                                          new PDFRect($x, $y, $w, $h),
1325                                          $field_name,
1326                                          $value,
1327                                          $this->CurrentFont['i'],
1328                                          $this->FontSizePt);
1329
1330      $current_form =& $this->current_form();
1331      $current_form->add_field($field);
1332
1333      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1334    }
1335
1336    /**
1337     * Create a new interactive password input field
1338     *
1339     * @param $x Left coordinate of the widget bounding box
1340     * @param $y Top coordinate of the widget bounding box
1341     * @param $w Widget width
1342     * @param $h Widget height
1343     * @param $value Default widget value
1344     * @param $field_name Field name
1345     *
1346     * @return Field number
1347     */
1348    function add_field_password($x, $y, $w, $h, $value, $field_name) {
1349      $field =& new PDFFieldPassword($this,
1350                                     $this->_generate_new_object_number(),
1351                                     0,
1352                                     new PDFRect($x, $y, $w, $h),
1353                                     $field_name,
1354                                     $value,
1355                                     $this->CurrentFont['i'],
1356                                     $this->FontSizePt);
1357
1358      $current_form =& $this->current_form();
1359      $current_form->add_field($field);
1360
1361      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1362    }
1363
1364    function add_field_pushbuttonimage($x, $y, $w, $h, $field_name, $value, $actionURL) {
1365      $field =& new PDFFieldPushButtonImage($this,
1366                                            $this->_generate_new_object_number(),
1367                                            0,
1368                                            new PDFRect($x, $y, $w, $h),
1369                                            $this->CurrentFont['i'],
1370                                            $this->FontSizePt,
1371                                            $field_name,
1372                                            $value,
1373                                            $actionURL);
1374
1375      $current_form =& $this->current_form();
1376      $current_form->add_field($field);
1377
1378      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1379    }
1380
1381    function add_field_pushbuttonsubmit($x, $y, $w, $h, $field_name, $value, $actionURL) {
1382      $field =& new PDFFieldPushButtonSubmit($this,
1383                                             $this->_generate_new_object_number(),
1384                                             0,
1385                                             new PDFRect($x, $y, $w, $h),
1386                                             $this->CurrentFont['i'],
1387                                             $this->FontSizePt,
1388                                             $field_name,
1389                                             $value,
1390                                             $actionURL);
1391
1392      $current_form =& $this->current_form();
1393      $current_form->add_field($field);
1394
1395      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1396    }
1397
1398    function add_field_pushbuttonreset($x, $y, $w, $h) {
1399      $field =& new PDFFieldPushButtonReset($this,
1400                                            $this->_generate_new_object_number(),
1401                                            0,
1402                                            new PDFRect($x, $y, $w, $h),
1403                                            null,
1404                                            $this->CurrentFont['i'],
1405                                            $this->FontSizePt);
1406
1407      $current_form =& $this->current_form();
1408      $current_form->add_field($field);
1409
1410      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1411    }
1412
1413    function add_field_pushbutton($x, $y, $w, $h) {
1414      $field =& new PDFFieldPushButton($this,
1415                                       $this->_generate_new_object_number(),
1416                                       0,
1417                                       new PDFRect($x, $y, $w, $h),
1418                                       null,
1419                                       $this->CurrentFont['i'],
1420                                       $this->FontSizePt);
1421
1422      $current_form =& $this->current_form();
1423      $current_form->add_field($field);
1424
1425      $this->_pages[count($this->_pages)-1]->add_annotation($field);
1426    }
1427
1428
1429    function SetDash($x, $y) {
1430      $x = (int)$x;
1431      $y = (int)$y;
1432      $this->_out(sprintf("[%d %d] 0 d", $x*2, $y*2));
1433    }
1434
1435    function _GetFontBBox() {
1436      return preg_split("/[\[\]\s]+/", $this->CurrentFont['desc']['FontBBox']);
1437    }
1438
1439    function _dounderline($x,$y,$txt) {
1440      //Underline text
1441      $up=$this->CurrentFont['up'];
1442      $ut=$this->CurrentFont['ut'];
1443      $w=$this->GetStringWidth($txt)+$this->ws*substr_count($txt,' ');
1444
1445      $content = sprintf('%.2f %.2f %.2f %.2f re f',
1446                         $x*$this->k,
1447                         ($this->h-($y-$up/1000*$this->FontSize))*$this->k,
1448                         $w*$this->k,
1449                         -$ut/1000*$this->FontSizePt);
1450
1451      return $content;
1452    }
1453
1454    function _dooverline($x,$y,$txt) {
1455      $bbox = $this->_GetFontBBox();
1456      $up = round($bbox[3] * 0.8);
1457
1458      $ut=$this->CurrentFont['ut'];
1459
1460      $w=$this->GetStringWidth($txt)+$this->ws*substr_count($txt,' ');
1461      return sprintf('%.2f %.2f %.2f %.2f re f',
1462                     $x*$this->k,
1463                     ($this->h-($y-$up/1000*$this->FontSize))*$this->k,
1464                     $w*$this->k,
1465                     -$ut/1000*$this->FontSizePt);
1466    }
1467
1468    function _dostrikeout($x,$y,$txt) {
1469      $bbox = $this->_GetFontBBox();
1470      $up = round($bbox[3] * 0.25);
1471
1472      $ut=$this->CurrentFont['ut'];
1473      $w=$this->GetStringWidth($txt)+$this->ws*substr_count($txt,' ');
1474      return sprintf('%.2f %.2f %.2f %.2f re f',
1475                     $x*$this->k,
1476                     ($this->h-($y-$up/1000*$this->FontSize))*$this->k,
1477                     $w*$this->k,
1478                     -$ut/1000*$this->FontSizePt);
1479    }
1480
1481    function SetDecoration($underline, $overline, $strikeout) {
1482      $this->underline = $underline;
1483      $this->overline  = $overline;
1484      $this->strikeout = $strikeout;
1485    }
1486
1487    function ClipPath($path) {
1488      if (count($path) < 3) {
1489        die("Attempt to clip on the path containing less than three points");
1490      };
1491
1492      $this->MakePath($path);
1493      $this->Clip();
1494    }
1495
1496    function Clip() {
1497      $this->_out("W n");
1498    }
1499
1500    // TODO: more graceful custom encoding processing
1501    function _LoadFont($fontkey, $family, $encoding) {
1502      if (!isset($this->fonts[$fontkey])) {
1503        global $g_font_resolver_pdf;
1504        $file = $g_font_resolver_pdf->ttf_mappings[$family];
1505
1506        $embed = $g_font_resolver_pdf->embed[$family];
1507
1508        // Remove the '.ttf' suffix
1509        $file = substr($file, 0, strlen($file) - 4);
1510
1511        // Generate (if required) PHP font description files
1512        if (!file_exists($this->_getfontpath().$fontkey.'.php') ||
1513            ManagerEncoding::is_custom_encoding($encoding)) {
1514          // As MakeFont squeaks a lot, we'll need to capture and discard its output
1515          MakeFont(TTF_FONTS_REPOSITORY.$file.'.ttf',
1516                   TTF_FONTS_REPOSITORY.$file.'.afm',
1517                   $this->_getfontpath(),
1518                   $fontkey.'.php',
1519                   $encoding);
1520        };
1521
1522        $this->AddFont($fontkey, $family, $encoding, $fontkey.'.php', $embed);
1523      };
1524    }
1525
1526    function _MakeFontKey($family, $encoding) {
1527      return $family.'-'.$encoding;
1528    }
1529
1530    function GetFontAscender($name, $encoding) {
1531      $fontkey = $this->_MakeFontKey($name, $encoding);
1532      $this->_LoadFont($fontkey, $name, $encoding, '');
1533      return $this->fonts[$fontkey]['desc']['Ascent'] / 1000;
1534    }
1535
1536    function GetFontDescender($name, $encoding) {
1537      $fontkey = $this->_MakeFontKey($name, $encoding);
1538      $this->_LoadFont($fontkey, $name, $encoding, '');
1539      return -$this->fonts[$fontkey]['desc']['Descent'] / 1000;
1540    }
1541
1542    // Note that FPDF do some caching, which can conflict with "save/restore" pairs
1543    function Save() {
1544      $this->_out("q");
1545    }
1546
1547    function Restore() {
1548      $this->_out("Q");
1549    }
1550
1551    function Translate($dx, $dy) {
1552      $this->_out(sprintf("1 0 0 1 %.2f %.2f cm", $dx, $dy));
1553    }
1554
1555    function Rotate($alpha) {
1556      $this->_out(sprintf("%.2f %.2f %.2f %.2f 0 0 cm",
1557                          cos($alpha/180*pi()),
1558                          sin($alpha/180*pi()),
1559                          -sin($alpha/180*pi()),
1560                          cos($alpha/180*pi())
1561                          ));
1562    }
1563
1564    function SetTextRendering($mode) {
1565      $this->_out(sprintf("%d Tr", $mode));
1566    }
1567
1568    function MakePath($path) {
1569      $this->_out(sprintf("%.2f %.2f m", $path[0]['x'], $path[0]['y']));
1570
1571      for ($i=1; $i<count($path); $i++) {
1572        $this->_out(sprintf("%.2f %.2f l", $path[$i]['x'], $path[$i]['y']));
1573      };
1574    }
1575
1576    function FillPath($path) {
1577      if (count($path) < 3) {
1578        die("Attempt to fill path containing less than three points");
1579      };
1580
1581      $this->_out($this->FillColor);
1582      $this->MakePath($path);
1583      $this->Fill();
1584    }
1585
1586    function Fill() {
1587      $this->_out("f");
1588    }
1589
1590    /**
1591     * Thanks G. Adam Stanislav for information about approximation circle using bezier curves
1592     * http://www.whizkidtech.redprince.net/bezier/circle/
1593     */
1594    function Circle($x, $y, $r) {
1595      $kappa = (sqrt(2) - 1) / 3 * 4;
1596      $l = $kappa * $r;
1597
1598      $this->_out(sprintf("%.2f %.f2 m", $x + $r, $y));
1599      $this->_out(sprintf("%.2f %.f2 %.2f %.2f %.2f %.2f c",
1600                          $x + $r, $y + $l,
1601                          $x + $l, $y + $r,
1602                          $x, $y + $r));
1603      $this->_out(sprintf("%.2f %.f2 %.2f %.2f %.2f %.2f c",
1604                          $x - $l, $y + $r,
1605                          $x - $r, $y + $l,
1606                          $x - $r, $y));
1607      $this->_out(sprintf("%.2f %.f2 %.2f %.2f %.2f %.2f c",
1608                          $x - $r, $y - $l,
1609                          $x - $l, $y - $r,
1610                          $x, $y - $r));
1611      $this->_out(sprintf("%.2f %.f2 %.2f %.2f %.2f %.2f c",
1612                          $x + $l, $y - $r,
1613                          $x + $r, $y - $l,
1614                          $x + $r, $y));
1615    }
1616
1617    /*******************************************************************************
1618     *                                                                              *
1619     *                               Public methods                                 *
1620     *                                                                              *
1621     *******************************************************************************/
1622    function FPDF($orientation='P', $unit='mm', $format='A4') {
1623      $this->_forms = array();
1624      $this->_form_radios = array();
1625      $this->_pages = array();
1626
1627      //Some checks
1628      $this->_dochecks();
1629
1630      //Initialization of properties
1631      $this->page=0;
1632
1633      $this->n=2;
1634
1635      $this->buffer='';
1636      $this->pages=array();
1637      $this->state = FPDF_STATE_UNINITIALIZED;
1638      $this->fonts=array();
1639      $this->FontFiles=array();
1640      $this->diffs  = array();
1641      $this->images = array();
1642      $this->links  = array();
1643      $this->lasth=0;
1644      $this->FontFamily='';
1645      $this->FontSizePt=12;
1646
1647      $this->underline = false;
1648      $this->overline  = false;
1649      $this->strikeout = false;
1650
1651      $this->DrawColor='0 G';
1652      $this->FillColor='0 g';
1653      $this->TextColor='0 g';
1654      $this->ColorFlag=false;
1655      $this->ws=0;
1656
1657      //Scale factor
1658      switch ($unit) {
1659      case 'pt':
1660        $this->k = 1; break;
1661      case 'mm':
1662        $this->k = 72/25.4; break;
1663      case 'cm':
1664        $this->k = 72/2.54; break;
1665      case 'in':
1666        $this->k = 72;
1667      default:
1668        $this->Error('Incorrect unit: '.$unit);
1669      };
1670
1671      $this->setup_format($format[0], $format[1]);
1672
1673      //Line width (0.2 mm)
1674      $this->LineWidth=.567/$this->k;
1675
1676      //Full width display mode
1677      $this->SetDisplayMode('fullwidth');
1678
1679      //Enable compression
1680      $this->SetCompression(true);
1681
1682      //Set default PDF version number
1683      $this->PDFVersion='1.3';
1684    }
1685
1686    function setup_format($width, $height) {
1687      $this->fwPt = $width * $this->k;
1688      $this->fhPt = $height * $this->k;
1689      $this->wPt = $this->fwPt;
1690      $this->hPt = $this->fhPt;
1691
1692      $this->fw = $width;
1693      $this->fh = $height;
1694      $this->w = $this->fw;
1695      $this->h = $this->fh;
1696
1697      $this->DefOrientation='P';
1698    }
1699
1700    function SetDisplayMode($zoom,$layout='continuous') {
1701      //Set display mode in viewer
1702      if($zoom=='fullpage' || $zoom=='fullwidth' || $zoom=='real' || $zoom=='default' || !is_string($zoom))
1703        $this->ZoomMode=$zoom;
1704      else
1705        $this->Error('Incorrect zoom display mode: '.$zoom);
1706      if($layout=='single' || $layout=='continuous' || $layout=='two' || $layout=='default')
1707        $this->LayoutMode=$layout;
1708      else
1709        $this->Error('Incorrect layout display mode: '.$layout);
1710    }
1711
1712    /**
1713     * @param $compress Boolean indicates whether compression is enabled
1714     */
1715    function SetCompression($compress) {
1716      if (function_exists('gzcompress')) {
1717        $this->compress=$compress;
1718      } else {
1719        $this->compress=false;
1720      };
1721    }
1722
1723    function SetTitle($title) {
1724      //Title of document
1725      $this->title=$title;
1726    }
1727
1728    function SetSubject($subject) {
1729      //Subject of document
1730      $this->subject=$subject;
1731    }
1732
1733    function SetAuthor($author) {
1734      //Author of document
1735      $this->author=$author;
1736    }
1737
1738    function SetKeywords($keywords) {
1739      //Keywords of document
1740      $this->keywords=$keywords;
1741    }
1742
1743    function SetCreator($creator) {
1744      //Creator of document
1745      $this->creator=$creator;
1746    }
1747
1748    function Error($msg) {
1749      //Fatal error
1750      die('<B>FPDF error: </B>'.$msg);
1751    }
1752
1753    function Open() {
1754      //Begin document
1755      $this->state = FPDF_STATE_DOCUMENT_STARTED;
1756    }
1757
1758    function Close() {
1759      //Terminate document
1760      if ($this->state == FPDF_STATE_COMPLETED) {
1761        return;
1762      };
1763
1764      if ($this->page==0) {
1765        $this->AddPage();
1766      };
1767
1768      //Close page
1769      $this->_endpage();
1770      //Close document
1771      $this->_enddoc();
1772    }
1773
1774    function AddPage($width = null, $height = null) {
1775      if (!$width) {
1776        $width = $this->fwPt;
1777      };
1778
1779      if (!$height) {
1780        $height = $this->fhPt;
1781      };
1782
1783      $this->setup_format($width, $height);
1784
1785      $this->_pages[] =& new PDFPage($this, $width, $height, $this->_generate_new_object_number(), 0);
1786
1787      //Start a new page
1788      if ($this->state == FPDF_STATE_UNINITIALIZED) {
1789        $this->Open();
1790      };
1791
1792      $family=$this->FontFamily;
1793      $size=$this->FontSizePt;
1794      $lw=$this->LineWidth;
1795      $dc=$this->DrawColor;
1796      $fc=$this->FillColor;
1797      $tc=$this->TextColor;
1798      $cf=$this->ColorFlag;
1799      if ($this->page>0) {
1800        //Close page
1801        $this->_endpage();
1802      }
1803
1804      //Start new page
1805      $this->_beginpage();
1806      //Set line cap style to square
1807      $this->_out('2 J');
1808      //Set line width
1809      $this->LineWidth=$lw;
1810      $this->_out(sprintf('%.2f w',$lw*$this->k));
1811
1812      //Set colors
1813      $this->DrawColor=$dc;
1814      if ($dc!='0 G') {
1815        $this->_out($dc);
1816      };
1817
1818      $this->FillColor=$fc;
1819      if ($fc!='0 g') {
1820        $this->_out($fc);
1821      };
1822
1823      $this->TextColor=$tc;
1824      $this->ColorFlag=$cf;
1825
1826      //Restore line width
1827      if ($this->LineWidth!=$lw) {
1828        $this->LineWidth=$lw;
1829        $this->_out(sprintf('%.2f w',$lw*$this->k));
1830      }
1831
1832      //Restore colors
1833      if ($this->DrawColor!=$dc) {
1834        $this->DrawColor=$dc;
1835        $this->_out($dc);
1836      }
1837      if ($this->FillColor!=$fc) {
1838        $this->FillColor=$fc;
1839        $this->_out($fc);
1840      }
1841      $this->TextColor=$tc;
1842      $this->ColorFlag=$cf;
1843
1844      if (!is_null($this->CurrentFont)) {
1845        $this->_out(sprintf('BT /F%d %.2f Tf ET',$this->CurrentFont['i'],$this->FontSizePt));
1846      };
1847    }
1848
1849    function SetDrawColor($r,$g=-1,$b=-1) {
1850      // Set color for all stroking operations
1851      if (($r==0 && $g==0 && $b==0) || $g==-1) {
1852        $new_color = sprintf('%.3f G',$r/255);
1853      } else {
1854        $new_color = sprintf('%.3f %.3f %.3f RG',$r/255,$g/255,$b/255);
1855      };
1856
1857      if ($this->page > 0 /*&& $this->DrawColor != $new_color*/) {
1858        $this->DrawColor = $new_color;
1859        $this->_out($this->DrawColor);
1860      };
1861    }
1862
1863    function SetFillColor($r,$g=-1,$b=-1) {
1864      // Set color for all filling operations
1865      if (($r==0 && $g==0 && $b==0) || $g==-1) {
1866        $new_color = sprintf('%.3f g',$r/255);
1867      } else {
1868        $new_color = sprintf('%.3f %.3f %.3f rg',$r/255,$g/255,$b/255);
1869      };
1870
1871      if ($this->page>0 /*&& $this->FillColor != $new_color*/) {
1872        $this->FillColor = $new_color;
1873        $this->ColorFlag = ($this->FillColor!=$this->TextColor);
1874        $this->_out($this->FillColor);
1875      };
1876    }
1877
1878    function SetTextColor($r,$g=-1,$b=-1) {
1879      //Set color for text
1880      if (($r==0 && $g==0 && $b==0) || $g==-1) {
1881        $this->TextColor=sprintf('%.3f g',$r/255);
1882      } else {
1883        $this->TextColor=sprintf('%.3f %.3f %.3f rg',$r/255,$g/255,$b/255);
1884      };
1885
1886      $this->ColorFlag=($this->FillColor!=$this->TextColor);
1887    }
1888
1889    function GetStringWidth($s) {
1890      //Get width of a string in the current font
1891      $s=(string)$s;
1892      $cw = &$this->CurrentFont['cw'];
1893      $w=0;
1894
1895      $l=strlen($s);
1896      for ($i=0; $i<$l; $i++) {
1897        $w+=$cw[$s{$i}];
1898      };
1899
1900      return $w*$this->FontSize/1000;
1901    }
1902
1903    /**
1904     * Set line width
1905     */
1906    function SetLineWidth($width) {
1907      $this->LineWidth = $width;
1908      if ($this->page > 0) {
1909        $this->_out(sprintf('%.2f w',$width*$this->k));
1910      };
1911    }
1912
1913    /**
1914     * Draw a line
1915     */
1916    function Line($x1,$y1,$x2,$y2) {
1917      $this->_out(sprintf('%.2f %.2f m %.2f %.2f l S',$x1*$this->k,($this->h-$y1)*$this->k,$x2*$this->k,($this->h-$y2)*$this->k));
1918    }
1919
1920    /**
1921     * Add a TrueType or Type1 font
1922     */
1923    function AddFont($fontkey, $family, $encoding, $file, $bEmbed) {
1924      if(isset($this->fonts[$fontkey])) {
1925        $this->Error('Font already added: '.$family);
1926      };
1927
1928      $filepath = $this->_getfontpath().$file;
1929      include($filepath);
1930
1931      // After we've executed 'include' the $file variable
1932      // have been overwritten by $file declared in font definition file; if we do not want
1933      // to embed the font in the PDF file, we should set to empty string
1934      if (!$bEmbed) { $file = ''; };
1935
1936      if(!isset($name)) {
1937        $this->Error("Could not include font definition file: $filepath");
1938      };
1939
1940      $i=count($this->fonts)+1;
1941      $this->fonts[$fontkey]=array('i'    =>$i,
1942                                   'type' =>$type,
1943                                   'name' =>$name,
1944                                   'desc' =>$desc,
1945                                   'up'   =>$up,
1946                                   'ut'   =>$ut,
1947                                   'cw'   =>$cw,
1948                                   'enc'  =>$enc,
1949                                   'file' =>$file);
1950
1951      if ($diff) {
1952        //Search existing encodings
1953        $d=0;
1954        $nb=count($this->diffs);
1955        for ($i=1; $i<=$nb; $i++) {
1956          if ($this->diffs[$i] == $diff) {
1957            $d=$i;
1958            break;
1959          }
1960        }
1961        if ($d==0) {
1962          $d=$nb+1;
1963          $this->diffs[$d] = $diff;
1964
1965          /**
1966           * TODO
1967           * Add CMAP for this font
1968           */
1969          $this->cmaps[$d] = new PDFCMap($cmap,
1970                                         $handler,
1971                                         $this->_generate_new_object_number(),
1972                                         0);
1973        }
1974        $this->fonts[$fontkey]['diff']=$d;
1975      }
1976
1977      if ($file) {
1978        if ($type=='TrueType') {
1979          $this->FontFiles[$file]=array('length1'=>$originalsize);
1980        } else {
1981          $this->FontFiles[$file]=array('length1'=>$size1,'length2'=>$size2);
1982        };
1983      }
1984    }
1985
1986    /**
1987     * Select a font; size given in points
1988     */
1989    function SetFont($family, $encoding, $size) {
1990      global $fpdf_charwidths;
1991
1992      $fontkey = $this->_MakeFontKey($family, $encoding);
1993      $this->_LoadFont($fontkey, $family, $encoding);
1994
1995      $this->FontFamily  = $family;
1996      $this->FontSizePt  = $size;
1997      $this->FontSize    = $size/$this->k;
1998      $this->CurrentFont = &$this->fonts[$fontkey];
1999
2000      if ($this->page > 0) {
2001        $this->_out(sprintf('BT /F%d %.2f Tf ET',$this->CurrentFont['i'],$this->FontSizePt));
2002      };
2003    }
2004
2005    /**
2006     * Create a new internal link
2007     */
2008    function AddLink() {
2009      $n=count($this->links)+1;
2010      $this->links[$n]=array(0,0);
2011      return $n;
2012    }
2013
2014    /**
2015     * Set destination of internal link
2016     */
2017    function SetLink($link,$y,$page) {
2018      $this->links[$link]=array($page,$y);
2019    }
2020
2021    /**
2022     * Add an external hyperlink on the page (an rectangular area). It is not bound to any other PDF element,
2023     * like text. It is the task of layout engine to draw the appropriate text inside this area.
2024     *
2025     * @param Float $x X-coordinate of the upper-left corner of the link area
2026     * @param Float $y Y-coordinate of the upper-left corner of the link area
2027     * @param Float $w link area width
2028     * @param Float $h link area height
2029     * @param String $link Link URL
2030     */
2031    function add_link_external($x, $y, $w, $h, $link) {
2032      $link = new PDFAnnotationExternalLink($this,
2033                                            $this->_generate_new_object_number(),
2034                                            0,
2035                                            new PDFRect($x, $y, $w, $h),
2036                                            $link);
2037      $this->_pages[count($this->_pages)-1]->add_annotation($link);
2038    }
2039
2040    /**
2041     * Add an internal hyperlink on the page (an rectangular area). It is not bound to any other PDF element,
2042     * like text. It is the task of layout engine to draw the appropriate text inside this area.
2043     *
2044     * @param Float $x X-coordinate of the upper-left corner of the link area
2045     * @param Float $y Y-coordinate of the upper-left corner of the link area
2046     * @param Float $w link area width
2047     * @param Float $h link area height
2048     * @param Integer $link Internal Link identifier
2049     */
2050    function add_link_internal($x, $y, $w, $h, $link) {
2051      $link = new PDFAnnotationInternalLink($this,
2052                                            $this->_generate_new_object_number(),
2053                                            0,
2054                                            new PDFRect($x, $y, $w, $h),
2055                                            $link);
2056      $this->_pages[count($this->_pages)-1]->add_annotation($link);
2057    }
2058
2059    function Text($x, $y, $txt) {
2060      //Output a string
2061      $s = sprintf('BT %.2f %.2f Td (%s) Tj ET',$x*$this->k,($this->h-$y)*$this->k,$this->_escape($txt));
2062
2063      if ($this->underline && $txt!='') {
2064        $s.=' '.$this->_dounderline($x,$y,$txt);
2065      }
2066
2067      if ($this->overline && $txt!='') {
2068        $s.=' '.$this->_dooverline($x,$y,$txt);
2069      }
2070
2071      if ($this->strikeout && $txt!='') {
2072        $s.=' '.$this->_dostrikeout($x,$y,$txt);
2073      }
2074
2075      if ($this->ColorFlag) {
2076        $s='q '.$this->TextColor.' '.$s.' Q';
2077      };
2078      $this->_out($s);
2079    }
2080
2081    /**
2082     * Accepts PNG images only
2083     */
2084    function Image($file, $x, $y, $w, $h) {
2085      // Image used first time, parse input file
2086      if (!isset($this->images[$file])) {
2087        $ext = pathinfo($file, PATHINFO_EXTENSION);
2088        switch ($ext) {
2089        case 'jpg':
2090        case 'jpeg':
2091          $info = $this->_parsejpg($file);
2092          break;
2093        case 'png':
2094          $info = $this->_parsepng($file);
2095          break;
2096        };
2097
2098        $info['i'] = count($this->images) + 1;
2099        $this->images[$file] = $info;
2100      };
2101
2102      $info = $this->images[$file];
2103      $this->_out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',
2104                          $w*$this->k,
2105                          $h*$this->k,
2106                          $x*$this->k,
2107                          ($this->h-($y+$h))*$this->k,
2108                          $info['i']));
2109    }
2110
2111    /**
2112     * @param $name String file to save generated PDF in
2113     */
2114    function Output($name) {
2115      //Finish document if necessary
2116      if ($this->state != FPDF_STATE_COMPLETED) {
2117        $this->Close();
2118      };
2119
2120      $f=fopen($name,'wb');
2121      if (!$f) {
2122        $this->Error('Unable to create output file: '.$name);
2123      };
2124      fwrite($f,$this->buffer,strlen($this->buffer));
2125      fclose($f);
2126    }
2127
2128    /********************************************************************************
2129     *                                                                              *
2130     *                              Protected methods                               *
2131     *                                                                              *
2132     *******************************************************************************/
2133    function _dochecks() {
2134      // Check for locale-related bug
2135      if (1.1==1) {
2136        $this->Error('Don\'t alter the locale before including class file');
2137      };
2138
2139      // Check for decimal separator
2140      if (sprintf('%.1f',1.0)!='1.0') {
2141        setlocale(LC_NUMERIC,'C');
2142      };
2143    }
2144
2145    function _getfontpath() {
2146      return CACHE_DIR;
2147    }
2148
2149    function _putpages() {
2150      $nb=$this->page;
2151
2152      if ($this->DefOrientation=='P') {
2153        $wPt=$this->fwPt;
2154        $hPt=$this->fhPt;
2155      } else {
2156        $wPt=$this->fhPt;
2157        $hPt=$this->fwPt;
2158      };
2159
2160      $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
2161
2162      $pages_start_obj_number = $this->n+1;
2163
2164      for ($n=1; $n<=$nb; $n++) {
2165        //Page
2166
2167        $page = $this->_pages[$n-1];
2168        $this->offsets[$page->get_object_id()] = strlen($this->buffer);
2169        $this->_out(sprintf("%u %u obj",$page->object_id, $page->generation_id));
2170
2171        $this->_out('<</Type /Page');
2172        $this->_out('/Parent 1 0 R');
2173        $this->_out(sprintf('/MediaBox [0 0 %.2f %.2f]',
2174                            $page->get_width(),
2175                            $page->get_height()));
2176        $this->_out("/Annots ".$this->_pages[$n-1]->_annotations($this));
2177        $this->_out('/Resources 2 0 R');
2178
2179        $this->_out('/Contents '.($this->n+1).' 0 R>>');
2180        $this->_out('endobj');
2181        //Page content
2182        $p=($this->compress) ? gzcompress($this->pages[$n]) : $this->pages[$n];
2183        $this->_newobj();
2184        $this->_out('<<'.$filter.'/Length '.strlen($p).'>>');
2185        $this->_putstream($p);
2186        $this->_out('endobj');
2187
2188        // Output annotation object for this page
2189        $annotations = $this->_pages[$n-1]->annotations;
2190        $size = count($annotations);
2191
2192        for ($j=0; $j<$size; $j++) {
2193          $annotations[$j]->out($this);
2194        };
2195      }
2196
2197      //Pages root
2198      $this->offsets[1] = strlen($this->buffer);
2199      $this->_out('1 0 obj');
2200      $this->_out('<</Type /Pages');
2201
2202      $this->_out('/Kids '.$this->_reference_array($this->_pages));
2203
2204      $this->_out('/Count '.$nb);
2205      $this->_out(sprintf('/MediaBox [0 0 %.2f %.2f]',$wPt,$hPt));
2206      $this->_out('>>');
2207      $this->_out('endobj');
2208
2209      return $pages_start_obj_number;
2210    }
2211
2212    function _putfonts() {
2213      $nf=$this->n;
2214
2215      $num_diffs = count($this->diffs);
2216      for ($i=1; $i<=$num_diffs; $i++) {
2217        $diff = $this->diffs[$i];
2218        $cmap = $this->cmaps[$i];
2219
2220        //Encodings
2221        $this->_newobj();
2222        $this->_out($this->_dictionary(array("Type"         => "/Encoding",
2223                                             "BaseEncoding" => "/WinAnsiEncoding",
2224                                             "Differences"  => $this->_array($diff))));
2225        $this->_out('endobj');
2226
2227        $cmap->out($this);
2228      }
2229
2230      foreach ($this->FontFiles as $file=>$info) {
2231        //Font file embedding
2232        $this->_newobj();
2233        $this->FontFiles[$file]['n'] = $this->n;
2234        $font='';
2235        $f=fopen($this->_getfontpath().$file,'rb',1);
2236        if (!$f) {
2237          $this->Error('Font file not found');
2238        };
2239
2240        while (!feof($f)) { $font.=fread($f,8192); };
2241
2242        fclose($f);
2243        $compressed=(substr($file,-2)=='.z');
2244        if (!$compressed && isset($info['length2'])) {
2245          $header=(ord($font{0})==128);
2246          if($header) {
2247            //Strip first binary header
2248            $font=substr($font,6);
2249          }
2250          if($header && ord($font{$info['length1']})==128) {
2251            //Strip second binary header
2252            $font=substr($font,0,$info['length1']).substr($font,$info['length1']+6);
2253          }
2254        }
2255        $this->_out('<</Length '.strlen($font));
2256
2257        if ($compressed) {
2258          $this->_out('/Filter /FlateDecode');
2259        };
2260
2261        $this->_out('/Length1 '.$info['length1']);
2262        if(isset($info['length2'])) {
2263          $this->_out('/Length2 '.$info['length2'].' /Length3 0');
2264        };
2265        $this->_out('>>');
2266        $this->_putstream($font);
2267        $this->_out('endobj');
2268      }
2269
2270      foreach ($this->fonts as $k=>$font) {
2271        //Font objects
2272        $this->fonts[$k]['n'] = $this->n+1;
2273        $type=$font['type'];
2274        $name=$font['name'];
2275
2276        if ($type=='Type1' || $type=='TrueType') {
2277          //Additional Type1 or TrueType font
2278          $this->_newobj();
2279          $this->_out('<</Type /Font');
2280          $this->_out('/BaseFont /'.$name);
2281          $this->_out('/Subtype /'.$type);
2282          $this->_out('/FirstChar 32 /LastChar 255');
2283          $this->_out('/Widths '.($this->n+1).' 0 R');
2284          $this->_out('/FontDescriptor '.($this->n+2).' 0 R');
2285          if ($font['enc']) {
2286            if(isset($font['diff'])) {
2287              $this->_out('/Encoding '.($nf+$font['diff']).' 0 R');
2288              $this->_out('/ToUnicode '.($this->_reference($this->cmaps[$font['diff']])));
2289            } else {
2290              $this->_out('/Encoding /WinAnsiEncoding');
2291            };
2292          }
2293          $this->_out('>>');
2294          $this->_out('endobj');
2295
2296          //Widths
2297          $this->_newobj();
2298          $cw = &$font['cw'];
2299          $s='[';
2300          for ($i=32;$i<=255;$i++) {
2301            $s.=$cw[chr($i)].' ';
2302          };
2303          $this->_out($s.']');
2304          $this->_out('endobj');
2305
2306          /**
2307           * Font descriptor
2308           */
2309          $this->_newobj();
2310          $fontDescriptor = array('Type'        => '/FontDescriptor',
2311                                  'FontName'    => '/'.$name,
2312                                  'Flags'       => $font['desc']['Flags'],
2313                                  'FontBBox'    => $font['desc']['FontBBox'],
2314                                  'ItalicAngle' => $font['desc']['ItalicAngle'],
2315                                  'Ascent'      => $font['desc']['Ascent'],
2316                                  'Descent'     => $font['desc']['Descent'],
2317                                  'CapHeight'   => $font['desc']['CapHeight'],
2318                                  'StemV'       => $font['desc']['StemV']
2319                                  );
2320          if ($font['file'] != "") {
2321            $fontDescriptor['FontFile'.($type=='Type1' ? '' : '2')] =
2322              $this->FontFiles[$font['file']]['n'].' 0 R';
2323          };
2324          $this->_out($this->_dictionary($fontDescriptor));
2325          $this->_out('endobj');
2326
2327        } else {
2328          //Allow for additional types
2329          $mtd='_put'.strtolower($type);
2330          if(!method_exists($this,$mtd))
2331            $this->Error('Unsupported font type: '.$type);
2332          $this->$mtd($font);
2333        }
2334      }
2335    }
2336
2337    function _putimages() {
2338      $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
2339      reset($this->images);
2340      while (list($file,$info) = each($this->images)) {
2341        $this->_newobj();
2342        $this->images[$file]['n']=$this->n;
2343        $this->_out('<</Type /XObject');
2344        $this->_out('/Subtype /Image');
2345        $this->_out('/Width '.$info['w']);
2346        $this->_out('/Height '.$info['h']);
2347        if ($info['cs']=='Indexed') {
2348          $this->_out('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]');
2349        } else {
2350          $this->_out('/ColorSpace /'.$info['cs']);
2351          if($info['cs']=='DeviceCMYK') {
2352            $this->_out('/Decode [1 0 1 0 1 0 1 0]');
2353          };
2354        }
2355        $this->_out('/BitsPerComponent '.$info['bpc']);
2356        if (isset($info['f'])) {
2357          $this->_out('/Filter /'.$info['f']);
2358        };
2359
2360        if(isset($info['parms'])) {
2361          $this->_out($info['parms']);
2362        };
2363
2364        if(isset($info['trns']) && is_array($info['trns'])) {
2365          $trns='';
2366          for ($i=0;$i<count($info['trns']);$i++) {
2367            $trns.=$info['trns'][$i].' '.$info['trns'][$i].' ';
2368          };
2369          $this->_out('/Mask ['.$trns.']');
2370        };
2371
2372        $this->_out('/Length '.strlen($info['data']).'>>');
2373        $this->_putstream($info['data']);
2374        unset($this->images[$file]['data']);
2375        $this->_out('endobj');
2376
2377        // Palette
2378        if ($info['cs']=='Indexed') {
2379          $this->_newobj();
2380          $pal=($this->compress) ? gzcompress($info['pal']) : $info['pal'];
2381          $this->_out('<<'.$filter.'/Length '.strlen($pal).'>>');
2382          $this->_putstream($pal);
2383          $this->_out('endobj');
2384        };
2385      }
2386    }
2387
2388    function _putxobjectdict() {
2389      foreach ($this->images as $image) {
2390        $this->_out('/I'.$image['i'].' '.$image['n'].' 0 R');
2391      };
2392    }
2393
2394    function _putresourcedict() {
2395      $this->_out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
2396      $this->_out('/Font <<');
2397      foreach ($this->fonts as $font) {
2398        $this->_out('/F'.$font['i'].' '.$font['n'].' 0 R');
2399      };
2400      $this->_out('>>');
2401      $this->_out('/XObject <<');
2402      $this->_putxobjectdict();
2403      $this->_out('>>');
2404    }
2405
2406    function _putresources() {
2407      $this->_putfonts();
2408      $this->_putimages();
2409
2410      //Resource dictionary
2411      $this->offsets[2]=strlen($this->buffer);
2412      $this->_out('2 0 obj');
2413      $this->_out('<<');
2414      $this->_putresourcedict();
2415      $this->_out('>>');
2416      $this->_out('endobj');
2417    }
2418
2419    function _putinfo() {
2420      $this->_out('/Producer '.$this->_textstring('FPDF '.FPDF_VERSION));
2421
2422      if (!empty($this->title)) {
2423        $this->_out('/Title '.$this->_textstring($this->title));
2424      };
2425
2426      if (!empty($this->subject)) {
2427        $this->_out('/Subject '.$this->_textstring($this->subject));
2428      };
2429
2430      if (!empty($this->author)) {
2431        $this->_out('/Author '.$this->_textstring($this->author));
2432      };
2433
2434      if (!empty($this->keywords)) {
2435        $this->_out('/Keywords '.$this->_textstring($this->keywords));
2436      };
2437
2438      if (!empty($this->creator)) {
2439        $this->_out('/Creator '.$this->_textstring($this->creator));
2440      };
2441
2442      $this->_out('/CreationDate '.$this->_textstring('D:'.date('YmdHis')));
2443    }
2444
2445    // Generate the document catalog entry of PDF file
2446    function _putcatalog($pages_start_obj_number) {
2447      $this->_out('/Type /Catalog');
2448
2449      $this->_out('/Pages 1 0 R');
2450      if ($this->ZoomMode=='fullpage') {
2451        $this->_out("/OpenAction [$pages_start_obj_number 0 R /Fit]");
2452      } elseif ($this->ZoomMode=='fullwidth') {
2453        $this->_out("/OpenAction [$pages_start_obj_number 0 R /FitH null]");
2454      } elseif ($this->ZoomMode=='real') {
2455        $this->_out("/OpenAction [$pages_start_obj_number 0 R /XYZ null null 1]");
2456      } elseif (!is_string($this->ZoomMode)) {
2457        $this->_out("/OpenAction [$pages_start_obj_number 0 R /XYZ null null ".($this->ZoomMode/100).']');
2458      };
2459
2460      if ($this->LayoutMode=='single') {
2461        $this->_out('/PageLayout /SinglePage');
2462      } elseif ($this->LayoutMode=='continuous') {
2463        $this->_out('/PageLayout /OneColumn');
2464      } elseif ($this->LayoutMode=='two') {
2465        $this->_out('/PageLayout /TwoColumnLeft');
2466      };
2467
2468      if (count($this->_forms) > 0) {
2469        $this->_out('/AcroForm <<');
2470        $this->_out('/Fields '.$this->_reference_array($this->_forms));
2471        $this->_out('/DR 2 0 R');
2472        $this->_out('/NeedAppearances true');
2473        $this->_out('>>');
2474      };
2475    }
2476
2477    function _putheader() {
2478      $this->_out('%PDF-'.$this->PDFVersion);
2479    }
2480
2481    function _puttrailer() {
2482      $this->_out('/Size '.($this->n+1));
2483      $this->_out('/Root '.$this->n.' 0 R');
2484      $this->_out('/Info '.($this->n-1).' 0 R');
2485    }
2486
2487    function _enddoc() {
2488      $this->_putheader();
2489      $pages_start_obj_number = $this->_putpages();
2490
2491      $this->_putresources();
2492
2493      //Info
2494      $this->_newobj();
2495      $this->_out('<<');
2496      $this->_putinfo();
2497      $this->_out('>>');
2498      $this->_out('endobj');
2499
2500      // Form fields
2501      for ($i=0; $i<count($this->_forms); $i++) {
2502        $form =& $this->_forms[$i];
2503
2504        $form->out($this);
2505      };
2506
2507      //Catalog
2508      $this->_newobj();
2509      $this->_out('<<');
2510      $this->_putcatalog($pages_start_obj_number);
2511      $this->_out('>>');
2512      $this->_out('endobj');
2513
2514      //Cross-ref
2515      $o=strlen($this->buffer);
2516      $this->_out('xref');
2517      $this->_out('0 '.($this->n+1));
2518      $this->_out('0000000000 65535 f ');
2519
2520      for ($i=1; $i<=$this->n; $i++) {
2521        $this->_out(sprintf('%010d 00000 n ',$this->offsets[$i]));
2522      };
2523
2524      //Trailer
2525      $this->_out('trailer');
2526      $this->_out('<<');
2527      $this->_puttrailer();
2528      $this->_out('>>');
2529      $this->_out('startxref');
2530      $this->_out($o);
2531      $this->_out('%%EOF');
2532      $this->state = FPDF_STATE_COMPLETED;
2533    }
2534
2535    function _beginpage() {
2536      $this->page++;
2537      $this->pages[$this->page]='';
2538      $this->state = FPDF_STATE_PAGE_STARTED;
2539      $this->FontFamily='';
2540    }
2541
2542    /**
2543     * End of page contents
2544     */
2545    function _endpage() {
2546      $this->state = FPDF_STATE_DOCUMENT_STARTED;
2547    }
2548
2549    /**
2550     * Start a new indirect object
2551     */
2552    function _newobj() {
2553      $num = $this->_generate_new_object_number();
2554      $this->offsets[$num]=strlen($this->buffer);
2555      $this->_out($num.' 0 obj');
2556    }
2557
2558    // Extract info from a JPEG file
2559    function _parsejpg($file) {
2560      $size_info = GetImageSize($file);
2561      if (!$size_info) {
2562        $this->Error('Missing or incorrect image file: '.$file);
2563      };
2564
2565      if ($size_info[2]!=2) {
2566        $this->Error('Not a JPEG file: '.$file);
2567      };
2568
2569      if (!isset($size_info['channels']) || $size_info['channels']==3) {
2570        $colspace='DeviceRGB';
2571      } elseif($size_info['channels']==4) {
2572        $colspace='DeviceCMYK';
2573      } else {
2574        $colspace='DeviceGray';
2575      };
2576
2577      $bpc = isset($size_info['bits']) ? $size_info['bits'] : 8;
2578
2579      //Read whole file
2580      $f=fopen($file,'rb');
2581      $data='';
2582      while (!feof($f)) {
2583        $data .= fread($f, 4096);
2584      };
2585      fclose($f);
2586
2587      return array('w' => $size_info[0],
2588                   'h' => $size_info[1],
2589                   'cs' => $colspace,
2590                   'bpc' => $bpc,
2591                   'f' => 'DCTDecode',
2592                   'data' => $data);
2593    }
2594
2595    // Extract info from a PNG file
2596    function _parsepng($file) {
2597      $f = fopen($file,'rb');
2598      if (!$f) {
2599        $this->Error('Can\'t open image file: '.$file);
2600      };
2601
2602      //Check signature
2603      if (fread($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
2604        $this->Error('Not a PNG file: '.$file);
2605      };
2606
2607      //Read header chunk
2608      fread($f,4);
2609      if (fread($f,4)!='IHDR') {
2610        $this->Error('Incorrect PNG file: '.$file);
2611      };
2612
2613      $w = $this->_freadint($f);
2614      $h = $this->_freadint($f);
2615      $bpc = ord(fread($f,1));
2616
2617      if ($bpc>8) {
2618        $this->Error('16-bit depth not supported: '.$file);
2619      };
2620
2621      $ct=ord(fread($f,1));
2622      if ($ct==0) {
2623        $colspace='DeviceGray';
2624      } elseif($ct==2) {
2625        $colspace='DeviceRGB';
2626      } elseif($ct==3) {
2627        $colspace='Indexed';
2628      } else {
2629        $this->Error('Alpha channel not supported: '.$file);
2630      };
2631
2632      if (ord(fread($f,1))!=0) {
2633        $this->Error('Unknown compression method: '.$file);
2634      };
2635
2636      if (ord(fread($f,1))!=0) {
2637        $this->Error('Unknown filter method: '.$file);
2638      };
2639
2640      if (ord(fread($f,1))!=0) {
2641        $this->Error('Interlacing not supported: '.$file);
2642      };
2643
2644      fread($f,4);
2645      $parms='/DecodeParms <</Predictor 15 /Colors '.($ct==2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.'>>';
2646
2647      //Scan chunks looking for palette, transparency and image data
2648      $pal='';
2649      $trns='';
2650      $data='';
2651      do {
2652        $n=$this->_freadint($f);
2653        $type=fread($f,4);
2654        if ($type=='PLTE') {
2655          //Read palette
2656          $pal=fread($f,$n);
2657          fread($f,4);
2658        } elseif($type=='tRNS') {
2659          //Read transparency info
2660          $t=fread($f,$n);
2661          if ($ct==0) {
2662            $trns=array(ord(substr($t,1,1)));
2663          } elseif($ct==2) {
2664            $trns=array(ord(substr($t,1,1)),ord(substr($t,3,1)),ord(substr($t,5,1)));
2665          } else {
2666            $pos=strpos($t,chr(0));
2667            if ($pos!==false) {
2668              $trns=array($pos);
2669            }
2670          }
2671          fread($f,4);
2672        } elseif ($type=='IDAT') {
2673          //Read image data block
2674          $data.=fread($f,$n);
2675          fread($f,4);
2676        } elseif ($type=='IEND') {
2677          break;
2678        } else {
2679          fread($f,$n+4);
2680        };
2681      } while($n);
2682
2683      if ($colspace=='Indexed' && empty($pal)) {
2684        $this->Error('Missing palette in '.$file);
2685      };
2686      fclose($f);
2687      return array('w'     => $w,
2688                   'h'     => $h,
2689                   'cs'    => $colspace,
2690                   'bpc'   => $bpc,
2691                   'f'     => 'FlateDecode',
2692                   'parms' => $parms,
2693                   'pal'   => $pal,
2694                   'trns'  => $trns,
2695                   'data'  => $data);
2696    }
2697
2698    /**
2699     * Read a 4-byte integer from file
2700     */
2701    function _freadint($f) {
2702      $a=unpack('Ni',fread($f,4));
2703      return $a['i'];
2704    }
2705
2706    /**
2707     * Format a text string
2708     */
2709    function _textstring($s) {
2710      return '('.$this->_escape($s).')';
2711    }
2712
2713    /**
2714     * Add \ before \, ( and )
2715     */
2716    function _escape($s) {
2717      return str_replace(')','\\)',str_replace('(','\\(',str_replace('\\','\\\\',$s)));
2718    }
2719
2720    function _putstream($s) {
2721      $this->_out('stream');
2722      $this->_out($s);
2723      $this->_out('endstream');
2724    }
2725
2726    /**
2727     * Add a line to the document
2728     */
2729    function _out($s) {
2730      if ($this->state == FPDF_STATE_PAGE_STARTED) {
2731        $this->pages[$this->page].=$s."\n";
2732      } else {
2733        $this->buffer.=$s."\n";
2734      }
2735    }
2736  }
2737}
2738?>
2739