/[drupal]/contributions/modules/graphstat/phplot.php
ViewVC logotype

Diff of /contributions/modules/graphstat/phplot.php

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.2.2.1, Wed Nov 14 22:40:52 2007 UTC revision 1.2.2.2, Sun Jan 25 17:33:59 2009 UTC
# Line 1  Line 1 
1  <?php  <?php
2  // $Id: phplot.php,v 1.2 2007/11/14 22:29:56 profix898 Exp $  // $Id: phplot.php,v 1.2.2.1 2007/11/14 22:40:52 profix898 Exp $
3    
4  /*  /*
5   * PHPLOT Version 5.0.4   * PHPLOT Version 5.0.6
6   * Copyright (C) 1998-2007 Afan Ottenheimer.  Released under   * Copyright (C) 1998-2009 Afan Ottenheimer.  Released under
7   * the GPL and PHP licenses as stated in the README file which should   * the GPL and PHP licenses as stated in the the README file which should
8   * have been included with this document.   * have been included with this document.
9   *   *
10   * Co-author and maintainer (2003-2005)   * Co-author and maintainer (2003-2005)
# Line 16  Line 16 
16   * Visit http://sourceforge.net/projects/phplot/   * Visit http://sourceforge.net/projects/phplot/
17   * for PHPlot documentation, downloads, and discussions.   * for PHPlot documentation, downloads, and discussions.
18   *   *
19   * Requires PHP 4.3.0 or later. PHP 5.2.x or later is recommended.   * Requires PHP 5.2.x or later. (PHP 4 is unsupported as of Jan 2008)
20   */   */
21    
22  class PHPlot {  class PHPlot {
# Line 42  class PHPlot { Line 42  class PHPlot {
42      var $yscale_type = 'linear';      var $yscale_type = 'linear';
43    
44  //Fonts  //Fonts
45      var $use_ttf  = FALSE;                  // Use True Type Fonts?      var $use_ttf  = FALSE;                  // Use True Type Fonts by default?
46      var $ttf_path = '.';                    // Default path to look in for TT Fonts.      var $ttf_path = '.';                    // Default path to look in for TT Fonts.
47      var $default_ttfont = 'benjamingothic.ttf';      var $default_ttfont = 'benjamingothic.ttf';
48      var $line_spacing = 4;                  // Pixels between lines.      var $line_spacing = 4;                  // Controls line spacing of multi-line labels
49    
50      // Font angles: 0 or 90 degrees for fixed fonts, any for TTF      // Font angles: 0 or 90 degrees for fixed fonts, any for TTF
51      var $x_label_angle = 0;                 // For labels on X axis (tick and data)      var $x_label_angle = 0;                 // For labels on X axis (tick and data)
52      var $y_label_angle = 0;                 // For labels on Y axis (tick and data)      var $y_label_angle = 0;                 // For labels on Y axis (tick and data)
     var $x_title_angle = 0;                 // Don't change this if you don't want to screw things up!  
     var $y_title_angle = 90;                // Nor this.  
     var $title_angle = 0;                   // Or this.  
53    
54  //Formats  //Formats
55      var $file_format = 'png';      var $file_format = 'png';
# Line 62  class PHPlot { Line 59  class PHPlot {
59      var $data_type = 'text-data';           // text-data, data-data-error, data-data, text-data-single      var $data_type = 'text-data';           // text-data, data-data-error, data-data, text-data-single
60      var $plot_type= 'linepoints';           // bars, lines, linepoints, area, points, pie, thinbarline, squared      var $plot_type= 'linepoints';           // bars, lines, linepoints, area, points, pie, thinbarline, squared
61    
62      var $label_scale_position = 0.5;        // Shifts data labes in pie charts. 1 = top, 0 = bottom      var $label_scale_position = 0.5;        // Shifts data labels in pie charts. 1 = top, 0 = bottom
63      var $group_frac_width = 0.7;            // Bars use this fraction (0 to 1) of a group's space      var $group_frac_width = 0.7;            // Bars use this fraction (0 to 1) of a group's space
64      var $bar_extra_space = 0.5;             // Number of extra bar's worth of space in a group      var $bar_extra_space = 0.5;             // Number of extra bar's worth of space in a group
65      var $bar_width_adjust = 1;              // 1 = bars of normal width, must be > 0      var $bar_width_adjust = 1;              // 1 = bars of normal width, must be > 0
66    
     var $y_precision = 1;  
     var $x_precision = 1;  
   
     var $data_units_text = '';              // Units text for 'data' labels (i.e: '¤', '$', etc.)  
   
67  // Titles  // Titles
68      var $title_txt = '';      var $title_txt = '';
69    
# Line 101  class PHPlot { Line 93  class PHPlot {
93      var $draw_x_data_label_lines = FALSE;   // Draw a line from the data point to the axis?      var $draw_x_data_label_lines = FALSE;   // Draw a line from the data point to the axis?
94      var $draw_y_data_label_lines = FALSE;   // TODO      var $draw_y_data_label_lines = FALSE;   // TODO
95    
96      // Label types: (for tick, data and plot labels)      // Label format controls: (for tick, data and plot labels)
97      var $x_label_type = '';                 // data, time. Leave blank for no formatting.      // Unset by default, these array members are used as needed for 'x' and 'y':
98      var $y_label_type = '';                 // data, time. Leave blank for no formatting.      //    type, precision, prefix, suffix, time_format, printf_format, custom_callback, custom_arg.
99      var $x_time_format = '%H:%M:%S';        // See http://www.php.net/manual/html/function.strftime.html      // These replace the former: x_label_type, x_time_format, x_precision (similar for y), data_units_text.
100      var $y_time_format = '%H:%M:%S';        // SetYTimeFormat() too...      var $label_format = array('x' => array(), 'y' => array());
101        // data_units_text is retained for backward compatibility, because there was never a function
102        // to set it. Use the 'suffix' argument to Set[XY]LabelType instead.
103        var $data_units_text = '';              // Units text for 'data' labels (i.e: '¤', '$', etc.)
104    
105      // Skipping labels      // Skipping labels
106      var $x_label_inc = 1;                   // Draw a label every this many (1 = all) (TODO)      // var $x_label_inc = 1;                   // Draw a label every this many (1 = all) (TODO)
107      var $y_label_inc = 1;      // var $y_label_inc = 1;
108      var $_x_label_cnt = 0;                  // internal count FIXME: work in progress      // var $_x_label_cnt = 0;                  // internal count FIXME: work in progress
109    
110  // Legend  // Legend
111      var $legend = '';                       // An array with legend titles      var $legend = '';                       // An array with legend titles
# Line 194  class PHPlot { Line 189  class PHPlot {
189          'draw_graph' => NULL,          'draw_graph' => NULL,
190          'draw_border' => NULL,          'draw_border' => NULL,
191          'draw_legend' => NULL,          'draw_legend' => NULL,
192            'debug_textbox' => NULL,  // For testing/debugging text box alignment
193            'debug_scale' => NULL,    // For testing/debugging scale setup
194      );      );
195    
196    
# Line 207  class PHPlot { Line 204  class PHPlot {
204       * \param which_width       int    Image width in pixels.       * \param which_width       int    Image width in pixels.
205       * \param which_height      int    Image height in pixels.       * \param which_height      int    Image height in pixels.
206       * \param which_output_file string Filename for output.       * \param which_output_file string Filename for output.
207       * \param which_input_fule  string Path to a file to be used as background.       * \param which_input_file  string Path to a file to be used as background.
208       */       */
209      function PHPlot($which_width=600, $which_height=400, $which_output_file=NULL, $which_input_file=NULL)      function PHPlot($which_width=600, $which_height=400, $which_output_file=NULL, $which_input_file=NULL)
210      {      {
211          $this->SetRGBArray($this->color_array);          $this->SetRGBArray($this->color_array);
212    
213          $this->background_done = FALSE;     // Set to TRUE after background image is drawn once          $this->background_done = FALSE;     // TRUE after background image is drawn once
214            $this->plot_margins_set = FALSE;    // TRUE with user-set plot area or plot margins.
215    
216          if ($which_output_file)          if ($which_output_file)
217              $this->SetOutputFile($which_output_file);              $this->SetOutputFile($which_output_file);
# Line 226  class PHPlot { Line 224  class PHPlot {
224    
225              $this->img = ImageCreate($this->image_width, $this->image_height);              $this->img = ImageCreate($this->image_width, $this->image_height);
226              if (! $this->img)              if (! $this->img)
227                  $this->PrintError('PHPlot(): Could not create image resource.');                  return $this->PrintError('PHPlot(): Could not create image resource.');
228    
229          }          }
230    
# Line 273  class PHPlot { Line 271  class PHPlot {
271              $error = "Failed to read image file $image_filename";              $error = "Failed to read image file $image_filename";
272          }          }
273          if (!empty($error)) {          if (!empty($error)) {
274              $this->PrintError($error);              return $this->PrintError("GetImage(): $error");
275          }          }
276          $width = $size[0];          $width = $size[0];
277          $height = $size[1];          $height = $size[1];
# Line 311  class PHPlot { Line 309  class PHPlot {
309       * Returns an index to a color passed in as anything (string, hex, rgb)       * Returns an index to a color passed in as anything (string, hex, rgb)
310       *       *
311       * \param which_color * Color (can be '#AABBCC', 'Colorname', or array(r,g,b))       * \param which_color * Color (can be '#AABBCC', 'Colorname', or array(r,g,b))
312         * Returns a GD color index (integer >= 0), or NULL on error.
313       */       */
314      function SetIndexColor($which_color)      function SetIndexColor($which_color)
315      {      {
316          list ($r, $g, $b) = $this->SetRGBColor($which_color);  //Translate to RGB          list ($r, $g, $b) = $this->SetRGBColor($which_color);  //Translate to RGB
317            if (!isset($r)) return NULL;
318          return ImageColorResolve($this->img, $r, $g, $b);          return ImageColorResolve($this->img, $r, $g, $b);
319      }      }
320    
321    
322      /*!      /*!
323       * Returns an index to a slightly darker color than the one requested.       * Returns an index to a slightly darker color than the one requested.
324         * Returns a GD color index (integer >= 0), or NULL on error.
325       */       */
326      function SetIndexDarkColor($which_color)      function SetIndexDarkColor($which_color)
327      {      {
328          list ($r, $g, $b) = $this->SetRGBColor($which_color);          list ($r, $g, $b) = $this->SetRGBColor($which_color);
329            if (!isset($r)) return NULL;
330          $r = max(0, $r - 0x30);          $r = max(0, $r - 0x30);
331          $g = max(0, $g - 0x30);          $g = max(0, $g - 0x30);
332          $b = max(0, $b - 0x30);          $b = max(0, $b - 0x30);
# Line 368  class PHPlot { Line 370  class PHPlot {
370          $this->SetDataColors();          $this->SetDataColors();
371          $this->SetErrorBarColors();          $this->SetErrorBarColors();
372          $this->SetDataBorderColors();          $this->SetDataBorderColors();
373            return TRUE;
374      }      }
375    
376    
# Line 378  class PHPlot { Line 381  class PHPlot {
381      {      {
382          $this->bg_color= $which_color;          $this->bg_color= $which_color;
383          $this->ndx_bg_color= $this->SetIndexColor($this->bg_color);          $this->ndx_bg_color= $this->SetIndexColor($this->bg_color);
384          return TRUE;          return isset($this->ndx_bg_color);
385      }      }
386    
387      /*      /*
# Line 388  class PHPlot { Line 391  class PHPlot {
391      {      {
392          $this->plot_bg_color= $which_color;          $this->plot_bg_color= $which_color;
393          $this->ndx_plot_bg_color= $this->SetIndexColor($this->plot_bg_color);          $this->ndx_plot_bg_color= $this->SetIndexColor($this->plot_bg_color);
394          return TRUE;          return isset($this->ndx_plot_bg_color);
395      }      }
396    
397     /*     /*
# Line 398  class PHPlot { Line 401  class PHPlot {
401      {      {
402          $this->title_color= $which_color;          $this->title_color= $which_color;
403          $this->ndx_title_color= $this->SetIndexColor($this->title_color);          $this->ndx_title_color= $this->SetIndexColor($this->title_color);
404          return TRUE;          return isset($this->ndx_title_color);
405      }      }
406    
407      /*      /*
# Line 408  class PHPlot { Line 411  class PHPlot {
411      {      {
412          $this->tick_color= $which_color;          $this->tick_color= $which_color;
413          $this->ndx_tick_color= $this->SetIndexColor($this->tick_color);          $this->ndx_tick_color= $this->SetIndexColor($this->tick_color);
414          return TRUE;          return isset($this->ndx_tick_color);
415      }      }
416    
417    
418      /*      /*
419       *       * Do not use. Use SetTitleColor instead.
420       */       */
421      function SetLabelColor ($which_color)      function SetLabelColor ($which_color)
422      {      {
423          $this->label_color = $which_color;          $this->label_color = $which_color;
424          $this->ndx_title_color= $this->SetIndexColor($this->label_color);          $this->ndx_title_color= $this->SetIndexColor($this->label_color);
425          return TRUE;          return isset($this->ndx_title_color);
426      }      }
427    
428    
# Line 430  class PHPlot { Line 433  class PHPlot {
433      {      {
434          $this->text_color= $which_color;          $this->text_color= $which_color;
435          $this->ndx_text_color= $this->SetIndexColor($this->text_color);          $this->ndx_text_color= $this->SetIndexColor($this->text_color);
436          return TRUE;          return isset($this->ndx_text_color);
437      }      }
438    
439    
# Line 441  class PHPlot { Line 444  class PHPlot {
444      {      {
445          $this->light_grid_color= $which_color;          $this->light_grid_color= $which_color;
446          $this->ndx_light_grid_color= $this->SetIndexColor($this->light_grid_color);          $this->ndx_light_grid_color= $this->SetIndexColor($this->light_grid_color);
447          return TRUE;          return isset($this->ndx_light_grid_color);
448      }      }
449    
450    
# Line 452  class PHPlot { Line 455  class PHPlot {
455      {      {
456          $this->grid_color = $which_color;          $this->grid_color = $which_color;
457          $this->ndx_grid_color= $this->SetIndexColor($this->grid_color);          $this->ndx_grid_color= $this->SetIndexColor($this->grid_color);
458          return TRUE;          return isset($this->ndx_grid_color);
459      }      }
460    
461    
# Line 464  class PHPlot { Line 467  class PHPlot {
467          $this->i_border = $which_color;          $this->i_border = $which_color;
468          $this->ndx_i_border = $this->SetIndexColor($this->i_border);          $this->ndx_i_border = $this->SetIndexColor($this->i_border);
469          $this->ndx_i_border_dark = $this->SetIndexDarkColor($this->i_border);          $this->ndx_i_border_dark = $this->SetIndexDarkColor($this->i_border);
470          return TRUE;          return isset($this->ndx_i_border);
471      }      }
472    
473    
# Line 473  class PHPlot { Line 476  class PHPlot {
476       */       */
477      function SetTransparentColor($which_color)      function SetTransparentColor($which_color)
478      {      {
479          ImageColorTransparent($this->img, $this->SetIndexColor($which_color));          $ndx = $this->SetIndexColor($which_color);
480            if (!isset($ndx))
481                return FALSE;
482            ImageColorTransparent($this->img, $ndx);
483          return TRUE;          return TRUE;
484      }      }
485    
# Line 559  class PHPlot { Line 565  class PHPlot {
565          } elseif (isset($this->rgb_array[$color_asked])) {  // Color by name          } elseif (isset($this->rgb_array[$color_asked])) {  // Color by name
566              $ret_val = $this->rgb_array[$color_asked];              $ret_val = $this->rgb_array[$color_asked];
567          } else {          } else {
568              $this->DrawError("SetRGBColor(): Color '$color_asked' is not valid.");              return $this->PrintError("SetRGBColor(): Color '$color_asked' is not valid.");
569          }          }
570          return $ret_val;          return $ret_val;
571      }      }
# Line 580  class PHPlot { Line 586  class PHPlot {
586    
587          $i = 0;          $i = 0;
588          foreach ($this->data_colors as $col) {          foreach ($this->data_colors as $col) {
589              $this->ndx_data_colors[$i] = $this->SetIndexColor($col);              $ndx = $this->SetIndexColor($col);
590                if (!isset($ndx))
591                    return FALSE;
592                $this->ndx_data_colors[$i] = $ndx;
593              $this->ndx_data_dark_colors[$i] = $this->SetIndexDarkColor($col);              $this->ndx_data_dark_colors[$i] = $this->SetIndexDarkColor($col);
594              $i++;              $i++;
595          }          }
596    
597          // For past compatibility:          // For past compatibility:
598          $this->SetDataBorderColors($which_border);          return $this->SetDataBorderColors($which_border);
599      } // function SetDataColors()      } // function SetDataColors()
600    
601    
# Line 606  class PHPlot { Line 615  class PHPlot {
615    
616          $i = 0;          $i = 0;
617          foreach($this->data_border_colors as $col) {          foreach($this->data_border_colors as $col) {
618              $this->ndx_data_border_colors[$i] = $this->SetIndexColor($col);              $ndx = $this->SetIndexColor($col);
619                if (!isset($ndx))
620                    return FALSE;
621                $this->ndx_data_border_colors[$i] = $ndx;
622              $i++;              $i++;
623          }          }
624            return TRUE;
625      } // function SetDataBorderColors()      } // function SetDataBorderColors()
626    
627    
# Line 627  class PHPlot { Line 640  class PHPlot {
640    
641          $i = 0;          $i = 0;
642          foreach($this->error_bar_colors as $col) {          foreach($this->error_bar_colors as $col) {
643              $this->ndx_error_bar_colors[$i] = $this->SetIndexColor($col);              $ndx = $this->SetIndexColor($col);
644                if (!isset($ndx))
645                    return FALSE;
646                $this->ndx_error_bar_colors[$i] = $ndx;
647              $i++;              $i++;
648          }          }
649          return TRUE;          return TRUE;
   
