Parent Directory
|
Revision Log
|
Revision Graph
|
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) |