source: trunk/client/modules/Elezioni/grafici/gd_image.inc.php@ 285

Last change on this file since 285 was 284, checked in by roby, 6 years ago
File size: 83.2 KB
RevLine 
[2]1<?php
2//=======================================================================
[284]3// File: GD_IMAGE.INC.PHP
4// Description: PHP Graph Plotting library. Low level image drawing routines
5// Created: 2001-01-08, refactored 2008-03-29
6// Ver: $Id: gd_image.inc.php 1922 2010-01-11 11:42:50Z ljp $
[2]7//
[284]8// Copyright (c) Asial Corporation. All rights reserved.
[2]9//========================================================================
10
[284]11require_once 'jpgraph_rgb.inc.php';
12require_once 'jpgraph_ttf.inc.php';
13require_once 'imageSmoothArc.php';
14require_once 'jpgraph_errhandler.inc.php';
15
16// Line styles
17define('LINESTYLE_SOLID',1);
18define('LINESTYLE_DOTTED',2);
19define('LINESTYLE_DASHED',3);
20define('LINESTYLE_LONGDASH',4);
21
22// The DEFAULT_GFORMAT sets the default graphic encoding format, i.e.
23// PNG, JPG or GIF depending on what is installed on the target system
24// in that order.
25if( !DEFINED("DEFAULT_GFORMAT") ) {
26 define("DEFAULT_GFORMAT","auto");
27}
28
29//========================================================================
[2]30// CLASS Image
[284]31// Description: The very coor image drawing class that encapsulates all
32// calls to the GD library
33// Note: The class used by the library is the decendant
34// class RotImage which extends the Image class with transparent
35// rotation.
36//=========================================================================
[2]37class Image {
38 public $img=null;
39 public $rgb=null;
40 public $img_format;
41 public $ttf=null;
[284]42 public $line_style=LINESTYLE_SOLID;
43 public $current_color,$current_color_name;
44 public $original_width=0, $original_height=0;
45 public $plotwidth=0,$plotheight=0;
46
47 // for __get, __set
48 private $_left_margin=30,$_right_margin=30,$_top_margin=20,$_bottom_margin=30;
49 //private $_plotwidth=0,$_plotheight=0;
50 private $_width=0, $_height=0;
51 private $_line_weight=1;
52
[2]53 protected $expired=true;
54 protected $lastx=0, $lasty=0;
55 protected $obs_list=array();
[284]56 protected $font_size=12,$font_family=FF_DEFAULT, $font_style=FS_NORMAL;
[2]57 protected $font_file='';
58 protected $text_halign="left",$text_valign="bottom";
59 protected $use_anti_aliasing=false;
60 protected $quality=null;
61 protected $colorstack=array(),$colorstackidx=0;
62 protected $canvascolor = 'white' ;
63 protected $langconv = null ;
64 protected $iInterlace=false;
[284]65 protected $bbox_cache = array(); // STore the last found tetx bounding box
66 protected $ff_font0;
67 protected $ff_font0_bold;
68 protected $ff_font1;
69 protected $ff_font1_bold;
70 protected $ff_font2;
71 protected $ff_font2_bold;
72
73
[2]74 //---------------
75 // CONSTRUCTOR
[284]76 function __construct($aWidth=0,$aHeight=0,$aFormat=DEFAULT_GFORMAT,$aSetAutoMargin=true) {
[2]77
[284]78 $this->original_width = $aWidth;
79 $this->original_height = $aHeight;
80 $this->CreateImgCanvas($aWidth, $aHeight);
81
82 if( $aSetAutoMargin ) {
83 $this->SetAutoMargin();
84 }
85
86 if( !$this->SetImgFormat($aFormat) ) {
87 JpGraphError::RaiseL(25081,$aFormat);//("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
88 }
89 $this->ttf = new TTF();
90 $this->langconv = new LanguageConv();
91
92 $this->ff_font0 = imageloadfont(dirname(__FILE__) . "/fonts/FF_FONT0.gdf");
93 $this->ff_font1 = imageloadfont(dirname(__FILE__) . "/fonts/FF_FONT1.gdf");
94 $this->ff_font2 = imageloadfont(dirname(__FILE__) . "/fonts/FF_FONT2.gdf");
95 $this->ff_font1_bold = imageloadfont(dirname(__FILE__) . "/fonts/FF_FONT1-Bold.gdf");
96 $this->ff_font2_bold = imageloadfont(dirname(__FILE__) . "/fonts/FF_FONT2-Bold.gdf");
[2]97 }
98
99 // Enable interlacing in images
100 function SetInterlace($aFlg=true) {
[284]101 $this->iInterlace=$aFlg;
[2]102 }
103
104 // Should we use anti-aliasing. Note: This really slows down graphics!
105 function SetAntiAliasing($aFlg=true) {
[284]106 $this->use_anti_aliasing = $aFlg;
107 if( function_exists('imageantialias') ) {
108 imageantialias($this->img,$aFlg);
109 }
110 else {
111 JpGraphError::RaiseL(25128);//('The function imageantialias() is not available in your PHP installation. Use the GD version that comes with PHP and not the standalone version.')
112 }
[2]113 }
114
[284]115 function GetAntiAliasing() {
116 return $this->use_anti_aliasing ;
117 }
118
[2]119 function CreateRawCanvas($aWidth=0,$aHeight=0) {
120
[284]121 $aWidth *= SUPERSAMPLING_SCALE;
122 $aHeight *= SUPERSAMPLING_SCALE;
[2]123
[284]124 if( $aWidth <= 1 || $aHeight <= 1 ) {
125 JpGraphError::RaiseL(25082,$aWidth,$aHeight);//("Illegal sizes specified for width or height when creating an image, (width=$aWidth, height=$aHeight)");
126 }
127
128 $this->img = @imagecreatetruecolor($aWidth, $aHeight);
129 if( $this->img < 1 ) {
130 JpGraphError::RaiseL(25126);
131 //die("Can't create truecolor image. Check that you really have GD2 library installed.");
132 }
133 $this->SetAlphaBlending();
134
135 if( $this->iInterlace ) {
136 imageinterlace($this->img,1);
137 }
138 if( $this->rgb != null ) {
139 $this->rgb->img = $this->img ;
140 }
141 else {
142 $this->rgb = new RGB($this->img);
143 }
[2]144 }
145
146 function CloneCanvasH() {
[284]147 $oldimage = $this->img;
148 $this->CreateRawCanvas($this->width,$this->height);
149 imagecopy($this->img,$oldimage,0,0,0,0,$this->width,$this->height);
150 return $oldimage;
[2]151 }
[284]152
[2]153 function CreateImgCanvas($aWidth=0,$aHeight=0) {
154
[284]155 $old = array($this->img,$this->width,$this->height);
[2]156
[284]157 $aWidth = round($aWidth);
158 $aHeight = round($aHeight);
[2]159
[284]160 $this->width=$aWidth;
161 $this->height=$aHeight;
[2]162
[284]163
164 if( $aWidth==0 || $aHeight==0 ) {
165 // We will set the final size later.
166 // Note: The size must be specified before any other
167 // img routines that stroke anything are called.
168 $this->img = null;
169 $this->rgb = null;
170 return $old;
171 }
172
173 $this->CreateRawCanvas($aWidth,$aHeight);
174 // Set canvas color (will also be the background color for a
175 // a pallett image
176 $this->SetColor($this->canvascolor);
177 $this->FilledRectangle(0,0,$this->width-1,$this->height-1);
178
179 return $old ;
[2]180 }
181
182 function CopyCanvasH($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight,$aw=-1,$ah=-1) {
[284]183 if( $aw === -1 ) {
184 $aw = $aWidth;
185 $ah = $aHeight;
186 $f = 'imagecopyresized';
187 }
188 else {
189 $f = 'imagecopyresampled';
190 }
191 $f($aToHdl,$aFromHdl,$aToX,$aToY,$aFromX,$aFromY, $aWidth,$aHeight,$aw,$ah);
[2]192 }
193
194 function Copy($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1) {
[284]195 $this->CopyCanvasH($this->img,$fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight);
[2]196 }
197
198 function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) {
[284]199 if( $aMix == 100 ) {
200 $this->CopyCanvasH($this->img,$fromImg,
201 $toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight);
202 }
203 else {
204 if( ($fromWidth != -1 && ($fromWidth != $toWidth)) || ($fromHeight != -1 && ($fromHeight != $fromHeight)) ) {
205 // Create a new canvas that will hold the re-scaled original from image
206 if( $toWidth <= 1 || $toHeight <= 1 ) {
207 JpGraphError::RaiseL(25083);//('Illegal image size when copying image. Size for copied to image is 1 pixel or less.');
208 }
209
210 $tmpimg = @imagecreatetruecolor($toWidth, $toHeight);
211
212 if( $tmpimg < 1 ) {
213 JpGraphError::RaiseL(25084);//('Failed to create temporary GD canvas. Out of memory ?');
214 }
215 $this->CopyCanvasH($tmpimg,$fromImg,0,0,0,0,
216 $toWidth,$toHeight,$fromWidth,$fromHeight);
217 $fromImg = $tmpimg;
218 }
219 imagecopymerge($this->img,$fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$aMix);
220 }
[2]221 }
222
223 static function GetWidth($aImg=null) {
[284]224 if( $aImg === null ) {
225 $aImg = $this->img;
226 }
227 return imagesx($aImg);
[2]228 }
229
230 static function GetHeight($aImg=null) {
[284]231 if( $aImg === null ) {
232 $aImg = $this->img;
233 }
234 return imagesy($aImg);
[2]235 }
[284]236
[2]237 static function CreateFromString($aStr) {
[284]238 $img = imagecreatefromstring($aStr);
239 if( $img === false ) {
240 JpGraphError::RaiseL(25085);
241 //('An image can not be created from the supplied string. It is either in a format not supported or the string is representing an corrupt image.');
242 }
243 return $img;
[2]244 }
245
246 function SetCanvasH($aHdl) {
[284]247 $this->img = $aHdl;
248 $this->rgb->img = $aHdl;
[2]249 }
250
251 function SetCanvasColor($aColor) {
[284]252 $this->canvascolor = $aColor ;
[2]253 }
254
255 function SetAlphaBlending($aFlg=true) {
[284]256 ImageAlphaBlending($this->img,$aFlg);
[2]257 }
258
[284]259 function SetAutoMargin() {
260 $min_bm=5;
261 $lm = min(40,$this->width/7);
262 $rm = min(20,$this->width/10);
263 $tm = max(5,$this->height/7);
264 $bm = max($min_bm,$this->height/6);
265 $this->SetMargin($lm,$rm,$tm,$bm);
[2]266 }
267
268 //---------------
[284]269 // PUBLIC METHODS
270
[2]271 function SetFont($family,$style=FS_NORMAL,$size=10) {
[284]272 $this->font_family=$family;
273 $this->font_style=$style;
274 $this->font_size=$size*SUPERSAMPLING_SCALE;
275 $this->font_file='';
276 if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
277 ++$this->font_family;
278 }
279 if( $this->font_family > FF_FONT2+1 ) { // A TTF font so get the font file
[2]280
[284]281 // Check that this PHP has support for TTF fonts
282 if( !function_exists('imagettfbbox') ) {
283 // use internal font when php is configured without '--with-ttf'
284 $this->font_family = FF_FONT1;
285// JpGraphError::RaiseL(25087);//('This PHP build has not been configured with TTF support. You need to recompile your PHP installation with FreeType support.');
286 } else {
287 $this->font_file = $this->ttf->File($this->font_family,$this->font_style);
288 }
289 }
[2]290 }
291
292 // Get the specific height for a text string
293 function GetTextHeight($txt="",$angle=0) {
[284]294 $tmp = preg_split('/\n/',$txt);
295 $n = count($tmp);
296 $m=0;
297 for($i=0; $i< $n; ++$i) {
298 $m = max($m,strlen($tmp[$i]));
299 }
[2]300
[284]301 if( $this->font_family <= FF_FONT2+1 ) {
302 if( $angle==0 ) {
303 $h = imagefontheight($this->font_family);
304 if( $h === false ) {
305 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
306 }
[2]307
[284]308 return $n*$h;
309 }
310 else {
311 $w = @imagefontwidth($this->font_family);
312 if( $w === false ) {
313 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
314 }
[2]315
[284]316 return $m*$w;
317 }
318 }
319 else {
320 $bbox = $this->GetTTFBBox($txt,$angle);
321 return $bbox[1]-$bbox[5]+1;
322 }
[2]323 }
[284]324
[2]325 // Estimate font height
326 function GetFontHeight($angle=0) {
[284]327 $txt = "XOMg";
328 return $this->GetTextHeight($txt,$angle);
[2]329 }
[284]330
[2]331 // Approximate font width with width of letter "O"
332 function GetFontWidth($angle=0) {
[284]333 $txt = 'O';
334 return $this->GetTextWidth($txt,$angle);
[2]335 }
[284]336
337 // Get actual width of text in absolute pixels. Note that the width is the
338 // texts projected with onto the x-axis. Call with angle=0 to get the true
339 // etxt width.
[2]340 function GetTextWidth($txt,$angle=0) {
341
[284]342 $tmp = preg_split('/\n/',$txt);
343 $n = count($tmp);
344 if( $this->font_family <= FF_FONT2+1 ) {
[2]345
[284]346 $m=0;
347 for($i=0; $i < $n; ++$i) {
348 $l=strlen($tmp[$i]);
349 if( $l > $m ) {
350 $m = $l;
351 }
352 }
[2]353
[284]354 if( $angle==0 ) {
355 $w = @imagefontwidth($this->font_family);
356 if( $w === false ) {
357 JpGraphError::RaiseL(25088);//('You have a misconfigured GD font support. The call to imagefontwidth() fails.');
358 }
359 return $m*$w;
360 }
361 else {
362 // 90 degrees internal so height becomes width
363 $h = @imagefontheight($this->font_family);
364 if( $h === false ) {
365 JpGraphError::RaiseL(25089);//('You have a misconfigured GD font support. The call to imagefontheight() fails.');
366 }
367 return $n*$h;
368 }
369 }
370 else {
371 // For TTF fonts we must walk through a lines and find the
372 // widest one which we use as the width of the multi-line
373 // paragraph
374 $m=0;
375 for( $i=0; $i < $n; ++$i ) {
376 $bbox = $this->GetTTFBBox($tmp[$i],$angle);
377 $mm = $bbox[2] - $bbox[0];
378 if( $mm > $m )
379 $m = $mm;
380 }
381 return $m;
382 }
[2]383 }
[284]384
385
[2]386 // Draw text with a box around it
387 function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
[284]388 $shadowcolor=false,$paragraph_align="left",
389 $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) {
[2]390
[284]391 $oldx = $this->lastx;
392 $oldy = $this->lasty;
[2]393
[284]394 if( !is_numeric($dir) ) {
395 if( $dir=="h" ) $dir=0;
396 elseif( $dir=="v" ) $dir=90;
397 else JpGraphError::RaiseL(25090,$dir);//(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
398 }
[2]399
[284]400 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
401 $width=$this->GetTextWidth($txt,$dir) ;
402 $height=$this->GetTextHeight($txt,$dir) ;
403 }
404 else {
405 $width=$this->GetBBoxWidth($txt,$dir) ;
406 $height=$this->GetBBoxHeight($txt,$dir) ;
407 }
[2]408
[284]409 $height += 2*$ymarg;
410 $width += 2*$xmarg;
[2]411
[284]412 if( $this->text_halign=="right" ) $x -= $width;
413 elseif( $this->text_halign=="center" ) $x -= $width/2;
[2]414
[284]415 if( $this->text_valign=="bottom" ) $y -= $height;
416 elseif( $this->text_valign=="center" ) $y -= $height/2;
417
418 $olda = $this->SetAngle(0);
419
420 if( $shadowcolor ) {
421 $this->PushColor($shadowcolor);
422 $this->FilledRoundedRectangle($x-$xmarg+$dropwidth,$y-$ymarg+$dropwidth,
423 $x+$width+$dropwidth,$y+$height-$ymarg+$dropwidth,
424 $cornerradius);
425 $this->PopColor();
426 $this->PushColor($fcolor);
427 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,
428 $x+$width,$y+$height-$ymarg,
429 $cornerradius);
430 $this->PopColor();
431 $this->PushColor($bcolor);
432 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,
433 $x+$width,$y+$height-$ymarg,$cornerradius);
434 $this->PopColor();
435 }
436 else {
437 if( $fcolor ) {
438 $oc=$this->current_color;
439 $this->SetColor($fcolor);
440 $this->FilledRoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius);
441 $this->current_color=$oc;
442 }
443 if( $bcolor ) {
444 $oc=$this->current_color;
445 $this->SetColor($bcolor);
446 $this->RoundedRectangle($x-$xmarg,$y-$ymarg,$x+$width,$y+$height-$ymarg,$cornerradius);
447 $this->current_color=$oc;
448 }
449 }
450
451 $h=$this->text_halign;
452 $v=$this->text_valign;
453 $this->SetTextAlign("left","top");
454
455 $debug=false;
456 $this->StrokeText($x, $y, $txt, $dir, $paragraph_align,$debug);
457
458 $bb = array($x-$xmarg,$y+$height-$ymarg,$x+$width,$y+$height-$ymarg,
459 $x+$width,$y-$ymarg,$x-$xmarg,$y-$ymarg);
460 $this->SetTextAlign($h,$v);
461
462 $this->SetAngle($olda);
463 $this->lastx = $oldx;
464 $this->lasty = $oldy;
465
466 return $bb;
[2]467 }
468
[284]469 // Draw text with a box around it. This time the box will be rotated
470 // with the text. The previous method will just make a larger enough non-rotated
471 // box to hold the text inside.
472 function StrokeBoxedText2($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
473 $shadowcolor=false,$paragraph_align="left",
474 $xmarg=6,$ymarg=4,$cornerradius=0,$dropwidth=3) {
475
476 // This version of boxed text will stroke a rotated box round the text
477 // thta will follow the angle of the text.
478 // This has two implications:
479 // 1) This methos will only support TTF fonts
480 // 2) The only two alignment that makes sense are centered or baselined
481
482 if( $this->font_family <= FF_FONT2+1 ) {
483 JpGraphError::RaiseL(25131);//StrokeBoxedText2() Only support TTF fonts and not built in bitmap fonts
484 }
485
486 $oldx = $this->lastx;
487 $oldy = $this->lasty;
488 $dir = $this->NormAngle($dir);
489
490 if( !is_numeric($dir) ) {
491 if( $dir=="h" ) $dir=0;
492 elseif( $dir=="v" ) $dir=90;
493 else JpGraphError::RaiseL(25090,$dir);//(" Unknown direction specified in call to StrokeBoxedText() [$dir]");
494 }
495
496 $width=$this->GetTextWidth($txt,0) + 2*$xmarg;
497 $height=$this->GetTextHeight($txt,0) + 2*$ymarg ;
498 $rect_width=$this->GetBBoxWidth($txt,$dir) ;
499 $rect_height=$this->GetBBoxHeight($txt,$dir) ;
500
501 $baseline_offset = $this->bbox_cache[1]-1;
502
503 if( $this->text_halign=="center" ) {
504 if( $dir >= 0 && $dir <= 90 ) {
505
506 $x -= $rect_width/2;
507 $x += sin($dir*M_PI/180)*$height;
508 $y += $rect_height/2;
509
510 } elseif( $dir >= 270 && $dir <= 360 ) {
511
512 $x -= $rect_width/2;
513 $y -= $rect_height/2;
514 $y += cos($dir*M_PI/180)*$height;
515
516 } elseif( $dir >= 90 && $dir <= 180 ) {
517
518 $x += $rect_width/2;
519 $y += $rect_height/2;
520 $y += cos($dir*M_PI/180)*$height;
521
522 }
523 else {
524 // $dir > 180 && $dir < 270
525 $x += $rect_width/2;
526 $x += sin($dir*M_PI/180)*$height;
527 $y -= $rect_height/2;
528 }
529 }
530
531 // Rotate the box around this point
532 $this->SetCenter($x,$y);
533 $olda = $this->SetAngle(-$dir);
534
535 // We need to use adjusted coordinats for the box to be able
536 // to draw the box below the baseline. This cannot be done before since
537 // the rotating point must be the original x,y since that is arounbf the
538 // point where the text will rotate and we cannot change this since
539 // that is where the GD/GreeType will rotate the text
540
541
542 // For smaller <14pt font we need to do some additional
543 // adjustments to make it look good
544 if( $this->font_size < 14 ) {
545 $x -= 2;
546 $y += 2;
547 }
548 else {
549 // $y += $baseline_offset;
550 }
551
552 if( $shadowcolor ) {
553 $this->PushColor($shadowcolor);
554 $this->FilledRectangle($x-$xmarg+$dropwidth,$y+$ymarg+$dropwidth-$height,
555 $x+$width+$dropwidth,$y+$ymarg+$dropwidth);
556 //$cornerradius);
557 $this->PopColor();
558 $this->PushColor($fcolor);
559 $this->FilledRectangle($x-$xmarg, $y+$ymarg-$height,
560 $x+$width, $y+$ymarg);
561 //$cornerradius);
562 $this->PopColor();
563 $this->PushColor($bcolor);
564 $this->Rectangle($x-$xmarg,$y+$ymarg-$height,
565 $x+$width,$y+$ymarg);
566 //$cornerradius);
567 $this->PopColor();
568 }
569 else {
570 if( $fcolor ) {
571 $oc=$this->current_color;
572 $this->SetColor($fcolor);
573 $this->FilledRectangle($x-$xmarg,$y+$ymarg-$height,$x+$width,$y+$ymarg);//,$cornerradius);
574 $this->current_color=$oc;
575 }
576 if( $bcolor ) {
577 $oc=$this->current_color;
578 $this->SetColor($bcolor);
579 $this->Rectangle($x-$xmarg,$y+$ymarg-$height,$x+$width,$y+$ymarg);//,$cornerradius);
580 $this->current_color=$oc;
581 }
582 }
583
584 if( $this->font_size < 14 ) {
585 $x += 2;
586 $y -= 2;
587 }
588 else {
589
590 // Restore the original y before we stroke the text
591 // $y -= $baseline_offset;
592
593 }
594
595 $this->SetCenter(0,0);
596 $this->SetAngle($olda);
597
598 $h=$this->text_halign;
599 $v=$this->text_valign;
600 if( $this->text_halign == 'center') {
601 $this->SetTextAlign('center','basepoint');
602 }
603 else {
604 $this->SetTextAlign('basepoint','basepoint');
605 }
606
607 $debug=false;
608 $this->StrokeText($x, $y, $txt, $dir, $paragraph_align,$debug);
609
610 $bb = array($x-$xmarg, $y+$height-$ymarg,
611 $x+$width, $y+$height-$ymarg,
612 $x+$width, $y-$ymarg,
613 $x-$xmarg, $y-$ymarg);
614
615 $this->SetTextAlign($h,$v);
616 $this->SetAngle($olda);
617
618 $this->lastx = $oldx;
619 $this->lasty = $oldy;
620
621 return $bb;
622 }
623
624 // Set text alignment
[2]625 function SetTextAlign($halign,$valign="bottom") {
[284]626 $this->text_halign=$halign;
627 $this->text_valign=$valign;
[2]628 }
629
[284]630 function _StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,&$aBoundingBox,$aDebug=false) {
[2]631
[284]632 if( is_numeric($dir) && $dir!=90 && $dir!=0)
633 JpGraphError::RaiseL(25091);//(" Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
[2]634
[284]635 $h=$this->GetTextHeight($txt);
636 $fh=$this->GetFontHeight();
637 $w=$this->GetTextWidth($txt);
638
639 if( $this->text_halign=="right") {
640 $x -= $dir==0 ? $w : $h;
641 }
642 elseif( $this->text_halign=="center" ) {
643 // For center we subtract 1 pixel since this makes the middle
644 // be prefectly in the middle
645 $x -= $dir==0 ? $w/2-1 : $h/2;
646 }
647 if( $this->text_valign=="top" ) {
648 $y += $dir==0 ? $h : $w;
649 }
650 elseif( $this->text_valign=="center" ) {
651 $y += $dir==0 ? $h/2 : $w/2;
652 }
653
654 $use_font = $this->font_family;
655
656 if( $dir==90 ) {
657 imagestringup($this->img,$use_font,$x,$y,$txt,$this->current_color);
658 $aBoundingBox = array(round($x),round($y),round($x),round($y-$w),round($x+$h),round($y-$w),round($x+$h),round($y));
[2]659 if( $aDebug ) {
[284]660 // Draw bounding box
661 $this->PushColor('green');
662 $this->Polygon($aBoundingBox,true);
663 $this->PopColor();
664 }
665 }
666 else {
667 if( preg_match('/\n/',$txt) ) {
668 $tmp = preg_split('/\n/',$txt);
669 for($i=0; $i < count($tmp); ++$i) {
670 $w1 = $this->GetTextWidth($tmp[$i]);
671 if( $paragraph_align=="left" ) {
672 imagestring($this->img,$use_font,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
673 }
674 elseif( $paragraph_align=="right" ) {
675 imagestring($this->img,$use_font,$x+($w-$w1),$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
676 }
677 else {
678 imagestring($this->img,$use_font,$x+$w/2-$w1/2,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
679 }
680 }
681 }
682 else {
683 //Put the text
684 imagestring($this->img,$use_font,$x,$y-$h+1,$txt,$this->current_color);
685 }
[2]686 if( $aDebug ) {
[284]687 // Draw the bounding rectangle and the bounding box
688 $p1 = array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y));
[2]689
[284]690 // Draw bounding box
691 $this->PushColor('green');
692 $this->Polygon($p1,true);
693 $this->PopColor();
694
[2]695 }
[284]696 $aBoundingBox=array(round($x),round($y),round($x),round($y-$h),round($x+$w),round($y-$h),round($x+$w),round($y));
697 }
[2]698 }
699
700 function AddTxtCR($aTxt) {
[284]701 // If the user has just specified a '\n'
702 // instead of '\n\t' we have to add '\r' since
703 // the width will be too muchy otherwise since when
704 // we print we stroke the individually lines by hand.
705 $e = explode("\n",$aTxt);
706 $n = count($e);
707 for($i=0; $i<$n; ++$i) {
708 $e[$i]=str_replace("\r","",$e[$i]);
709 }
710 return implode("\n\r",$e);
[2]711 }
712
[284]713 function NormAngle($a) {
714 // Normalize angle in degrees
715 // Normalize angle to be between 0-360
716 while( $a > 360 )
717 $a -= 360;
718 while( $a < -360 )
719 $a += 360;
720 if( $a < 0 )
721 $a = 360 + $a;
722 return $a;
723 }
724
725 function imagettfbbox_fixed($size, $angle, $fontfile, $text) {
726
727
728 if( ! USE_LIBRARY_IMAGETTFBBOX ) {
729
730 $bbox = @imagettfbbox($size, $angle, $fontfile, $text);
731 if( $bbox === false ) {
732 JpGraphError::RaiseL(25092,$this->font_file);
733 //("There is either a configuration problem with TrueType or a problem reading font file (".$this->font_file."). Make sure file exists and is in a readable place for the HTTP process. (If 'basedir' restriction is enabled in PHP then the font file must be located in the document root.). It might also be a wrongly installed FreeType library. Try uppgrading to at least FreeType 2.1.13 and recompile GD with the correct setup so it can find the new FT library.");
734 }
735 $this->bbox_cache = $bbox;
736 return $bbox;
737 }
738
739 // The built in imagettfbbox is buggy for angles != 0 so
740 // we calculate this manually by getting the bounding box at
741 // angle = 0 and then rotate the bounding box manually
742 $bbox = @imagettfbbox($size, 0, $fontfile, $text);
743 if( $bbox === false ) {
744 JpGraphError::RaiseL(25092,$this->font_file);
745 //("There is either a configuration problem with TrueType or a problem reading font file (".$this->font_file."). Make sure file exists and is in a readable place for the HTTP process. (If 'basedir' restriction is enabled in PHP then the font file must be located in the document root.). It might also be a wrongly installed FreeType library. Try uppgrading to at least FreeType 2.1.13 and recompile GD with the correct setup so it can find the new FT library.");
746 }
747
748 $angle = $this->NormAngle($angle);
749
750 $a = $angle*M_PI/180;
751 $ca = cos($a);
752 $sa = sin($a);
753 $ret = array();
754
755 // We always add 1 pixel to the left since the left edge of the bounding
756 // box is sometimes coinciding with the first pixel of the text
757 //$bbox[0] -= 1;
758 //$bbox[6] -= 1;
759
760 // For roatated text we need to add extra width for rotated
761 // text since the kerning and stroking of the TTF is not the same as for
762 // text at a 0 degree angle
763
764 if( $angle > 0.001 && abs($angle-360) > 0.001 ) {
765 $h = abs($bbox[7]-$bbox[1]);
766 $w = abs($bbox[2]-$bbox[0]);
767
768 $bbox[0] -= 2;
769 $bbox[6] -= 2;
770 // The width is underestimated so compensate for that
771 $bbox[2] += round($w*0.06);
772 $bbox[4] += round($w*0.06);
773
774 // and we also need to compensate with increased height
775 $bbox[5] -= round($h*0.1);
776 $bbox[7] -= round($h*0.1);
777
778 if( $angle > 90 ) {
779 // For angles > 90 we also need to extend the height further down
780 // by the baseline since that is also one more problem
781 $bbox[1] += round($h*0.15);
782 $bbox[3] += round($h*0.15);
783
784 // and also make it slighty less height
785 $bbox[7] += round($h*0.05);
786 $bbox[5] += round($h*0.05);
787
788 // And we need to move the box slightly top the rright (from a tetx perspective)
789 $bbox[0] += round($w*0.02);
790 $bbox[6] += round($w*0.02);
791
792 if( $angle > 180 ) {
793 // And we need to move the box slightly to the left (from a text perspective)
794 $bbox[0] -= round($w*0.02);
795 $bbox[6] -= round($w*0.02);
796 $bbox[2] -= round($w*0.02);
797 $bbox[4] -= round($w*0.02);
798
799 }
800
801 }
802 for($i = 0; $i < 7; $i += 2) {
803 $ret[$i] = round($bbox[$i] * $ca + $bbox[$i+1] * $sa);
804 $ret[$i+1] = round($bbox[$i+1] * $ca - $bbox[$i] * $sa);
805 }
806 $this->bbox_cache = $ret;
807 return $ret;
808 }
809 else {
810 $this->bbox_cache = $bbox;
811 return $bbox;
812 }
813 }
814
815 // Deprecated
[2]816 function GetTTFBBox($aTxt,$aAngle=0) {
[284]817 $bbox = $this->imagettfbbox_fixed($this->font_size,$aAngle,$this->font_file,$aTxt);
818 return $bbox;
[2]819 }
820
821 function GetBBoxTTF($aTxt,$aAngle=0) {
[284]822 // Normalize the bounding box to become a minimum
823 // enscribing rectangle
[2]824
[284]825 $aTxt = $this->AddTxtCR($aTxt);
[2]826
[284]827 if( !is_readable($this->font_file) ) {
828 JpGraphError::RaiseL(25093,$this->font_file);
829 //('Can not read font file ('.$this->font_file.') in call to Image::GetBBoxTTF. Please make sure that you have set a font before calling this method and that the font is installed in the TTF directory.');
830 }
831 $bbox = $this->imagettfbbox_fixed($this->font_size,$aAngle,$this->font_file,$aTxt);
[2]832
[284]833 if( $aAngle==0 ) return $bbox;
834
835 if( $aAngle >= 0 ) {
836 if( $aAngle <= 90 ) { //<=0
837 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
838 $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
839 }
840 elseif( $aAngle <= 180 ) { //<= 2
841 $bbox = array($bbox[4],$bbox[7],$bbox[0],$bbox[7],
842 $bbox[0],$bbox[3],$bbox[4],$bbox[3]);
843 }
844 elseif( $aAngle <= 270 ) { //<= 3
845 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
846 $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
847 }
848 else {
849 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
850 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
851 }
852 }
853 elseif( $aAngle < 0 ) {
854 if( $aAngle <= -270 ) { // <= -3
855 $bbox = array($bbox[6],$bbox[1],$bbox[2],$bbox[1],
856 $bbox[2],$bbox[5],$bbox[6],$bbox[5]);
857 }
858 elseif( $aAngle <= -180 ) { // <= -2
859 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
860 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
861 }
862 elseif( $aAngle <= -90 ) { // <= -1
863 $bbox = array($bbox[2],$bbox[5],$bbox[6],$bbox[5],
864 $bbox[6],$bbox[1],$bbox[2],$bbox[1]);
865 }
866 else {
867 $bbox = array($bbox[0],$bbox[3],$bbox[4],$bbox[3],
868 $bbox[4],$bbox[7],$bbox[0],$bbox[7]);
869 }
870 }
871 return $bbox;
[2]872 }
873
874 function GetBBoxHeight($aTxt,$aAngle=0) {
[284]875 $box = $this->GetBBoxTTF($aTxt,$aAngle);
876 return abs($box[7]-$box[1]);
[2]877 }
878
879 function GetBBoxWidth($aTxt,$aAngle=0) {
[284]880 $box = $this->GetBBoxTTF($aTxt,$aAngle);
881 return $box[2]-$box[0]+1;
[2]882 }
883
884
[284]885 function _StrokeTTF($x,$y,$txt,$dir,$paragraph_align,&$aBoundingBox,$debug=false) {
[2]886
[284]887 // Setup default inter line margin for paragraphs to be
888 // 3% of the font height.
889 $ConstLineSpacing = 0.03 ;
[2]890
[284]891 // Remember the anchor point before adjustment
892 if( $debug ) {
893 $ox=$x;
894 $oy=$y;
895 }
[2]896
[284]897 if( !preg_match('/\n/',$txt) || ($dir>0 && preg_match('/\n/',$txt)) ) {
898 // Format a single line
[2]899
[284]900 $txt = $this->AddTxtCR($txt);
901 $bbox=$this->GetBBoxTTF($txt,$dir);
902 $width = $this->GetBBoxWidth($txt,$dir);
903 $height = $this->GetBBoxHeight($txt,$dir);
[2]904
[284]905 // The special alignment "basepoint" is mostly used internally
906 // in the library. This will put the anchor position at the left
907 // basepoint of the tetx. This is the default anchor point for
908 // TTF text.
[2]909
[284]910 if( $this->text_valign != 'basepoint' ) {
911 // Align x,y ot lower left corner of bbox
912
[2]913
[284]914 if( $this->text_halign=='right' ) {
915 $x -= $width;
916 $x -= $bbox[0];
917 }
918 elseif( $this->text_halign=='center' ) {
919 $x -= $width/2;
920 $x -= $bbox[0];
921 }
922 elseif( $this->text_halign=='baseline' ) {
923 // This is only support for text at 90 degree !!
924 // Do nothing the text is drawn at baseline by default
925 }
[2]926
[284]927 if( $this->text_valign=='top' ) {
928 $y -= $bbox[1]; // Adjust to bottom of text
929 $y += $height;
930 }
931 elseif( $this->text_valign=='center' ) {
932 $y -= $bbox[1]; // Adjust to bottom of text
933 $y += $height/2;
934 }
935 elseif( $this->text_valign=='baseline' ) {
936 // This is only support for text at 0 degree !!
937 // Do nothing the text is drawn at baseline by default
938 }
939 }
940 ImageTTFText ($this->img, $this->font_size, $dir, $x, $y,
941 $this->current_color,$this->font_file,$txt);
[2]942
[284]943 // Calculate and return the co-ordinates for the bounding box
944 $box = $this->imagettfbbox_fixed($this->font_size,$dir,$this->font_file,$txt);
945 $p1 = array();
[2]946
[284]947 for($i=0; $i < 4; ++$i) {
948 $p1[] = round($box[$i*2]+$x);
949 $p1[] = round($box[$i*2+1]+$y);
950 }
951 $aBoundingBox = $p1;
952
953 // Debugging code to highlight the bonding box and bounding rectangle
954 // For text at 0 degrees the bounding box and bounding rectangle are the
955 // same
[2]956 if( $debug ) {
[284]957 // Draw the bounding rectangle and the bounding box
[2]958
[284]959 $p = array();
960 $p1 = array();
961
962 for($i=0; $i < 4; ++$i) {
963 $p[] = $bbox[$i*2]+$x ;
964 $p[] = $bbox[$i*2+1]+$y;
965 $p1[] = $box[$i*2]+$x ;
966 $p1[] = $box[$i*2+1]+$y ;
967 }
968
969 // Draw bounding box
970 $this->PushColor('green');
971 $this->Polygon($p1,true);
972 $this->PopColor();
973
974 // Draw bounding rectangle
975 $this->PushColor('darkgreen');
976 $this->Polygon($p,true);
977 $this->PopColor();
978
979 // Draw a cross at the anchor point
980 $this->PushColor('red');
981 $this->Line($ox-15,$oy,$ox+15,$oy);
982 $this->Line($ox,$oy-15,$ox,$oy+15);
983 $this->PopColor();
[2]984 }
[284]985 }
986 else {
987 // Format a text paragraph
988 $fh=$this->GetFontHeight();
[2]989
[284]990 // Line margin is 25% of font height
991 $linemargin=round($fh*$ConstLineSpacing);
992 $fh += $linemargin;
993 $w=$this->GetTextWidth($txt);
[2]994
[284]995 $y -= $linemargin/2;
996 $tmp = preg_split('/\n/',$txt);
997 $nl = count($tmp);
998 $h = $nl * $fh;
[2]999
[284]1000 if( $this->text_halign=='right') {
1001 $x -= $dir==0 ? $w : $h;
1002 }
1003 elseif( $this->text_halign=='center' ) {
1004 $x -= $dir==0 ? $w/2 : $h/2;
1005 }
[2]1006
[284]1007 if( $this->text_valign=='top' ) {
1008 $y += $dir==0 ? $h : $w;
1009 }
1010 elseif( $this->text_valign=='center' ) {
1011 $y += $dir==0 ? $h/2 : $w/2;
1012 }
[2]1013
[284]1014 // Here comes a tricky bit.
1015 // Since we have to give the position for the string at the
1016 // baseline this means thaht text will move slightly up
1017 // and down depending on any of it's character descend below
1018 // the baseline, for example a 'g'. To adjust the Y-position
1019 // we therefore adjust the text with the baseline Y-offset
1020 // as used for the current font and size. This will keep the
1021 // baseline at a fixed positoned disregarding the actual
1022 // characters in the string.
1023 $standardbox = $this->GetTTFBBox('Gg',$dir);
1024 $yadj = $standardbox[1];
1025 $xadj = $standardbox[0];
1026 $aBoundingBox = array();
1027 for($i=0; $i < $nl; ++$i) {
1028 $wl = $this->GetTextWidth($tmp[$i]);
1029 $bbox = $this->GetTTFBBox($tmp[$i],$dir);
1030 if( $paragraph_align=='left' ) {
1031 $xl = $x;
1032 }
1033 elseif( $paragraph_align=='right' ) {
1034 $xl = $x + ($w-$wl);
1035 }
1036 else {
1037 // Center
1038 $xl = $x + $w/2 - $wl/2 ;
1039 }
[2]1040
[284]1041 // In theory we should adjust with full pre-lead to get the lines
1042 // lined up but this doesn't look good so therfore we only adjust with
1043 // half th pre-lead
1044 $xl -= $bbox[0]/2;
1045 $yl = $y - $yadj;
1046 //$xl = $xl- $xadj;
1047 ImageTTFText($this->img, $this->font_size, $dir, $xl, $yl-($h-$fh)+$fh*$i,
1048 $this->current_color,$this->font_file,$tmp[$i]);
[2]1049
[284]1050 // echo "xl=$xl,".$tmp[$i]." <br>";
1051 if( $debug ) {
1052 // Draw the bounding rectangle around each line
1053 $box=@ImageTTFBBox($this->font_size,$dir,$this->font_file,$tmp[$i]);
1054 $p = array();
1055 for($j=0; $j < 4; ++$j) {
1056 $p[] = $bbox[$j*2]+$xl;
1057 $p[] = $bbox[$j*2+1]+$yl-($h-$fh)+$fh*$i;
1058 }
[2]1059
[284]1060 // Draw bounding rectangle
1061 $this->PushColor('darkgreen');
1062 $this->Polygon($p,true);
1063 $this->PopColor();
1064 }
1065 }
[2]1066
[284]1067 // Get the bounding box
1068 $bbox = $this->GetBBoxTTF($txt,$dir);
1069 for($j=0; $j < 4; ++$j) {
1070 $bbox[$j*2]+= round($x);
1071 $bbox[$j*2+1]+= round($y - ($h-$fh) - $yadj);
1072 }
1073 $aBoundingBox = $bbox;
1074
1075 if( $debug ) {
1076 // Draw a cross at the anchor point
1077 $this->PushColor('red');
1078 $this->Line($ox-25,$oy,$ox+25,$oy);
1079 $this->Line($ox,$oy-25,$ox,$oy+25);
1080 $this->PopColor();
1081 }
1082
1083 }
[2]1084 }
[284]1085
[2]1086 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
1087
[284]1088 $x = round($x);
1089 $y = round($y);
[2]1090
[284]1091 // Do special language encoding
1092 $txt = $this->langconv->Convert($txt,$this->font_family);
[2]1093
[284]1094 if( !is_numeric($dir) ) {
1095 JpGraphError::RaiseL(25094);//(" Direction for text most be given as an angle between 0 and 90.");
1096 }
1097
1098 if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {
1099 $this->_StrokeBuiltinFont($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug);
1100 }
1101 elseif( $this->font_family >= _FIRST_FONT && $this->font_family <= _LAST_FONT) {
1102 $this->_StrokeTTF($x,$y,$txt,$dir,$paragraph_align,$boundingbox,$debug);
1103 }
1104 else {
1105 JpGraphError::RaiseL(25095);//(" Unknown font font family specification. ");
1106 }
1107 return $boundingbox;
[2]1108 }
[284]1109
[2]1110 function SetMargin($lm,$rm,$tm,$bm) {
[284]1111
1112 $this->left_margin=$lm;
1113 $this->right_margin=$rm;
1114 $this->top_margin=$tm;
1115 $this->bottom_margin=$bm;
1116
1117 $this->plotwidth = $this->width - $this->left_margin - $this->right_margin;
1118 $this->plotheight = $this->height - $this->top_margin - $this->bottom_margin;
1119
1120 if( $this->width > 0 && $this->height > 0 ) {
1121 if( $this->plotwidth < 0 || $this->plotheight < 0 ) {
1122 JpGraphError::RaiseL(25130, $this->plotwidth, $this->plotheight);
1123 //JpGraphError::raise("To small plot area. ($lm,$rm,$tm,$bm : $this->plotwidth x $this->plotheight). With the given image size and margins there is to little space left for the plot. Increase the plot size or reduce the margins.");
1124 }
1125 }
[2]1126 }
1127
1128 function SetTransparent($color) {
[284]1129 imagecolortransparent ($this->img,$this->rgb->allocate($color));
[2]1130 }
[284]1131
[2]1132 function SetColor($color,$aAlpha=0) {
[284]1133 $this->current_color_name = $color;
1134 $this->current_color=$this->rgb->allocate($color,$aAlpha);
1135 if( $this->current_color == -1 ) {
1136 $tc=imagecolorstotal($this->img);
1137 JpGraphError::RaiseL(25096);
1138 //("Can't allocate any more colors. Image has already allocated maximum of <b>$tc colors</b>. This might happen if you have anti-aliasing turned on together with a background image or perhaps gradient fill since this requires many, many colors. Try to turn off anti-aliasing. If there is still a problem try downgrading the quality of the background image to use a smaller pallete to leave some entries for your graphs. You should try to limit the number of colors in your background image to 64. If there is still problem set the constant DEFINE(\"USE_APPROX_COLORS\",true); in jpgraph.php This will use approximative colors when the palette is full. Unfortunately there is not much JpGraph can do about this since the palette size is a limitation of current graphic format and what the underlying GD library suppports.");
1139 }
1140 return $this->current_color;
[2]1141 }
[284]1142
[2]1143 function PushColor($color) {
[284]1144 if( $color != "" ) {
1145 $this->colorstack[$this->colorstackidx]=$this->current_color_name;
1146 $this->colorstack[$this->colorstackidx+1]=$this->current_color;
1147 $this->colorstackidx+=2;
1148 $this->SetColor($color);
1149 }
1150 else {
1151 JpGraphError::RaiseL(25097);//("Color specified as empty string in PushColor().");
1152 }
[2]1153 }
[284]1154
[2]1155 function PopColor() {
[284]1156 if( $this->colorstackidx < 1 ) {
1157 JpGraphError::RaiseL(25098);//(" Negative Color stack index. Unmatched call to PopColor()");
1158 }
1159 $this->current_color=$this->colorstack[--$this->colorstackidx];
1160 $this->current_color_name=$this->colorstack[--$this->colorstackidx];
[2]1161 }
[284]1162
1163
[2]1164 function SetLineWeight($weight) {
[284]1165 $old = $this->line_weight;
1166 imagesetthickness($this->img,$weight);
1167 $this->line_weight = $weight;
1168 return $old;
[2]1169 }
[284]1170
[2]1171 function SetStartPoint($x,$y) {
[284]1172 $this->lastx=round($x);
1173 $this->lasty=round($y);
[2]1174 }
[284]1175
[2]1176 function Arc($cx,$cy,$w,$h,$s,$e) {
[284]1177 // GD Arc doesn't like negative angles
1178 while( $s < 0) $s += 360;
1179 while( $e < 0) $e += 360;
1180 imagearc($this->img,round($cx),round($cy),round($w),round($h),$s,$e,$this->current_color);
[2]1181 }
[284]1182
[2]1183 function FilledArc($xc,$yc,$w,$h,$s,$e,$style='') {
[284]1184 $s = round($s);
1185 $e = round($e);
1186 while( $s < 0 ) $s += 360;
1187 while( $e < 0 ) $e += 360;
1188 if( $style=='' )
1189 $style=IMG_ARC_PIE;
1190 if( abs($s-$e) > 0 ) {
1191 imagefilledarc($this->img,round($xc),round($yc),round($w),round($h),$s,$e,$this->current_color,$style);
1192// $this->DrawImageSmoothArc($this->img,round($xc),round($yc),round($w),round($h),$s,$e,$this->current_color,$style);
1193 }
[2]1194 }
1195
1196 function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
[284]1197 $this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
[2]1198 }
1199
1200 function CakeSlice($xc,$yc,$w,$h,$s,$e,$fillcolor="",$arccolor="") {
[284]1201 $s = round($s); $e = round($e);
1202 $w = round($w); $h = round($h);
1203 $xc = round($xc); $yc = round($yc);
1204 if( $s == $e ) {
1205 // A full circle. We draw this a plain circle
1206 $this->PushColor($fillcolor);
1207 imagefilledellipse($this->img,$xc,$yc,2*$w,2*$h,$this->current_color);
1208
1209 // If antialiasing is used then we often don't have any color no the surrounding
1210 // arc. So, we need to check for this special case so we don't send an empty
1211 // color to the push function. In this case we use the fill color for the arc as well
1212 if( $arccolor != '' ) {
1213 $this->PopColor();
1214 $this->PushColor($arccolor);
1215 }
1216 imageellipse($this->img,$xc,$yc,2*$w,2*$h,$this->current_color);
1217 $this->Line($xc,$yc,cos($s*M_PI/180)*$w+$xc,$yc+sin($s*M_PI/180)*$h);
1218 $this->PopColor();
1219 }
1220 else {
1221 $this->PushColor($fillcolor);
1222 $this->FilledArc($xc,$yc,2*$w,2*$h,$s,$e);
1223 $this->PopColor();
1224 if( $arccolor != "" ) {
1225 $this->PushColor($arccolor);
1226 // We add 2 pixels to make the Arc() better aligned with
1227 // the filled arc.
1228 imagefilledarc($this->img,$xc,$yc,2*$w,2*$h,$s,$e,$this->current_color,IMG_ARC_NOFILL | IMG_ARC_EDGED ) ;
1229 $this->PopColor();
1230 }
1231 }
[2]1232 }
1233
1234 function Ellipse($xc,$yc,$w,$h) {
[284]1235 $this->Arc($xc,$yc,$w,$h,0,360);
[2]1236 }
[284]1237
[2]1238 function Circle($xc,$yc,$r) {
[284]1239 imageellipse($this->img,round($xc),round($yc),$r*2,$r*2,$this->current_color);
1240// $this->DrawImageSmoothArc($this->img,round($xc),round($yc),$r*2+1,$r*2+1,0,360,$this->current_color);
1241// $this->imageSmoothCircle($this->img, round($xc),round($yc), $r*2+1, $this->current_color);
[2]1242 }
[284]1243
[2]1244 function FilledCircle($xc,$yc,$r) {
[284]1245 imagefilledellipse($this->img,round($xc),round($yc),2*$r,2*$r,$this->current_color);
1246// $this->DrawImageSmoothArc($this->img, round($xc), round($yc), 2*$r, 2*$r, 0, 360, $this->current_color);
[2]1247 }
[284]1248
[2]1249 // Linear Color InterPolation
1250 function lip($f,$t,$p) {
[284]1251 $p = round($p,1);
1252 $r = $f[0] + ($t[0]-$f[0])*$p;
1253 $g = $f[1] + ($t[1]-$f[1])*$p;
1254 $b = $f[2] + ($t[2]-$f[2])*$p;
1255 return array($r,$g,$b);
[2]1256 }
1257
1258 // Set line style dashed, dotted etc
1259 function SetLineStyle($s) {
[284]1260 if( is_numeric($s) ) {
1261 if( $s<1 || $s>4 ) {
1262 JpGraphError::RaiseL(25101,$s);//(" Illegal numeric argument to SetLineStyle(): ($s)");
1263 }
1264 }
1265 elseif( is_string($s) ) {
1266 if( $s == "solid" ) $s=1;
1267 elseif( $s == "dotted" ) $s=2;
1268 elseif( $s == "dashed" ) $s=3;
1269 elseif( $s == "longdashed" ) $s=4;
1270 else {
1271 JpGraphError::RaiseL(25102,$s);//(" Illegal string argument to SetLineStyle(): $s");
1272 }
1273 }
1274 else {
1275 JpGraphError::RaiseL(25103,$s);//(" Illegal argument to SetLineStyle $s");
1276 }
1277 $old = $this->line_style;
1278 $this->line_style=$s;
1279 return $old;
[2]1280 }
[284]1281
[2]1282 // Same as Line but take the line_style into account
[284]1283 function StyleLine($x1,$y1,$x2,$y2,$aStyle='', $from_grid_class = false) {
1284 if( $this->line_weight <= 0 ) return;
[2]1285
[284]1286 if( $aStyle === '' ) {
1287 $aStyle = $this->line_style;
1288 }
[2]1289
[284]1290 $dashed_line_method = 'DashedLine';
1291 if ($from_grid_class) {
1292 $dashed_line_method = 'DashedLineForGrid';
1293 }
[2]1294
[284]1295 // Add error check since dashed line will only work if anti-alias is disabled
1296 // this is a limitation in GD
1297
1298 if( $aStyle == 1 ) {
1299 // Solid style. We can handle anti-aliasing for this
1300 $this->Line($x1,$y1,$x2,$y2);
1301 }
1302 else {
1303 // Since the GD routines doesn't handle AA for styled line
1304 // we have no option than to turn it off to get any lines at
1305 // all if the weight > 1
1306 $oldaa = $this->GetAntiAliasing();
1307 if( $oldaa && $this->line_weight > 1 ) {
1308 $this->SetAntiAliasing(false);
1309 }
1310
1311 switch( $aStyle ) {
1312 case 2: // Dotted
1313 $this->$dashed_line_method($x1,$y1,$x2,$y2,2,6);
1314 break;
1315 case 3: // Dashed
1316 $this->$dashed_line_method($x1,$y1,$x2,$y2,5,9);
1317 break;
1318 case 4: // Longdashes
1319 $this->$dashed_line_method($x1,$y1,$x2,$y2,9,13);
1320 break;
1321 default:
1322 JpGraphError::RaiseL(25104,$this->line_style);//(" Unknown line style: $this->line_style ");
1323 break;
1324 }
1325 if( $oldaa ) {
1326 $this->SetAntiAliasing(true);
1327 }
1328 }
[2]1329 }
[284]1330
[2]1331 function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
1332
[284]1333 if( $this->line_weight <= 0 ) return;
[2]1334
[284]1335 // Add error check to make sure anti-alias is not enabled.
1336 // Dashed line does not work with anti-alias enabled. This
1337 // is a limitation in GD.
1338 if( $this->use_anti_aliasing ) {
1339// JpGraphError::RaiseL(25129); // Anti-alias can not be used with dashed lines. Please disable anti-alias or use solid lines.
1340 }
1341
1342 $x1 = round($x1);
1343 $x2 = round($x2);
1344 $y1 = round($y1);
1345 $y2 = round($y2);
[2]1346
[284]1347 $dash_length *= SUPERSAMPLING_SCALE;
1348 $dash_space *= SUPERSAMPLING_SCALE;
[2]1349
[284]1350 $style = array_fill(0,$dash_length,$this->current_color);
1351 $style = array_pad($style,$dash_space,IMG_COLOR_TRANSPARENT);
1352 imagesetstyle($this->img, $style);
1353 imageline($this->img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED);
[2]1354
[284]1355 $this->lastx = $x2;
1356 $this->lasty = $y2;
1357 }
1358
1359 function DashedLineForGrid($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
1360
1361 if( $this->line_weight <= 0 ) return;
1362
1363 // Add error check to make sure anti-alias is not enabled.
1364 // Dashed line does not work with anti-alias enabled. This
1365 // is a limitation in GD.
1366 if( $this->use_anti_aliasing ) {
1367// JpGraphError::RaiseL(25129); // Anti-alias can not be used with dashed lines. Please disable anti-alias or use solid lines.
1368 }
1369
1370 $x1 = round($x1);
1371 $x2 = round($x2);
1372 $y1 = round($y1);
1373 $y2 = round($y2);
1374
1375 /*
1376 $dash_length *= $this->scale;
1377 $dash_space *= $this->scale;
1378 */
1379
1380 $dash_length = 2;
1381 $dash_length = 4;
1382 imagesetthickness($this->img, 1);
1383 $style = array_fill(0,$dash_length, $this->current_color); //hexdec('CCCCCC'));
1384 $style = array_pad($style,$dash_space,IMG_COLOR_TRANSPARENT);
1385 imagesetstyle($this->img, $style);
1386 imageline($this->img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED);
1387
1388 $this->lastx = $x2;
1389 $this->lasty = $y2;
1390 }
1391
[2]1392 function Line($x1,$y1,$x2,$y2) {
1393
[284]1394 if( $this->line_weight <= 0 ) return;
[2]1395
[284]1396 $x1 = round($x1);
1397 $x2 = round($x2);
1398 $y1 = round($y1);
1399 $y2 = round($y2);
[2]1400
[284]1401 imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
1402// $this->DrawLine($this->img, $x1, $y1, $x2, $y2, $this->line_weight, $this->current_color);
1403 $this->lastx=$x2;
1404 $this->lasty=$y2;
[2]1405 }
1406
1407 function Polygon($p,$closed=FALSE,$fast=FALSE) {
1408
[284]1409 if( $this->line_weight <= 0 ) return;
[2]1410
[284]1411 $n=count($p);
1412 $oldx = $p[0];
1413 $oldy = $p[1];
1414 if( $fast ) {
1415 for( $i=2; $i < $n; $i+=2 ) {
1416 imageline($this->img,$oldx,$oldy,$p[$i],$p[$i+1],$this->current_color);
1417 $oldx = $p[$i];
1418 $oldy = $p[$i+1];
1419 }
1420 if( $closed ) {
1421 imageline($this->img,$p[$n*2-2],$p[$n*2-1],$p[0],$p[1],$this->current_color);
1422 }
1423 }
1424 else {
1425 for( $i=2; $i < $n; $i+=2 ) {
1426 $this->StyleLine($oldx,$oldy,$p[$i],$p[$i+1]);
1427 $oldx = $p[$i];
1428 $oldy = $p[$i+1];
1429 }
1430 if( $closed ) {
1431 $this->StyleLine($oldx,$oldy,$p[0],$p[1]);
1432 }
1433 }
[2]1434 }
[284]1435
[2]1436 function FilledPolygon($pts) {
[284]1437 $n=count($pts);
1438 if( $n == 0 ) {
1439 JpGraphError::RaiseL(25105);//('NULL data specified for a filled polygon. Check that your data is not NULL.');
1440 }
1441 for($i=0; $i < $n; ++$i) {
1442 $pts[$i] = round($pts[$i]);
1443 }
1444 $old = $this->line_weight;
1445 imagesetthickness($this->img,1);
1446 imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
1447 $this->line_weight = $old;
1448 imagesetthickness($this->img,$old);
[2]1449 }
[284]1450
[2]1451 function Rectangle($xl,$yu,$xr,$yl) {
[284]1452 $this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl,$xl,$yu));
[2]1453 }
[284]1454
[2]1455 function FilledRectangle($xl,$yu,$xr,$yl) {
[284]1456 $this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
[2]1457 }
1458
1459 function FilledRectangle2($xl,$yu,$xr,$yl,$color1,$color2,$style=1) {
[284]1460 // Fill a rectangle with lines of two colors
1461 if( $style===1 ) {
1462 // Horizontal stripe
1463 if( $yl < $yu ) {
1464 $t = $yl; $yl=$yu; $yu=$t;
1465 }
1466 for( $y=$yu; $y <= $yl; ++$y) {
1467 $this->SetColor($color1);
1468 $this->Line($xl,$y,$xr,$y);
1469 ++$y;
1470 $this->SetColor($color2);
1471 $this->Line($xl,$y,$xr,$y);
1472 }
1473 }
1474 else {
1475 if( $xl < $xl ) {
1476 $t = $xl; $xl=$xr; $xr=$t;
1477 }
1478 for( $x=$xl; $x <= $xr; ++$x) {
1479 $this->SetColor($color1);
1480 $this->Line($x,$yu,$x,$yl);
1481 ++$x;
1482 $this->SetColor($color2);
1483 $this->Line($x,$yu,$x,$yl);
1484 }
1485 }
[2]1486 }
1487
[284]1488 function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=4,$shadow_color='darkgray',$useAlpha=true) {
1489 // This is complicated by the fact that we must also handle the case where
[2]1490 // the reactangle has no fill color
[284]1491 $xl = floor($xl);
1492 $yu = floor($yu);
1493 $xr = floor($xr);
1494 $yl = floor($yl);
1495 $this->PushColor($shadow_color);
1496 $shadowAlpha=0;
1497 $this->SetLineWeight(1);
1498 $this->SetLineStyle('solid');
1499 $basecolor = $this->rgb->Color($shadow_color);
1500 $shadow_color = array($basecolor[0],$basecolor[1],$basecolor[2],);
1501 for( $i=0; $i < $shadow_width; ++$i ) {
1502 $this->SetColor($shadow_color,$shadowAlpha);
1503 $this->Line($xr-$shadow_width+$i, $yu+$shadow_width,
1504 $xr-$shadow_width+$i, $yl-$shadow_width-1+$i);
1505 $this->Line($xl+$shadow_width, $yl-$shadow_width+$i,
1506 $xr-$shadow_width+$i, $yl-$shadow_width+$i);
1507 if( $useAlpha ) $shadowAlpha += 1.0/$shadow_width;
1508 }
1509
1510 $this->PopColor();
1511 if( $fcolor==false ) {
1512 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
1513 }
1514 else {
1515 $this->PushColor($fcolor);
1516 $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
1517 $this->PopColor();
1518 $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
1519 }
[2]1520 }
1521
1522 function FilledRoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
[284]1523 if( $r==0 ) {
1524 $this->FilledRectangle($xt,$yt,$xr,$yl);
1525 return;
1526 }
[2]1527
[284]1528 // To avoid overlapping fillings (which will look strange
1529 // when alphablending is enabled) we have no choice but
1530 // to fill the five distinct areas one by one.
[2]1531
[284]1532 // Center square
1533 $this->FilledRectangle($xt+$r,$yt+$r,$xr-$r,$yl-$r);
1534 // Top band
1535 $this->FilledRectangle($xt+$r,$yt,$xr-$r,$yt+$r);
1536 // Bottom band
1537 $this->FilledRectangle($xt+$r,$yl-$r,$xr-$r,$yl);
1538 // Left band
1539 $this->FilledRectangle($xt,$yt+$r,$xt+$r,$yl-$r);
1540 // Right band
1541 $this->FilledRectangle($xr-$r,$yt+$r,$xr,$yl-$r);
[2]1542
[284]1543 // Topleft & Topright arc
1544 $this->FilledArc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
1545 $this->FilledArc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
[2]1546
[284]1547 // Bottomleft & Bottom right arc
1548 $this->FilledArc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
1549 $this->FilledArc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
1550
[2]1551 }
1552
[284]1553 function RoundedRectangle($xt,$yt,$xr,$yl,$r=5) {
[2]1554
[284]1555 if( $r==0 ) {
1556 $this->Rectangle($xt,$yt,$xr,$yl);
1557 return;
1558 }
[2]1559
[284]1560 // Top & Bottom line
1561 $this->Line($xt+$r,$yt,$xr-$r,$yt);
1562 $this->Line($xt+$r,$yl,$xr-$r,$yl);
[2]1563
[284]1564 // Left & Right line
1565 $this->Line($xt,$yt+$r,$xt,$yl-$r);
1566 $this->Line($xr,$yt+$r,$xr,$yl-$r);
[2]1567
[284]1568 // Topleft & Topright arc
1569 $this->Arc($xt+$r,$yt+$r,$r*2,$r*2,180,270);
1570 $this->Arc($xr-$r,$yt+$r,$r*2,$r*2,270,360);
[2]1571
[284]1572 // Bottomleft & Bottomright arc
1573 $this->Arc($xt+$r,$yl-$r,$r*2,$r*2,90,180);
1574 $this->Arc($xr-$r,$yl-$r,$r*2,$r*2,0,90);
[2]1575 }
1576
1577 function FilledBevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='darkgray@0.4') {
[284]1578 $this->FilledRectangle($x1,$y1,$x2,$y2);
1579 $this->Bevel($x1,$y1,$x2,$y2,$depth,$color1,$color2);
[2]1580 }
1581
1582 function Bevel($x1,$y1,$x2,$y2,$depth=2,$color1='white@0.4',$color2='black@0.5') {
[284]1583 $this->PushColor($color1);
1584 for( $i=0; $i < $depth; ++$i ) {
1585 $this->Line($x1+$i,$y1+$i,$x1+$i,$y2-$i);
1586 $this->Line($x1+$i,$y1+$i,$x2-$i,$y1+$i);
1587 }
1588 $this->PopColor();
1589
1590 $this->PushColor($color2);
1591 for( $i=0; $i < $depth; ++$i ) {
1592 $this->Line($x1+$i,$y2-$i,$x2-$i,$y2-$i);
1593 $this->Line($x2-$i,$y1+$i,$x2-$i,$y2-$i-1);
1594 }
1595 $this->PopColor();
[2]1596 }
1597
1598 function StyleLineTo($x,$y) {
[284]1599 $this->StyleLine($this->lastx,$this->lasty,$x,$y);
1600 $this->lastx=$x;
1601 $this->lasty=$y;
[2]1602 }
[284]1603
[2]1604 function LineTo($x,$y) {
[284]1605 $this->Line($this->lastx,$this->lasty,$x,$y);
1606 $this->lastx=$x;
1607 $this->lasty=$y;
[2]1608 }
[284]1609
[2]1610 function Point($x,$y) {
[284]1611 imagesetpixel($this->img,round($x),round($y),$this->current_color);
[2]1612 }
[284]1613
[2]1614 function Fill($x,$y) {
[284]1615 imagefill($this->img,round($x),round($y),$this->current_color);
[2]1616 }
1617
1618 function FillToBorder($x,$y,$aBordColor) {
[284]1619 $bc = $this->rgb->allocate($aBordColor);
1620 if( $bc == -1 ) {
1621 JpGraphError::RaiseL(25106);//('Image::FillToBorder : Can not allocate more colors');
1622 }
1623 imagefilltoborder($this->img,round($x),round($y),$bc,$this->current_color);
[2]1624 }
1625
1626 function SetExpired($aFlg=true) {
[284]1627 $this->expired = $aFlg;
[2]1628 }
[284]1629
[2]1630 // Generate image header
1631 function Headers() {
1632
[284]1633 // In case we are running from the command line with the client version of
1634 // PHP we can't send any headers.
1635 $sapi = php_sapi_name();
1636 if( $sapi == 'cli' ) return;
1637
1638 // These parameters are set by headers_sent() but they might cause
1639 // an undefined variable error unless they are initilized
1640 $file='';
1641 $lineno='';
1642 if( headers_sent($file,$lineno) ) {
1643 $file=basename($file);
1644 $t = new ErrMsgText();
1645 $msg = $t->Get(10,$file,$lineno);
1646 die($msg);
1647 }
1648
1649 if ($this->expired) {
1650 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
1651 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
1652 header("Cache-Control: no-cache, must-revalidate");
1653 header("Pragma: no-cache");
1654 }
1655 header("Content-type: image/$this->img_format");
[2]1656 }
1657
1658 // Adjust image quality for formats that allow this
1659 function SetQuality($q) {
[284]1660 $this->quality = $q;
[2]1661 }
[284]1662
[2]1663 // Stream image to browser or to file
[284]1664 function Stream($aFile=NULL) {
1665 $this->DoSupersampling();
1666
1667 $func="image".$this->img_format;
1668 if( $this->img_format=="jpeg" && $this->quality != null ) {
1669 $res = @$func($this->img,$aFile,$this->quality);
1670
1671 if(!$res){
1672 if($aFile != NULL){
1673 JpGraphError::RaiseL(25107,$aFile);//("Can't write to file '$aFile'. Check that the process running PHP has enough permission.");
1674 }else{
1675 JpGraphError::RaiseL(25108);//("Can't stream image. This is most likely due to a faulty PHP/GD setup. Try to recompile PHP and use the built-in GD library that comes with PHP.");
1676 }
[2]1677
[284]1678 }
1679 }
1680 else {
1681 if( $aFile != NULL ) {
1682 $res = @$func($this->img,$aFile);
1683 if( !$res ) {
1684 JpGraphError::RaiseL(25107,$aFile);//("Can't write to file '$aFile'. Check that the process running PHP has enough permission.");
1685 }
1686 }
1687 else {
1688 $res = @$func($this->img);
1689 if( !$res ) {
1690 JpGraphError::RaiseL(25108);//("Can't stream image. This is most likely due to a faulty PHP/GD setup. Try to recompile PHP and use the built-in GD library that comes with PHP.");
1691 }
1692
1693 }
1694 }
[2]1695 }
[284]1696
1697 // Do SuperSampling using $scale
1698 function DoSupersampling() {
1699 if (SUPERSAMPLING_SCALE <= 1) {
1700 return $this->img;
1701 }
1702
1703 $dst_img = @imagecreatetruecolor($this->original_width, $this->original_height);
1704 imagecopyresampled($dst_img, $this->img, 0, 0, 0, 0, $this->original_width, $this->original_height, $this->width, $this->height);
1705 $this->Destroy();
1706 return $this->img = $dst_img;
1707 }
1708
1709 // Clear resources used by image (this is normally not used since all resources are/should be
1710 // returned when the script terminates
[2]1711 function Destroy() {
[284]1712 imagedestroy($this->img);
[2]1713 }
[284]1714
[2]1715 // Specify image format. Note depending on your installation
1716 // of PHP not all formats may be supported.
[284]1717 function SetImgFormat($aFormat,$aQuality=75) {
1718 $this->quality = $aQuality;
1719 $aFormat = strtolower($aFormat);
1720 $tst = true;
1721 $supported = imagetypes();
1722 if( $aFormat=="auto" ) {
1723 if( $supported & IMG_PNG ) $this->img_format="png";
1724 elseif( $supported & IMG_JPG ) $this->img_format="jpeg";
1725 elseif( $supported & IMG_GIF ) $this->img_format="gif";
1726 elseif( $supported & IMG_WBMP ) $this->img_format="wbmp";
1727 elseif( $supported & IMG_XPM ) $this->img_format="xpm";
1728 else {
1729 JpGraphError::RaiseL(25109);//("Your PHP (and GD-lib) installation does not appear to support any known graphic formats. You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images you must get the JPEG library. Please see the PHP docs for details.");
1730 }
1731 return true;
1732 }
1733 else {
1734 if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
1735 if( $aFormat=="jpeg" && !($supported & IMG_JPG) ) $tst=false;
1736 elseif( $aFormat=="png" && !($supported & IMG_PNG) ) $tst=false;
1737 elseif( $aFormat=="gif" && !($supported & IMG_GIF) ) $tst=false;
1738 elseif( $aFormat=="wbmp" && !($supported & IMG_WBMP) ) $tst=false;
1739 elseif( $aFormat=="xpm" && !($supported & IMG_XPM) ) $tst=false;
1740 else {
1741 $this->img_format=$aFormat;
1742 return true;
1743 }
1744 }
1745 else {
1746 $tst=false;
1747 }
1748 if( !$tst ) {
1749 JpGraphError::RaiseL(25110,$aFormat);//(" Your PHP installation does not support the chosen graphic format: $aFormat");
1750 }
1751 }
1752 }
1753
1754 /**
1755 * Draw Line
1756 */
1757 function DrawLine($im, $x1, $y1, $x2, $y2, $weight, $color) {
1758 if ($weight == 1) {
1759 return imageline($im,$x1,$y1,$x2,$y2,$color);
1760 }
1761
1762 $angle=(atan2(($y1 - $y2), ($x2 - $x1)));
1763
1764 $dist_x = $weight * (sin($angle)) / 2;
1765 $dist_y = $weight * (cos($angle)) / 2;
1766
1767 $p1x=ceil(($x1 + $dist_x));
1768 $p1y=ceil(($y1 + $dist_y));
1769 $p2x=ceil(($x2 + $dist_x));
1770 $p2y=ceil(($y2 + $dist_y));
1771 $p3x=ceil(($x2 - $dist_x));
1772 $p3y=ceil(($y2 - $dist_y));
1773 $p4x=ceil(($x1 - $dist_x));
1774 $p4y=ceil(($y1 - $dist_y));
1775
1776 $array=array($p1x,$p1y,$p2x,$p2y,$p3x,$p3y,$p4x,$p4y);
1777 imagefilledpolygon ( $im, $array, (count($array)/2), $color );
1778
1779 // for antialias
1780 imageline($im, $p1x, $p1y, $p2x, $p2y, $color);
1781 imageline($im, $p3x, $p3y, $p4x, $p4y, $color);
1782 return;
1783
1784
1785
1786 return imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
1787 $weight = 8;
1788 if ($weight <= 1) {
1789 return imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
1790 }
1791
1792 $pts = array();
1793
1794 $weight /= 2;
1795
1796 if ($y2 - $y1 == 0) {
1797 // x line
1798 $pts = array();
1799 $pts[] = $x1; $pts[] = $y1 - $weight;
1800 $pts[] = $x1; $pts[] = $y1 + $weight;
1801 $pts[] = $x2; $pts[] = $y2 + $weight;
1802 $pts[] = $x2; $pts[] = $y2 - $weight;
1803
1804 } elseif ($x2 - $x1 == 0) {
1805 // y line
1806 $pts = array();
1807 $pts[] = $x1 - $weight; $pts[] = $y1;
1808 $pts[] = $x1 + $weight; $pts[] = $y1;
1809 $pts[] = $x2 + $weight; $pts[] = $y2;
1810 $pts[] = $x2 - $weight; $pts[] = $y2;
1811
1812 } else {
1813
1814 var_dump($x1, $x2, $y1, $y2);
1815 $length = sqrt(pow($x2 - $x1, 2) + pow($y2 - $y1, 2));
1816 var_dump($length);exit;
1817 exit;
1818
1819/*
1820 $lean = ($y2 - $y1) / ($x2 - $x1);
1821 $lean2 = -1 / $lean;
1822 $sin = $lean / ($y2 - $y1);
1823 $cos = $lean / ($x2 - $x1);
1824
1825 $pts[] = $x1 + (-$weight * $sin); $pts[] = $y1 + (-$weight * $cos);
1826 $pts[] = $x1 + (+$weight * $sin); $pts[] = $y1 + (+$weight * $cos);
1827 $pts[] = $x2 + (+$weight * $sin); $pts[] = $y2 + (+$weight * $cos);
1828 $pts[] = $x2 + (-$weight * $sin); $pts[] = $y2 + (-$weight * $cos);
1829*/
1830 }
1831
1832//print_r($pts);exit;
1833 if (count($pts)/2 < 3) {
1834 return;
1835 }
1836
1837 imagesetthickness($im, 1);
1838 imagefilledpolygon($im, $pts,count($pts)/2, $color);
1839
1840
1841 $weight *= 2;
1842
1843// $this->DrawImageSmoothArc($im, $x1, $y1, $weight, $weight, 0, 360, $color);
1844// $this->DrawImageSmoothArc($im, $x2, $y2, $weight, $weight, 0, 360, $color);
1845 }
1846
1847
1848 function DrawImageSmoothArc($im, $xc, $yc, $w, $h, $s, $e, $color, $style = null) {
1849 $tmp = $s;
1850 $s = (360 - $e) / 180 * M_PI;
1851 $e = (360 - $tmp) / 180 * M_PI;
1852 return imageSmoothArc($im, round($xc), round($yc), round($w), round($h), $this->CreateColorForImageSmoothArc($color), $s, $e);
1853 }
1854
1855 function CreateColorForImageSmoothArc($color) {
1856 $alpha = $color >> 24 & 0xFF;
1857 $red = $color >> 16 & 0xFF;
1858 $green = $color >> 8 & 0xFF;
1859 $blue = $color & 0xFF;
1860
1861//var_dump($alpha, $red, $green, $blue);exit;
1862
1863 return array($red, $green, $blue, $alpha);
1864 }
1865
1866 function imageSmoothCircle( &$img, $cx, $cy, $cr, $color ) {
1867 $ir = $cr;
1868 $ix = 0;
1869 $iy = $ir;
1870 $ig = 2 * $ir - 3;
1871 $idgr = -6;
1872 $idgd = 4 * $ir - 10;
1873 $fill = imageColorExactAlpha( $img, $color[ 'R' ], $color[ 'G' ], $color[ 'B' ], 0 );
1874 imageLine( $img, $cx + $cr - 1, $cy, $cx, $cy, $fill );
1875 imageLine( $img, $cx - $cr + 1, $cy, $cx - 1, $cy, $fill );
1876 imageLine( $img, $cx, $cy + $cr - 1, $cx, $cy + 1, $fill );
1877 imageLine( $img, $cx, $cy - $cr + 1, $cx, $cy - 1, $fill );
1878 $draw = imageColorExactAlpha( $img, $color[ 'R' ], $color[ 'G' ], $color[ 'B' ], 42 );
1879 imageSetPixel( $img, $cx + $cr, $cy, $draw );
1880 imageSetPixel( $img, $cx - $cr, $cy, $draw );
1881 imageSetPixel( $img, $cx, $cy + $cr, $draw );
1882 imageSetPixel( $img, $cx, $cy - $cr, $draw );
1883 while ( $ix <= $iy - 2 ) {
1884 if ( $ig < 0 ) {
1885 $ig += $idgd;
1886 $idgd -= 8;
1887 $iy--;
1888 } else {
1889 $ig += $idgr;
1890 $idgd -= 4;
1891 }
1892 $idgr -= 4;
1893 $ix++;
1894 imageLine( $img, $cx + $ix, $cy + $iy - 1, $cx + $ix, $cy + $ix, $fill );
1895 imageLine( $img, $cx + $ix, $cy - $iy + 1, $cx + $ix, $cy - $ix, $fill );
1896 imageLine( $img, $cx - $ix, $cy + $iy - 1, $cx - $ix, $cy + $ix, $fill );
1897 imageLine( $img, $cx - $ix, $cy - $iy + 1, $cx - $ix, $cy - $ix, $fill );
1898 imageLine( $img, $cx + $iy - 1, $cy + $ix, $cx + $ix, $cy + $ix, $fill );
1899 imageLine( $img, $cx + $iy - 1, $cy - $ix, $cx + $ix, $cy - $ix, $fill );
1900 imageLine( $img, $cx - $iy + 1, $cy + $ix, $cx - $ix, $cy + $ix, $fill );
1901 imageLine( $img, $cx - $iy + 1, $cy - $ix, $cx - $ix, $cy - $ix, $fill );
1902 $filled = 0;
1903 for ( $xx = $ix - 0.45; $xx < $ix + 0.5; $xx += 0.2 ) {
1904 for ( $yy = $iy - 0.45; $yy < $iy + 0.5; $yy += 0.2 ) {
1905 if ( sqrt( pow( $xx, 2 ) + pow( $yy, 2 ) ) < $cr ) $filled += 4;
1906 }
1907 }
1908 $draw = imageColorExactAlpha( $img, $color[ 'R' ], $color[ 'G' ], $color[ 'B' ], ( 100 - $filled ) );
1909 imageSetPixel( $img, $cx + $ix, $cy + $iy, $draw );
1910 imageSetPixel( $img, $cx + $ix, $cy - $iy, $draw );
1911 imageSetPixel( $img, $cx - $ix, $cy + $iy, $draw );
1912 imageSetPixel( $img, $cx - $ix, $cy - $iy, $draw );
1913 imageSetPixel( $img, $cx + $iy, $cy + $ix, $draw );
1914 imageSetPixel( $img, $cx + $iy, $cy - $ix, $draw );
1915 imageSetPixel( $img, $cx - $iy, $cy + $ix, $draw );
1916 imageSetPixel( $img, $cx - $iy, $cy - $ix, $draw );
1917 }
1918 }
1919
1920 function __get($name) {
1921
1922 if (strpos($name, 'raw_') !== false) {
1923 // if $name == 'raw_left_margin' , return $this->_left_margin;
1924 $variable_name = '_' . str_replace('raw_', '', $name);
1925 return $this->$variable_name;
1926 }
1927
1928 $variable_name = '_' . $name;
1929
1930 if (isset($this->$variable_name)) {
1931 return $this->$variable_name * SUPERSAMPLING_SCALE;
1932 } else {
1933 JpGraphError::RaiseL('25132', $name);
1934 }
1935 }
1936
1937 function __set($name, $value) {
1938 $this->{'_'.$name} = $value;
1939 }
1940
[2]1941} // CLASS
1942
1943//===================================================
1944// CLASS RotImage
1945// Description: Exactly as Image but draws the image at
1946// a specified angle around a specified rotation point.
1947//===================================================
1948class RotImage extends Image {
1949 public $a=0;
[284]1950 public $dx=0,$dy=0,$transx=0,$transy=0;
[2]1951 private $m=array();
[284]1952
1953 function __construct($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT,$aSetAutoMargin=true) {
1954 parent::__construct($aWidth,$aHeight,$aFormat,$aSetAutoMargin);
1955 $this->dx=$this->width/2;
1956 $this->dy=$this->height/2;
1957 $this->SetAngle($a);
[2]1958 }
[284]1959
[2]1960 function SetCenter($dx,$dy) {
[284]1961 $old_dx = $this->dx;
1962 $old_dy = $this->dy;
1963 $this->dx=$dx;
1964 $this->dy=$dy;
1965 $this->SetAngle($this->a);
1966 return array($old_dx,$old_dy);
[2]1967 }
[284]1968
[2]1969 function SetTranslation($dx,$dy) {
[284]1970 $old = array($this->transx,$this->transy);
1971 $this->transx = $dx;
1972 $this->transy = $dy;
1973 return $old;
[2]1974 }
1975
1976 function UpdateRotMatrice() {
[284]1977 $a = $this->a;
1978 $a *= M_PI/180;
1979 $sa=sin($a); $ca=cos($a);
1980 // Create the rotation matrix
1981 $this->m[0][0] = $ca;
1982 $this->m[0][1] = -$sa;
1983 $this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
1984 $this->m[1][0] = $sa;
1985 $this->m[1][1] = $ca;
1986 $this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
[2]1987 }
1988
1989 function SetAngle($a) {
[284]1990 $tmp = $this->a;
1991 $this->a = $a;
1992 $this->UpdateRotMatrice();
1993 return $tmp;
[2]1994 }
1995
1996 function Circle($xc,$yc,$r) {
[284]1997 list($xc,$yc) = $this->Rotate($xc,$yc);
1998 parent::Circle($xc,$yc,$r);
[2]1999 }
2000
2001 function FilledCircle($xc,$yc,$r) {
[284]2002 list($xc,$yc) = $this->Rotate($xc,$yc);
2003 parent::FilledCircle($xc,$yc,$r);
[2]2004 }
2005
[284]2006
[2]2007 function Arc($xc,$yc,$w,$h,$s,$e) {
[284]2008 list($xc,$yc) = $this->Rotate($xc,$yc);
2009 $s += $this->a;
2010 $e += $this->a;
2011 parent::Arc($xc,$yc,$w,$h,$s,$e);
[2]2012 }
2013
2014 function FilledArc($xc,$yc,$w,$h,$s,$e,$style='') {
[284]2015 list($xc,$yc) = $this->Rotate($xc,$yc);
2016 $s += $this->a;
2017 $e += $this->a;
2018 parent::FilledArc($xc,$yc,$w,$h,$s,$e);
[2]2019 }
2020
2021 function SetMargin($lm,$rm,$tm,$bm) {
[284]2022 parent::SetMargin($lm,$rm,$tm,$bm);
2023 $this->UpdateRotMatrice();
[2]2024 }
[284]2025
[2]2026 function Rotate($x,$y) {
[284]2027 // Optimization. Ignore rotation if Angle==0 || Angle==360
2028 if( $this->a == 0 || $this->a == 360 ) {
2029 return array($x + $this->transx, $y + $this->transy );
2030 }
2031 else {
2032 $x1=round($this->m[0][0]*$x + $this->m[0][1]*$y,1) + $this->m[0][2] + $this->transx;
2033 $y1=round($this->m[1][0]*$x + $this->m[1][1]*$y,1) + $this->m[1][2] + $this->transy;
2034 return array($x1,$y1);
2035 }
[2]2036 }
[284]2037
[2]2038 function CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth=-1,$fromHeight=-1,$aMix=100) {
[284]2039 list($toX,$toY) = $this->Rotate($toX,$toY);
2040 parent::CopyMerge($fromImg,$toX,$toY,$fromX,$fromY,$toWidth,$toHeight,$fromWidth,$fromHeight,$aMix);
[2]2041
2042 }
2043
2044 function ArrRotate($pnts) {
[284]2045 $n = count($pnts)-1;
2046 for($i=0; $i < $n; $i+=2) {
2047 list ($x,$y) = $this->Rotate($pnts[$i],$pnts[$i+1]);
2048 $pnts[$i] = $x; $pnts[$i+1] = $y;
2049 }
2050 return $pnts;
[2]2051 }
2052
2053 function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
[284]2054 list($x1,$y1) = $this->Rotate($x1,$y1);
2055 list($x2,$y2) = $this->Rotate($x2,$y2);
2056 parent::DashedLine($x1,$y1,$x2,$y2,$dash_length,$dash_space);
[2]2057 }
[284]2058
[2]2059 function Line($x1,$y1,$x2,$y2) {
[284]2060 list($x1,$y1) = $this->Rotate($x1,$y1);
2061 list($x2,$y2) = $this->Rotate($x2,$y2);
2062 parent::Line($x1,$y1,$x2,$y2);
[2]2063 }
2064
2065 function Rectangle($x1,$y1,$x2,$y2) {
[284]2066 // Rectangle uses Line() so it will be rotated through that call
2067 parent::Rectangle($x1,$y1,$x2,$y2);
[2]2068 }
[284]2069
[2]2070 function FilledRectangle($x1,$y1,$x2,$y2) {
[284]2071 if( $y1==$y2 || $x1==$x2 )
2072 $this->Line($x1,$y1,$x2,$y2);
2073 else
2074 $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
[2]2075 }
[284]2076
[2]2077 function Polygon($pnts,$closed=FALSE,$fast=FALSE) {
[284]2078 // Polygon uses Line() so it will be rotated through that call unless
2079 // fast drawing routines are used in which case a rotate is needed
2080 if( $fast ) {
2081 parent::Polygon($this->ArrRotate($pnts));
2082 }
2083 else {
2084 parent::Polygon($pnts,$closed,$fast);
2085 }
[2]2086 }
[284]2087
[2]2088 function FilledPolygon($pnts) {
[284]2089 parent::FilledPolygon($this->ArrRotate($pnts));
[2]2090 }
[284]2091
[2]2092 function Point($x,$y) {
[284]2093 list($xp,$yp) = $this->Rotate($x,$y);
2094 parent::Point($xp,$yp);
[2]2095 }
[284]2096
[2]2097 function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left",$debug=false) {
[284]2098 list($xp,$yp) = $this->Rotate($x,$y);
2099 return parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align,$debug);
[2]2100 }
2101}
2102
[284]2103//=======================================================================
[2]2104// CLASS ImgStreamCache
[284]2105// Description: Handle caching of graphs to files. All image output goes
2106// through this class
2107//=======================================================================
[2]2108class ImgStreamCache {
[284]2109 private $cache_dir, $timeout=0; // Infinite timeout
[2]2110 //---------------
2111 // CONSTRUCTOR
[284]2112 function __construct($aCacheDir=CACHE_DIR) {
2113 $this->cache_dir = $aCacheDir;
[2]2114 }
2115
[284]2116 //---------------
2117 // PUBLIC METHODS
[2]2118
2119 // Specify a timeout (in minutes) for the file. If the file is older then the
2120 // timeout value it will be overwritten with a newer version.
2121 // If timeout is set to 0 this is the same as infinite large timeout and if
2122 // timeout is set to -1 this is the same as infinite small timeout
2123 function SetTimeout($aTimeout) {
[284]2124 $this->timeout=$aTimeout;
[2]2125 }
[284]2126
[2]2127 // Output image to browser and also write it to the cache
2128 function PutAndStream($aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
2129
[284]2130 // Check if we should always stroke the image to a file
2131 if( _FORCE_IMGTOFILE ) {
2132 $aStrokeFileName = _FORCE_IMGDIR.GenImgName();
2133 }
[2]2134
[284]2135 if( $aStrokeFileName != '' ) {
[2]2136
[284]2137 if( $aStrokeFileName == 'auto' ) {
2138 $aStrokeFileName = GenImgName();
2139 }
[2]2140
[284]2141 if( file_exists($aStrokeFileName) ) {
[2]2142
[284]2143 // Wait for lock (to make sure no readers are trying to access the image)
2144 $fd = fopen($aStrokeFileName,'w');
2145 $lock = flock($fd, LOCK_EX);
2146
2147 // Since the image write routines only accepts a filename which must not
2148 // exist we need to delete the old file first
2149 if( !@unlink($aStrokeFileName) ) {
2150 $lock = flock($fd, LOCK_UN);
2151 JpGraphError::RaiseL(25111,$aStrokeFileName);
2152 //(" Can't delete cached image $aStrokeFileName. Permission problem?");
2153 }
2154 $aImage->Stream($aStrokeFileName);
2155 $lock = flock($fd, LOCK_UN);
2156 fclose($fd);
2157
2158 }
2159 else {
2160 $aImage->Stream($aStrokeFileName);
2161 }
2162
2163 return;
2164 }
2165
2166 if( $aCacheFileName != '' && USE_CACHE) {
2167
2168 $aCacheFileName = $this->cache_dir . $aCacheFileName;
2169 if( file_exists($aCacheFileName) ) {
2170 if( !$aInline ) {
2171 // If we are generating image off-line (just writing to the cache)
2172 // and the file exists and is still valid (no timeout)
2173 // then do nothing, just return.
2174 $diff=time()-filemtime($aCacheFileName);
2175 if( $diff < 0 ) {
2176 JpGraphError::RaiseL(25112,$aCacheFileName);
2177 //(" Cached imagefile ($aCacheFileName) has file date in the future!!");
2178 }
2179 if( $this->timeout>0 && ($diff <= $this->timeout*60) ) return;
2180 }
2181
2182 // Wait for lock (to make sure no readers are trying to access the image)
2183 $fd = fopen($aCacheFileName,'w');
2184 $lock = flock($fd, LOCK_EX);
2185
2186 if( !@unlink($aCacheFileName) ) {
2187 $lock = flock($fd, LOCK_UN);
2188 JpGraphError::RaiseL(25113,$aStrokeFileName);
2189 //(" Can't delete cached image $aStrokeFileName. Permission problem?");
2190 }
2191 $aImage->Stream($aCacheFileName);
2192 $lock = flock($fd, LOCK_UN);
2193 fclose($fd);
2194
2195 }
2196 else {
2197 $this->MakeDirs(dirname($aCacheFileName));
2198 if( !is_writeable(dirname($aCacheFileName)) ) {
2199 JpGraphError::RaiseL(25114,$aCacheFileName);
2200 //('PHP has not enough permissions to write to the cache file '.$aCacheFileName.'. Please make sure that the user running PHP has write permission for this file if you wan to use the cache system with JpGraph.');
2201 }
2202 $aImage->Stream($aCacheFileName);
2203 }
2204
2205 $res=true;
2206 // Set group to specified
2207 if( CACHE_FILE_GROUP != '' ) {
2208 $res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
2209 }
2210 if( CACHE_FILE_MOD != '' ) {
2211 $res = @chmod($aCacheFileName,CACHE_FILE_MOD);
2212 }
2213 if( !$res ) {
2214 JpGraphError::RaiseL(25115,$aStrokeFileName);
2215 //(" Can't set permission for cached image $aStrokeFileName. Permission problem?");
2216 }
2217
2218 $aImage->Destroy();
2219 if( $aInline ) {
2220 if ($fh = @fopen($aCacheFileName, "rb") ) {
2221 $aImage->Headers();
2222 fpassthru($fh);
2223 return;
2224 }
2225 else {
2226 JpGraphError::RaiseL(25116,$aFile);//(" Cant open file from cache [$aFile]");
2227 }
2228 }
2229 }
2230 elseif( $aInline ) {
2231 $aImage->Headers();
2232 $aImage->Stream();
2233 return;
2234 }
[2]2235 }
[284]2236
2237 function IsValid($aCacheFileName) {
2238 $aCacheFileName = $this->cache_dir.$aCacheFileName;
2239 if ( USE_CACHE && file_exists($aCacheFileName) ) {
2240 $diff=time()-filemtime($aCacheFileName);
2241 if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
2242 return false;
2243 }
2244 else {
2245 return true;
2246 }
2247 }
2248 else {
2249 return false;
2250 }
2251 }
2252
2253 function StreamImgFile($aImage,$aCacheFileName) {
2254 $aCacheFileName = $this->cache_dir.$aCacheFileName;
2255 if ( $fh = @fopen($aCacheFileName, 'rb') ) {
2256 $lock = flock($fh, LOCK_SH);
2257 $aImage->Headers();
2258 fpassthru($fh);
2259 $lock = flock($fh, LOCK_UN);
2260 fclose($fh);
2261 return true;
2262 }
2263 else {
2264 JpGraphError::RaiseL(25117,$aCacheFileName);//(" Can't open cached image \"$aCacheFileName\" for reading.");
2265 }
2266 }
2267
[2]2268 // Check if a given image is in cache and in that case
2269 // pass it directly on to web browser. Return false if the
2270 // image file doesn't exist or exists but is to old
[284]2271 function GetAndStream($aImage,$aCacheFileName) {
2272 if( $this->Isvalid($aCacheFileName) ) {
2273 $this->StreamImgFile($aImage,$aCacheFileName);
2274 }
2275 else {
2276 return false;
2277 }
[2]2278 }
[284]2279
[2]2280 //---------------
[284]2281 // PRIVATE METHODS
[2]2282 // Create all necessary directories in a path
2283 function MakeDirs($aFile) {
[284]2284 $dirs = array();
2285 // In order to better work when open_basedir is enabled
2286 // we do not create directories in the root path
2287 while ( $aFile != '/' && !(file_exists($aFile)) ) {
2288 $dirs[] = $aFile.'/';
2289 $aFile = dirname($aFile);
2290 }
2291 for ($i = sizeof($dirs)-1; $i>=0; $i--) {
2292 if(! @mkdir($dirs[$i],0777) ) {
2293 JpGraphError::RaiseL(25118,$aFile);//(" Can't create directory $aFile. Make sure PHP has write permission to this directory.");
2294 }
2295 // We also specify mode here after we have changed group.
2296 // This is necessary if Apache user doesn't belong the
2297 // default group and hence can't specify group permission
2298 // in the previous mkdir() call
2299 if( CACHE_FILE_GROUP != "" ) {
2300 $res=true;
2301 $res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
2302 $res = @chmod($dirs[$i],0777);
2303 if( !$res ) {
2304 JpGraphError::RaiseL(25119,$aFile);//(" Can't set permissions for $aFile. Permission problems?");
2305 }
2306 }
2307 }
2308 return true;
2309 }
[2]2310} // CLASS Cache
2311
2312?>
Note: See TracBrowser for help on using the repository browser.