650      } // function SetErrorBarColors()      } // function SetErrorBarColors()
651    
652    
# Line 647  class PHPlot { Line 662  class PHPlot {
662          $asked = explode('-', $which_style);          $asked = explode('-', $which_style);
663    
664          if (count($asked) < 2) {          if (count($asked) < 2) {
665              $this->DrawError("SetDefaultDashedStyle(): Wrong parameter '$which_style'.");              return $this->PrintError("SetDefaultDashedStyle(): Wrong parameter '$which_style'.");
             return FALSE;  
666          }          }
667    
668          // Build the string to be eval()uated later by SetDashedStyle()          // Build the string to be eval()uated later by SetDashedStyle()
# Line 717  class PHPlot { Line 731  class PHPlot {
731    
732    
733  /////////////////////////////////////////////  /////////////////////////////////////////////
734  //////////////                          FONTS  //////////////                 TEXT and FONTS
735  /////////////////////////////////////////////  /////////////////////////////////////////////
736    
737    
738      /*!      /*!
739       * Sets number of pixels between lines of the same text.       * Controls the line spacing of multi-line labels.
740         * For GD text, this is the number of pixels between lines.
741         * For TTF text, it controls line spacing in proportion to the normal
742         * spacing defined by the font.
743       */       */
744      function SetLineSpacing($which_spc)      function SetLineSpacing($which_spc)
745      {      {
746          $this->line_spacing = $which_spc;          $this->line_spacing = $which_spc;
747            return TRUE;
748      }      }
749    
750    
751      /*!      /*!
752       * Enables use of TrueType fonts in the graph. Font initialisation methods       * Select the default font type to use.
753       * depend on this setting, so when called, SetUseTTF() resets the font       *   $which_ttf : True to default to TrueType, False to default to GD (fixed) fonts.
754       * settings       * This also resets all font settings to the defaults.
755       */       */
756      function SetUseTTF($which_ttf)      function SetUseTTF($which_ttf)
757      {      {
758          $this->use_ttf = $which_ttf;          $this->use_ttf = $which_ttf;
759          $this->SetDefaultFonts();          return $this->SetDefaultFonts();
         return TRUE;  
760      }      }
761    
762      /*!      /*!
# Line 753  class PHPlot { Line 770  class PHPlot {
770          if (is_dir($which_path) && is_readable($which_path)) {          if (is_dir($which_path) && is_readable($which_path)) {
771              $this->ttf_path = $which_path;              $this->ttf_path = $which_path;
772              return TRUE;              return TRUE;
         } else {  
             $this->PrintError("SetTTFPath(): $which_path is not a valid path.");  
             return FALSE;  
773          }          }
774            return $this->PrintError("SetTTFPath(): $which_path is not a valid path.");
775      }      }
776    
777      /*!      /*!
778       * Sets the default TrueType font and updates all fonts to that.       * Sets the default TrueType font and updates all fonts to that.
779       * The default font might be a full path, or relative to the TTFPath,       * The default font might be a full path, or relative to the TTFPath,
780       * so let SetFont check that it exists.       * so let SetFont check that it exists.
781       * Side effect: enable use of TrueType fonts.       * Side effects: Enables use of TrueType fonts as the default font type,
782         * and resets all font settings.
783       */       */
784      function SetDefaultTTFont($which_font)      function SetDefaultTTFont($which_font)
785      {      {
786          $this->default_ttfont = $which_font;          $this->default_ttfont = $which_font;
787          $this->SetUseTTF(TRUE);          return $this->SetUseTTF(TRUE);
         return TRUE;  
788      }      }
789    
790      /*!      /*!
# Line 779  class PHPlot { Line 794  class PHPlot {
794      {      {
795          // TTF:          // TTF:
796          if ($this->use_ttf) {          if ($this->use_ttf) {
797              $this->SetFont('generic', '', 8);              return $this->SetFont('generic', '', 8)
798              $this->SetFont('title', '', 14);                  && $this->SetFont('title', '', 14)
799              $this->SetFont('legend', '', 8);                  && $this->SetFont('legend', '', 8)
800              $this->SetFont('x_label', '', 6);                  && $this->SetFont('x_label', '', 6)
801              $this->SetFont('y_label', '', 6);                  && $this->SetFont('y_label', '', 6)
802              $this->SetFont('x_title', '', 10);                  && $this->SetFont('x_title', '', 10)
803              $this->SetFont('y_title', '', 10);                  && $this->SetFont('y_title', '', 10);
804          }          }
805          // Fixed:          // Fixed GD Fonts:
806          else {          return $this->SetFont('generic', 2)
807              $this->SetFont('generic', 2);              && $this->SetFont('title', 5)
808              $this->SetFont('title', 5);              && $this->SetFont('legend', 2)
809              $this->SetFont('legend', 2);              && $this->SetFont('x_label', 1)
810              $this->SetFont('x_label', 1);              && $this->SetFont('y_label', 1)
811              $this->SetFont('y_label', 1);              && $this->SetFont('x_title', 3)
812              $this->SetFont('x_title', 3);              && $this->SetFont('y_title', 3);
813              $this->SetFont('y_title', 3);      }
         }  
814    
815        /*
816         * Select a fixed (GD) font for an element.
817         * This allows using a fixed font, even with SetUseTTF(True).
818         *    $which_elem : The element whose font is to be changed.
819         *       One of: title legend generic x_label y_label x_title y_title
820         *    $which_font : A GD font number 1-5
821         *    $which_spacing (optional) : Line spacing factor
822         */
823        function SetFontGD($which_elem, $which_font, $which_spacing = NULL)
824        {
825            if ($which_font < 1 || 5 < $which_font) {
826                return $this->PrintError(__FUNCTION__ . ': Font size must be 1, 2, 3, 4 or 5');
827            }
828            if (!$this->CheckOption($which_elem,
829                                    'generic, title, legend, x_label, y_label, x_title, y_title',
830                                    __FUNCTION__)) {
831                return FALSE;
832            }
833    
834            # Store the font parameters: name/size, char cell height and width.
835            $this->fonts[$which_elem] = array('ttf' => FALSE,
836                                              'font' => $which_font,
837                                              'height' => ImageFontHeight($which_font),
838                                              'width' => ImageFontWidth($which_font),
839                                              'line_spacing' => $which_spacing);
840          return TRUE;          return TRUE;
841      }      }
842    
843      /*!      /*
844       * Sets Fixed/Truetype font parameters.       * Select a TrueType font for an element.
845       *  \param $which_elem Is the element whose font is to be changed.       * This allows using a TrueType font, even with SetUseTTF(False).
846       *         It can be one of 'title', 'legend', 'generic',       *    $which_elem : The element whose font is to be changed.
847       *         'x_label', 'y_label', x_title' or 'y_title'       *       One of: title legend generic x_label y_label x_title y_title
848       *  \param $which_font Can be a number (for fixed font sizes) or       *    $which_font : A TrueType font filename or pathname.
849       *         a string with the font pathname or filename when using TTFonts.       *    $which_size : Font point size.
850       *         For TTFonts, an empty string means use the default font.       *    $which_spacing (optional) : Line spacing factor
851       *  \param $which_size Point size (TTF only)       */
852       * Calculates and updates internal height and width variables.      function SetFontTTF($which_elem, $which_font, $which_size = 12, $which_spacing = NULL)
853       */      {
854      function SetFont($which_elem, $which_font, $which_size = 12)          if (!$this->CheckOption($which_elem,
855      {                                  'generic, title, legend, x_label, y_label, x_title, y_title',
856          // TTF:                                  __FUNCTION__)) {
857          if ($this->use_ttf) {              return FALSE;
858              // Empty font name means use the default font.          }
             if (empty($which_font))  
                 $which_font = $this->default_ttfont;  
             $path = $which_font;  
859    
860              // First try the font name directly, if not then try with path.          # Empty font name means use the default font.
861            if (empty($which_font))
862                $which_font = $this->default_ttfont;
863            $path = $which_font;
864    
865            # First try the font name directly, if not then try with path.
866            if (!is_file($path) || !is_readable($path)) {
867                $path = $this->ttf_path . DIRECTORY_SEPARATOR . $which_font;
868              if (!is_file($path) || !is_readable($path)) {              if (!is_file($path) || !is_readable($path)) {
869                  $path = $this->ttf_path . '/' . $which_font;                  return $this->PrintError(__FUNCTION__ . ": Can't find TrueType font $which_font");
                 if (!is_file($path) || !is_readable($path)) {  
                     $this->DrawError("SetFont(): Can't find TrueType font $which_font");  
                     return FALSE;  
                 }  
             }  
   
             switch ($which_elem) {  
             case 'generic':  
                 $this->generic_font['font'] = $path;  
                 $this->generic_font['size'] = $which_size;  
                 break;  
             case 'title':  
                 $this->title_font['font'] = $path;  
                 $this->title_font['size'] = $which_size;  
                 break;  
             case 'legend':  
                 $this->legend_font['font'] = $path;  
                 $this->legend_font['size'] = $which_size;  
                 break;  
             case 'x_label':  
                 $this->x_label_font['font'] = $path;  
                 $this->x_label_font['size'] = $which_size;  
                 break;  
             case 'y_label':  
                 $this->y_label_font['font'] = $path;  
                 $this->y_label_font['size'] = $which_size;  
                 break;  
             case 'x_title':  
                 $this->x_title_font['font'] = $path;  
                 $this->x_title_font['size'] = $which_size;  
                 break;  
             case 'y_title':  
                 $this->y_title_font['font'] = $path;  
                 $this->y_title_font['size'] = $which_size;  
                 break;  
             default:  
                 $this->DrawError("SetFont(): Unknown element '$which_elem' specified.");  
                 return FALSE;  
870              }              }
             return TRUE;  
   
871          }          }
872    
873          // Fixed fonts:          # Calculate the font height and inherent line spacing. TrueType fonts have this information
874          if ($which_font > 5 || $which_font < 0) {          # internally, but PHP/GD has no way to directly access it. So get the bounding box size of
875              $this->DrawError('SetFont(): Non-TTF font size must be 1, 2, 3, 4 or 5');          # an upper-case character without descenders, and the baseline-to-baseline height.
876              return FALSE;          # Note: In practice, $which_size = $height, maybe +/-1 . But which_size is in points,
877          }          # and height is in pixels, and someday GD may be able to tell the difference.
878            # The character width is saved too, but not used by the normal text drawing routines - it
879          switch ($which_elem) {          # isn't necessarily a fixed-space font. It is used in DrawLegend.
880          case 'generic':          $bbox = ImageTTFBBox($which_size, 0, $path, "E");
881              $this->generic_font['font'] = $which_font;          $height = $bbox[1] - $bbox[5];
882              $this->generic_font['height'] = ImageFontHeight($which_font);          $width = $bbox[2] - $bbox[0];
883              $this->generic_font['width'] = ImageFontWidth($which_font);          $bbox = ImageTTFBBox($which_size, 0, $path, "E\nE");
884              break;          $spacing = $bbox[1] - $bbox[5] - 2 * $height;
885          case 'title':  
886             $this->title_font['font'] = $which_font;          # Store the font parameters:
887             $this->title_font['height'] = ImageFontHeight($which_font);          $this->fonts[$which_elem] = array('ttf' => TRUE,
888             $this->title_font['width'] = ImageFontWidth($which_font);                                            'font' => $path,
889             break;                                            'size' => $which_size,
890          case 'legend':                                            'height' => $height,
891              $this->legend_font['font'] = $which_font;                                            'width' => $width,
892              $this->legend_font['height'] = ImageFontHeight($which_font);                                            'spacing' => $spacing,
893              $this->legend_font['width'] = ImageFontWidth($which_font);                                            'line_spacing' => $which_spacing);
             break;  
         case 'x_label':  
             $this->x_label_font['font'] = $which_font;  
             $this->x_label_font['height'] = ImageFontHeight($which_font);  
             $this->x_label_font['width'] = ImageFontWidth($which_font);  
             break;  
         case 'y_label':  
             $this->y_label_font['font'] = $which_font;  
             $this->y_label_font['height'] = ImageFontHeight($which_font);  
             $this->y_label_font['width'] = ImageFontWidth($which_font);  
             break;  
         case 'x_title':  
             $this->x_title_font['font'] = $which_font;  
             $this->x_title_font['height'] = ImageFontHeight($which_font);  
             $this->x_title_font['width'] = ImageFontWidth($which_font);  
             break;  
         case 'y_title':  
             $this->y_title_font['font'] = $which_font;  
             $this->y_title_font['height'] = ImageFontHeight($which_font);  
             $this->y_title_font['width'] = ImageFontWidth($which_font);  
             break;  
         default:  
             $this->DrawError("SetFont(): Unknown element '$which_elem' specified.");  
             return FALSE;  
         }  
894          return TRUE;          return TRUE;
895      }      }
896    
897    
898      /*!      /*
899       * Returns an array with the size of the bounding box of an       * Select Fixed/TrueType font for an element. Which type of font is
900       * arbitrarily placed (rotated) TrueType text string.       * selected depends on the $use_ttf class variable (see SetUseTTF()).
901       */       * Before PHPlot supported mixing font types, only this function and
902      function TTFBBoxSize($size, $angle, $font, $string)       * SetUseTTF were available to select an overall font type, but now
903      {       * SetFontGD() and SetFontTTF() can be used for mixing font types.
904          // First, assume angle < 90       *    $which_elem : The element whose font is to be changed.
905          $arr = ImageTTFBBox($size, 0, $font, $string);       *       One of: title legend generic x_label y_label x_title y_title
906          $flat_width  = $arr[2] - $arr[0];       *    $which_font : A number 1-5 for fixed fonts, or a TrueType font.
907          $flat_height = abs($arr[3] - $arr[5]);       *    $which_size : Ignored for Fixed fonts, point size for TrueType.
908         *    $which_spacing (optional) : Line spacing factor
909         */
910        function SetFont($which_elem, $which_font, $which_size = 12, $line_spacing = NULL)
911        {
912            if ($this->use_ttf)
913                return $this->SetFontTTF($which_elem, $which_font, $which_size, $line_spacing);
914            return $this->SetFontGD($which_elem, $which_font, $line_spacing);
915        }
916    
917          // Now the bounding box      /*
918          $angle = deg2rad($angle);       * Return the inter-line spacing for a font.
919          $width  = ceil(abs($flat_width*cos($angle) + $flat_height*sin($angle))); //Must be integer       * This is an internal function, used by ProcessText* and DrawLegend.
920          $height = ceil(abs($flat_width*sin($angle) + $flat_height*cos($angle))); //Must be integer       *   $font : A font array variable.
921         * Returns: Spacing, in pixels, between text lines.
922         */
923        function GetLineSpacing($font)
924        {
925            # Use the per-font line spacing preference, if set, else the global value:
926            if (isset($font['line_spacing']))
927                $line_spacing = $font['line_spacing'];
928            else
929                $line_spacing = $this->line_spacing;
930    
931          return array($width, $height);          # For GD fonts, that is the spacing in pixels.
932            # For TTF, adjust based on the 'natural' font spacing (see SetFontTTF):
933            if ($font['ttf']) {
934                $line_spacing = (int)($line_spacing * $font['spacing'] / 6.0);
935            }
936            return $line_spacing;
937      }      }
938    
   
939      /*!      /*!
940       * Draws a string of text. Horizontal and vertical alignment are relative to       * Text drawing and sizing functions:
941       * to the drawing. That is: vertical text (90 deg) gets centered along y-axis       * ProcessText is meant for use only by DrawText and SizeText.
942       * with v_align = 'center', and adjusted to the left of x-axis with h_align = 'right',       *    ProcessText(True, ...)  - Draw a block of text
943         *    ProcessText(False, ...) - Just return ($width, $height) of
944         *       the orthogonal bounding box containing the text.
945         * ProcessText is further split into separate functions for GD and TTF
946         * text, due to the size of the code.
947         *
948         * Horizontal and vertical alignment are relative to the drawing. That is:
949         * vertical text (90 deg) gets centered along Y position with
950         * v_align = 'center', and adjusted to the right of X position with
951         * h_align = 'right'.  Another way to look at this is to say
952         * that text rotation happens first, then alignment.
953         *
954         * Original multiple lines code submitted by Remi Ricard.
955         * Original vertical code submitted by Marlin Viss.
956         *
957         * Text routines rewritten by ljb to fix alignment and position problems.
958         * Here is my explanation and notes. More information and pictures will be
959         * placed in the PHPlot Reference Manual.
960         *
961         *    + Process TTF text one line at a time, not as a block. (See below)
962         *    + Flipped top vs bottom vertical alignment. The usual interpretation
963         *  is: bottom align means bottom of the text is at the specified Y
964         *  coordinate. For some reason, PHPlot did left/right the correct way,
965         *  but had top/bottom reversed. I fixed it, and left the default valign
966         *  argument as bottom, but the meaning of the default value changed.
967         *
968         *    For GD font text, only single-line text is handled by GD, and the
969         *  basepoint is the upper left corner of each text line.
970         *    For TTF text, multi-line text could be handled by GD, with the text
971         *  basepoint at the lower left corner of the first line of text.
972         *  (Behavior of TTF drawing routines on multi-line text is not documented.)
973         *  But you cannot do left/center/right alignment on each line that way,
974         *  or proper line spacing.
975         *    Therefore, for either text type, we have to break up the text into
976         *  lines and position each line independently.
977         *
978         *    There are 9 alignment modes: Horizontal = left, center, or right, and
979         *  Vertical = top, center, or bottom. Alignment is interpreted relative to
980         *  the image, not as the text is read. This makes sense when you consider
981         *  for example X axis labels. They need to be centered below the marks
982         *  (center, top alignment) regardless of the text angle.
983         *  'Bottom' alignment really means baseline alignment.
984         *
985         *    GD font text is supported (by libgd) at 0 degrees and 90 degrees only.
986         *  Multi-line or single line text works with any of the 9 alignment modes.
987         *
988         *    TTF text can be at any angle. The 9 alignment modes work for all angles,
989         *  but the results might not be what you expect for multi-line text. See
990         *  the PHPlot Reference Manual for pictures and details. In short, alignment
991         *  applies to the orthogonal (aligned with X and Y axes) bounding box that
992         *  contains the text, and to each line in the multi-line text box. Since
993         *  alignment is relative to the image, 45 degree multi-line text aligns
994         *  differently from 46 degree text.
995         *
996         *    Note that PHPlot allows multi-line text for the 3 titles, and they
997         *  are only drawn at 0 degrees (main and X titles) or 90 degrees (Y title).
998         *  Data labels can also be multi-line, and they can be drawn at any angle.
999         *  -ljb 2007-11-03
1000       *       *
      * \note Original multiple lines code submitted by Remi Ricard.  
      * \note Original vertical code submitted by Marlin Viss.  
1001       */       */
     function DrawText($which_font, $which_angle, $which_xpos, $which_ypos, $which_color, $which_text,  
                       $which_halign = 'left', $which_valign = 'bottom')  
     {  
         // TTF:  
         if ($this->use_ttf) {  
             $size = $this->TTFBBoxSize($which_font['size'], $which_angle, $which_font['font'], $which_text);  
             $rads = deg2rad($which_angle);  
1002    
1003              if ($which_valign == 'center')      /*
1004                  $which_ypos += $size[1]/2;       * ProcessTextGD() - Draw or size GD fixed-font text.
1005              elseif ($which_valign == 'bottom')       * This is intended for use only by ProcessText().
1006                  $which_ypos += $size[1];       *    $draw_it : True to draw the text, False to just return the orthogonal width and height.
1007         *    $font : PHPlot font array (with 'ttf' = False) - see SetFontGD()
1008              if ($which_halign == 'center')       *    $angle : Text angle in degrees. GD only supports 0 and 90. We treat >= 45 as 90, else 0.
1009                  $which_xpos -= ($size[0]/2) * cos($rads);       *    $x, $y : Reference point for the text (ignored if !$draw_it)
1010              elseif ($which_halign == 'left')       *    $color : GD color index to use for drawing the text (ignored if !$draw_it)
1011                  $which_xpos += $size[0] * sin($rads);       *    $text : The text to draw or size. Put a newline between lines.
1012              elseif ($which_halign == 'right')       *    $h_factor : Horizontal alignment factor: 0(left), .5(center), or 1(right) (ignored if !$draw_it)
1013                  $which_xpos -= $size[0] * cos($rads);       *    $v_factor : Vertical alignment factor: 0(top), .5(center), or 1(bottom) (ignored if !$draw_it)
1014         * Returns: True, if drawing text, or an array of ($width, $height) if not.
1015              ImageTTFText($this->img, $which_font['size'], $which_angle,       */
1016                           $which_xpos, $which_ypos, $which_color, $which_font['font'], $which_text);      function ProcessTextGD($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor)
1017        {
1018            # Extract font parameters:
1019            $font_number = $font['font'];
1020            $font_width = $font['width'];
1021            $font_height = $font['height'];
1022            $line_spacing = $this->GetLineSpacing($font);
1023    
1024            # Break up the text into lines, trim whitespace, find longest line.
1025            # Save the lines and length for drawing below.
1026            $longest = 0;
1027            foreach (explode("\n", $text) as $each_line) {
1028                $lines[] = $line = trim($each_line);
1029                $line_lens[] = $line_len = strlen($line);
1030                if ($line_len > $longest) $longest = $line_len;
1031            }
1032            $n_lines = count($lines);
1033    
1034            # Width, height are based on font size and longest line, line count respectively.
1035            # These are relative to the text angle.
1036            $total_width = $longest * $font_width;
1037            $total_height = $n_lines * $font_height + ($n_lines - 1) * $line_spacing;
1038    
1039            if (!$draw_it) {
1040                if ($angle < 45) return array($total_width, $total_height);
1041                return array($total_height, $total_width);
1042          }          }
         // Fixed fonts:  
         else {  
             // Split the text by its lines, and count them  
             $which_text = ereg_replace("\r", "", $which_text);  
             $str = split("\n", $which_text);  
             $nlines = count($str);  
             $spacing = $this->line_spacing * ($nlines - 1);  
1043    
1044              // Vertical text:          $interline_step = $font_height + $line_spacing; // Line-to-line step
1045    
1046            if ($angle >= 45) {
1047                // Vertical text (90 degrees):
1048              // (Remember the alignment convention with vertical text)              // (Remember the alignment convention with vertical text)
1049              if ($which_angle == 90) {              // For 90 degree text, alignment factors change like this:
1050                  // The text goes around $which_xpos.              $temp = $v_factor;
1051                  if ($which_halign == 'center')              $v_factor = $h_factor;
1052                      $which_xpos -= ($nlines * ($which_font['height'] + $spacing))/2;              $h_factor = 1 - $temp;
1053    
1054                  // Left alignment requires no modification to $xpos...              $draw_func = 'ImageStringUp';
1055                  // Right-align it. $which_xpos designated the rightmost x coordinate.  
1056                  elseif ($which_halign == 'right') {              // Rotation matrix "R" for 90 degrees (with Y pointing down):
1057                      $which_xpos -= ($nlines * ($which_font['height'] + $spacing));              $r00 = 0;  $r01 = 1;
1058                  }              $r10 = -1; $r11 = 0;
1059    
1060                  $ypos = $which_ypos;          } else {
1061                  for($i = 0; $i < $nlines; $i++) {              // Horizontal text (0 degrees):
1062                      // Center the text vertically around $which_ypos (each line)              $draw_func = 'ImageString';
1063                      if ($which_valign == 'center')  
1064                          $ypos = $which_ypos + (strlen($str[$i]) * $which_font['width']) / 2;              // Rotation matrix "R" for 0 degrees:
1065                      // Make the text finish (vertically) at $which_ypos              $r00 = 1; $r01 = 0;
1066                      if ($which_valign == 'bottom')              $r10 = 0; $r11 = 1;
1067                          $ypos = $which_ypos + strlen($str[$i]) * $which_font['width'];          }
1068    
1069                      ImageStringUp($this->img, $which_font['font'],          // Adjust for vertical alignment (horizontal text) or horizontal alignment (vertical text):
1070                                    $i * ($which_font['height'] + $spacing) + $which_xpos,          $factor = (int)($total_height * $v_factor);
1071                                    $ypos, $str[$i], $which_color);          $xpos = $x - $r01 * $factor;
1072                  }          $ypos = $y - $r11 * $factor;
1073    
1074            # Debug callback provides the bounding box:
1075            if ($this->GetCallback('debug_textbox')) {
1076                if ($angle >= 45) {
1077                    $bbox_width  = $total_height;
1078                    $bbox_height = $total_width;
1079                    $px = $xpos;
1080                    $py = $ypos - (1 - $h_factor) * $total_width;
1081                } else {
1082                    $bbox_width  = $total_width;
1083                    $bbox_height = $total_height;
1084                    $px = $xpos - $h_factor * $total_width;
1085                    $py = $ypos;
1086              }              }
1087              // Horizontal text:              $this->DoCallback('debug_textbox', $px, $py, $bbox_width, $bbox_height);
1088              else {          }
1089                  // The text goes above $which_ypos  
1090                  if ($which_valign == 'top')          for ($i = 0; $i < $n_lines; $i++) {
1091                      $which_ypos -= $nlines * ($which_font['height'] + $spacing);  
1092                  // The text is centered around $which_ypos              // Adjust for alignment of this line within the text block:
1093                  if ($which_valign == 'center')              $factor = (int)($line_lens[$i] * $font_width * $h_factor);
1094                      $which_ypos -= ($nlines * ($which_font['height'] + $spacing))/2;              $x = $xpos - $r00 * $factor;
1095                  // valign = 'bottom' requires no modification              $y = $ypos - $r10 * $factor;
1096    
1097                  $xpos = $which_xpos;              // Call ImageString or ImageStringUp:
1098                  for($i = 0; $i < $nlines; $i++) {              $draw_func($this->img, $font_number, $x, $y, $lines[$i], $color);
1099                      // center the text around $which_xpos  
1100                      if ($which_halign == 'center')              // Step to the next line of text. This is a rotation of (x=0, y=interline_spacing)
1101                          $xpos = $which_xpos - (strlen($str[$i]) * $which_font['width'])/2;              $xpos += $r01 * $interline_step;
1102                      // make the text finish at $which_xpos              $ypos += $r11 * $interline_step;
1103                      if ($which_halign == 'right')          }
1104                          $xpos = $which_xpos - strlen($str[$i]) * $which_font['width'];          return TRUE;
1105        }
1106                      ImageString($this->img, $which_font['font'], $xpos,  
1107                                  $i * ($which_font['height'] + $spacing) + $which_ypos,  
1108                                  $str[$i], $which_color);      /*
1109         * ProcessTextTTF() - Draw or size TTF text.
1110         * This is intended for use only by ProcessText().
1111         *    $draw_it : True to draw the text, False to just return the orthogonal width and height.
1112         *    $font : PHPlot font array (with 'ttf' = True) - see SetFontTTF()
1113         *    $angle : Text angle in degrees.
1114         *    $x, $y : Reference point for the text (ignored if !$draw_it)
1115         *    $color : GD color index to use for drawing the text (ignored if !$draw_it)
1116         *    $text : The text to draw or size. Put a newline between lines.
1117         *    $h_factor : Horizontal alignment factor: 0(left), .5(center), or 1(right) (ignored if !$draw_it)
1118         *    $v_factor : Vertical alignment factor: 0(top), .5(center), or 1(bottom) (ignored if !$draw_it)
1119         * Returns: True, if drawing text, or an array of ($width, $height) if not.
1120         */
1121        function ProcessTextTTF($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor)
1122        {
1123            # Extract font parameters (see SetFontTTF):
1124            $font_file = $font['font'];
1125            $font_size = $font['size'];
1126            $font_height = $font['height'];
1127            $line_spacing = $this->GetLineSpacing($font);
1128    
1129            # Break up the text into lines, trim whitespace.
1130            # Calculate the total width and height of the text box at 0 degrees.
1131            # Save the trimmed lines and their widths for later when drawing.
1132            # To get uniform spacing, don't use the actual line heights.
1133            # Total height = Font-specific line heights plus inter-line spacing.
1134            # Total width = width of widest line.
1135            # Last Line Descent is the offset from the bottom to the text baseline.
1136            # Note: For some reason, ImageTTFBBox uses (-1,-1) as the reference point.
1137            #   So 1+bbox[1] is the baseline to bottom distance.
1138            $total_width = 0;
1139            $lastline_descent = 0;
1140            foreach (explode("\n", $text) as $each_line) {
1141                $lines[] = $line = trim($each_line);
1142                $bbox = ImageTTFBBox($font_size, 0, $font_file, $line);
1143                $line_widths[] = $width = $bbox[2] - $bbox[0];
1144                if ($width > $total_width) $total_width = $width;
1145                $lastline_descent = 1 + $bbox[1];
1146            }
1147            $n_lines = count($lines);
1148            $total_height = $n_lines * $font_height + ($n_lines - 1) * $line_spacing;
1149    
1150            # Calculate the rotation matrix for the text's angle. Remember that GD points Y down,
1151            # so the sin() terms change sign.
1152            $theta = deg2rad($angle);
1153            $cos_t = cos($theta);
1154            $sin_t = sin($theta);
1155            $r00 = $cos_t;    $r01 = $sin_t;
1156            $r10 = -$sin_t;   $r11 = $cos_t;
1157    
1158            # Make a bounding box of the right size, with upper left corner at (0,0).
1159            # By convention, the point order is: LL, LR, UR, UL.
1160            # Note this is still working with the text at 0 degrees.
1161            # When sizing text (SizeText), use the overall size with descenders.
1162            #   This tells the caller how much room to leave for the text.
1163            # When drawing text (DrawText), use the size without descenders - that
1164            #   is, down to the baseline. This is for accurate positioning.
1165            $b[0] = 0;
1166            if ($draw_it) {
1167                $b[1] = $total_height;
1168            } else {
1169                $b[1] = $total_height + $lastline_descent;
1170            }
1171            $b[2] = $total_width;  $b[3] = $b[1];
1172            $b[4] = $total_width;  $b[5] = 0;
1173            $b[6] = 0;             $b[7] = 0;
1174    
1175            # Rotate the bounding box, then offset to the reference point:
1176            for ($i = 0; $i < 8; $i += 2) {
1177                $x_b = $b[$i];
1178                $y_b = $b[$i+1];
1179                $c[$i]   = $x + $r00 * $x_b + $r01 * $y_b;
1180                $c[$i+1] = $y + $r10 * $x_b + $r11 * $y_b;
1181            }
1182    
1183            # Get an orthogonal (aligned with X and Y axes) bounding box around it, by
1184            # finding the min and max X and Y:
1185            $bbox_ref_x = $bbox_max_x = $c[0];
1186            $bbox_ref_y = $bbox_max_y = $c[1];
1187            for ($i = 2; $i < 8; $i += 2) {
1188                $x_b = $c[$i];
1189                if ($x_b < $bbox_ref_x) $bbox_ref_x = $x_b;
1190                elseif ($bbox_max_x < $x_b) $bbox_max_x = $x_b;
1191                $y_b = $c[$i+1];
1192                if ($y_b < $bbox_ref_y) $bbox_ref_y = $y_b;
1193                elseif ($bbox_max_y < $y_b) $bbox_max_y = $y_b;
1194            }
1195            $bbox_width = $bbox_max_x - $bbox_ref_x;
1196            $bbox_height = $bbox_max_y - $bbox_ref_y;
1197    
1198            if (!$draw_it) {
1199                # Return the bounding box, rounded up (so it always contains the text):
1200                return array((int)ceil($bbox_width), (int)ceil($bbox_height));
1201            }
1202    
1203            $interline_step = $font_height + $line_spacing; // Line-to-line step
1204    
1205            # Calculate the offsets from the supplied reference point to the
1206            # upper-left corner of the text.
1207            # Start at the reference point at the upper left corner of the bounding
1208            # box (bbox_ref_x, bbox_ref_y) then adjust it for the 9 point alignment.
1209            # h,v_factor are 0,0 for top,left, .5,.5 for center,center, 1,1 for bottom,right.
1210            #    $off_x = $bbox_ref_x + $bbox_width * $h_factor - $x;
1211            #    $off_y = $bbox_ref_y + $bbox_height * $v_factor - $y;
1212            # Then use that offset to calculate back to the supplied reference point x, y
1213            # to get the text base point.
1214            #    $qx = $x - $off_x;
1215            #    $qy = $y - $off_y;
1216            # Reduces to:
1217            $qx = 2 * $x - $bbox_ref_x - $bbox_width * $h_factor;
1218            $qy = 2 * $y - $bbox_ref_y - $bbox_height * $v_factor;
1219    
1220            # Check for debug callback. Don't calculate bounding box unless it is wanted.
1221            if ($this->GetCallback('debug_textbox')) {
1222                # Calculate the orthogonal bounding box coordinates for debug testing.
1223    
1224                # qx, qy is upper left corner relative to the text.
1225                # Calculate px,py: upper left corner (absolute) of the bounding box.
1226                # There are 4 equation sets for this, depending on the quadrant:
1227                if ($sin_t > 0) {
1228                    if ($cos_t > 0) {
1229                        # Quadrant: 0d - 90d:
1230                        $px = $qx; $py = $qy - $total_width * $sin_t;
1231                    } else {
1232                        # Quadrant: 90d - 180d:
1233                       $px = $qx + $total_width * $cos_t; $py = $qy - $bbox_height;
1234                    }
1235                } else {
1236                    if ($cos_t < 0) {
1237                        # Quadrant: 180d - 270d:
1238                        $px = $qx - $bbox_width; $py = $qy + $total_height * $cos_t;
1239                    } else {
1240                        # Quadrant: 270d - 360d:
1241                        $px = $qx + $total_height * $sin_t; $py = $qy;
1242                  }                  }
1243              }              }
1244                $this->DoCallback('debug_textbox', $px, $py, $bbox_width, $bbox_height);
1245            }
1246    
1247            # Since alignment is applied after rotation, which parameter is used
1248            # to control alignment of each line within the text box varies with
1249            # the angle.
1250            #   Angle (degrees):       Line alignment controlled by:
1251            #  -45 < angle <= 45          h_align
1252            #   45 < angle <= 135         reversed v_align
1253            #  135 < angle <= 225         reversed h_align
1254            #  225 < angle <= 315         v_align
1255            if ($cos_t >= $sin_t) {
1256                if ($cos_t >= -$sin_t) $line_align_factor = $h_factor;
1257                else $line_align_factor = $v_factor;
1258            } else {
1259                if ($cos_t >= -$sin_t) $line_align_factor = 1-$v_factor;
1260                else $line_align_factor = 1-$h_factor;
1261          }          }
1262          return TRUE;  
1263      } // function DrawText()          # Now we have the start point, spacing and in-line alignment factor.
1264            # We are finally ready to start drawing the text, line by line.
1265            for ($i = 0; $i < $n_lines; $i++) {
1266    
1267                # For drawing TTF text, the reference point is the left edge of the
1268                # text baseline (not the lower left corner of the bounding box).
1269                # The following also adjusts for horizontal (relative to
1270                # the text) alignment of the current line within the box.
1271                # What is happening is rotation of this vector by the text angle:
1272                #    (x = (total_width - line_width) * factor, y = font_height)
1273    
1274                $width_factor = ($total_width - $line_widths[$i]) * $line_align_factor;
1275                $rx = $qx + $r00 * $width_factor + $r01 * $font_height;
1276                $ry = $qy + $r10 * $width_factor + $r11 * $font_height;
1277    
1278                # Finally, draw the text:
1279                ImageTTFText($this->img, $font_size, $angle, $rx, $ry, $color, $font_file, $lines[$i]);
1280    
1281                # Step to position of next line.
1282                # This is a rotation of (x=0,y=height+line_spacing) by $angle:
1283                $qx += $r01 * $interline_step;
1284                $qy += $r11 * $interline_step;
1285            }
1286            return True;
1287        }
1288    
1289        /*
1290         * ProcessText() - Wrapper for ProcessTextTTF() and ProcessTextGD(). See notes above.
1291         * This is intended for use from within PHPlot only, and only by DrawText() and SizeText().
1292         *    $draw_it : True to draw the text, False to just return the orthogonal width and height.
1293         *    $font : PHPlot font array
1294         *    $angle : Text angle in degrees
1295         *    $x, $y : Reference point for the text (ignored if !$draw_it)
1296         *    $color : GD color index to use for drawing the text (ignored if !$draw_it)
1297         *    $text : The text to draw or size. Put a newline between lines.
1298         *    $halign : Horizontal alignment: left, center, or right (ignored if !$draw_it)
1299         *    $valign : Vertical alignment: top, center, or bottom (ignored if !$draw_it)
1300         *      Note: Alignment is relative to the image, not the text.
1301         * Returns: True, if drawing text, or an array of ($width, $height) if not.
1302         */
1303        function ProcessText($draw_it, $font, $angle, $x, $y, $color, $text, $halign, $valign)
1304        {
1305            # Empty text case:
1306            if ($text === '') {
1307                if ($draw_it) return TRUE;
1308                return array(0, 0);
1309            }
1310    
1311            # Calculate width and height offset factors using the alignment args:
1312            if ($valign == 'top') $v_factor = 0;
1313            elseif ($valign == 'center') $v_factor = 0.5;
1314            else $v_factor = 1.0; # 'bottom'
1315            if ($halign == 'left') $h_factor = 0;
1316            elseif ($halign == 'center') $h_factor = 0.5;
1317            else $h_factor = 1.0; # 'right'
1318    
1319            if ($font['ttf']) {
1320                return $this->ProcessTextTTF($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor);
1321            }
1322            return $this->ProcessTextGD($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor);
1323        }
1324    
1325    
1326        /*
1327         * Draws a block of text. See comments above before ProcessText().
1328         *    $which_font : PHPlot font array.
1329         *    $which_angle : Text angle in degrees
1330         *    $which_xpos, $which_ypos: Reference point for the text
1331         *    $which_color : GD color index to use for drawing the text
1332         *    $which_text :  The text to draw, with newlines (\n) between lines.
1333         *    $which_halign : Horizontal (relative to the image) alignment: left, center, or right.
1334         *    $which_valign : Vertical (relative to the image) alignment: top, center, or bottom.
1335         */
1336        function DrawText($which_font, $which_angle, $which_xpos, $which_ypos, $which_color, $which_text,
1337                          $which_halign = 'left', $which_valign = 'bottom')
1338        {
1339            return $this->ProcessText(True,
1340                               $which_font, $which_angle, $which_xpos, $which_ypos,
1341                               $which_color, $which_text, $which_halign, $which_valign);
1342        }
1343    
1344        /*
1345         * Returns the size of block of text. This is the orthogonal width and height of a bounding
1346         * box aligned with the X and Y axes of the text. Only for angle=0 is this the actual
1347         * width and height of the text block, but for any angle it is the amount of space needed
1348         * to contain the text.
1349         *    $which_font : PHPlot font array.
1350         *    $which_angle : Text angle in degrees
1351         *    $which_text :  The text to draw, with newlines (\n) between lines.
1352         * Returns a two element array with: $width, $height.
1353         * This is just a wrapper for ProcessText() - see above.
1354         */
1355        function SizeText($which_font, $which_angle, $which_text)
1356        {
1357            // Color, position, and alignment are not used when calculating the size.
1358            return $this->ProcessText(False,
1359                               $which_font, $which_angle, 0, 0, 1, $which_text, '', '');
1360        }
1361    
1362    
1363  /////////////////////////////////////////////  /////////////////////////////////////////////
# Line 1042  class PHPlot { Line 1370  class PHPlot {
1370      function SetFileFormat($format)      function SetFileFormat($format)
1371      {      {
1372          $asked = $this->CheckOption($format, 'jpg, png, gif, wbmp', __FUNCTION__);          $asked = $this->CheckOption($format, 'jpg, png, gif, wbmp', __FUNCTION__);
1373            if (!$asked) return False;
1374          switch ($asked) {          switch ($asked) {
1375          case 'jpg':          case 'jpg':
1376              if (imagetypes() & IMG_JPG)              $format_test = IMG_JPG;
                 $this->file_format = 'jpg';  
                 return TRUE;  
1377              break;              break;
1378          case 'png':          case 'png':
1379              if (imagetypes() & IMG_PNG)              $format_test = IMG_PNG;
                 $this->file_format = 'png';  
                 return TRUE;  
1380              break;              break;
1381          case 'gif':          case 'gif':
1382              if (imagetypes() & IMG_GIF)              $format_test = IMG_GIF;
                 $this->file_format = 'gif';  
                 return TRUE;  
1383              break;              break;
1384          case 'wbmp':          case 'wbmp':
1385              if (imagetypes() & IMG_WBMP)              $format_test = IMG_WBMP;
                 $this->file_format = 'wbmp';  
                 return TRUE;  
1386              break;              break;
         default:  
             $this->PrintError("SetFileFormat():File format '$format' not supported");  
             return FALSE;  
1387          }          }
1388            if (!(imagetypes() & $format_test)) {
1389                return $this->PrintError("SetFileFormat(): File format '$format' not supported");
1390            }
1391            $this->file_format = $asked;
1392            return TRUE;
1393      }      }
1394    
1395    
# Line 1081  class PHPlot { Line 1403  class PHPlot {
1403      {      {
1404          $this->bgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);          $this->bgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
1405          $this->bgimg  = $input_file;          $this->bgimg  = $input_file;
1406            return (boolean)$this->bgmode;
1407      }      }
1408    
1409      /*!      /*!
# Line 1093  class PHPlot { Line 1416  class PHPlot {
1416      {      {
1417          $this->plotbgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);          $this->plotbgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
1418          $this->plotbgimg  = $input_file;          $this->plotbgimg  = $input_file;
1419            return (boolean)$this->plotbgmode;
1420      }      }
1421    
1422    
# Line 1173  class PHPlot { Line 1497  class PHPlot {
1497    
1498              break;              break;
1499          default:          default:
1500              $this->PrintError('PrintImage(): Please select an image type!');              return $this->PrintError('PrintImage(): Please select an image type!');
             break;  
1501          }          }
1502          return TRUE;          return TRUE;
1503      }      }
1504    
1505      /*!      /*!
1506       * Prints an error message to stdout and dies       *  Error handling for 'fatal' errors:
1507         *   $error_message       Text of the error message
1508         *  Standard output from PHPlot is expected to be an image file, such as
1509         *  when handling an <img> tag browser request. So it is not permitted to
1510         *  output text to standard output. (You should have display_errors=off)
1511         *  Here is how PHPlot handles fatal errors:
1512         *    + Write the error message into an image, and output the image.
1513         *    + If no image can be output, write nothing and produce an HTTP
1514         *      error header.
1515         *    + Trigger a user-level error containing the error message.
1516         *      If no error handler was set up, the script will log the
1517         *      error and exit with non-zero status.
1518         *
1519         *  PrintError() and DrawError() are now equivalent. Both are provided for
1520         *  compatibility. (In earlier releases, PrintError sent the message to
1521         *  stdout only, and DrawError sent it in an image only.)
1522         *
1523         *  This function does not return, unless the calling script has set up
1524         *  an error handler which does not exit. In that case, PrintError will
1525         *  return False. But not all of PHPlot will handle this correctly, so
1526         *  it is probably a bad idea for an error handler to return.
1527       */       */
1528      function PrintError($error_message)      function PrintError($error_message)
1529      {      {
1530          echo "<p><b>Fatal error</b>: $error_message<p>";          // Be sure not to loop recursively, e.g. PrintError - PrintImage - PrintError.
1531          die;          if (isset($this->in_error)) return FALSE;
1532            $this->in_error = TRUE;
1533    
1534            // Output an image containing the error message:
1535            if (!empty($this->img)) {
1536                $ypos = $this->image_height/2;
1537                $xpos = $this->image_width/2;
1538                $bgcolor = ImageColorResolve($this->img, 255, 255, 255);
1539                $fgcolor = ImageColorResolve($this->img, 0, 0, 0);
1540                ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height, $bgcolor);
1541    
1542                // Switch to built-in fonts, in case of error with TrueType fonts:
1543                $this->SetUseTTF(FALSE);
1544    
1545                $this->DrawText($this->fonts['generic'], 0, $xpos, $ypos, $fgcolor,
1546                                wordwrap($error_message), 'center', 'center');
1547    
1548                $this->PrintImage();
1549            } elseif (! $this->is_inline) {
1550                Header('HTTP/1.0 500 Internal Server Error');
1551            }
1552            trigger_error($error_message, E_USER_ERROR);
1553            unset($this->in_error);
1554            return FALSE;  # In case error handler returns, rather than doing exit().
1555      }      }
1556    
1557      /*!      /*!
1558       * Prints an error message inline into the generated image and draws it centered       * Display an error message and exit.
1559       * around the given coordinates (defaults to center of the image)       * This is provided for backward compatibility only. Use PrintError() instead.
1560       *   \param error_message Message to be drawn       *   $error_message       Text of the error message
1561       *   \param where_x       X coordinate       *   $where_x, $where_y   Ignored, provided for compatibility.
      *   \param where_y       Y coordinate  
1562       */       */
1563      function DrawError($error_message, $where_x = NULL, $where_y = NULL)      function DrawError($error_message, $where_x = NULL, $where_y = NULL)