1 /*
2     AWFFull - A Webalizer Fork, Full o' features
3 
4     graphs.c
5         produces graphs used by AWFFull
6 
7     Copyright (C) 1997-2001  Bradford L. Barrett (brad@mrunix.net)
8 	Copyright (C) 2004-2008  Stephen McInerney (spm@stedee.id.au)
9     Copyright (C) 2006 by Benoit Rouits (brouits@free.fr)
10 
11     This file is part of AWFFull.
12 
13     AWFFull is free software: you can redistribute it and/or modify
14     it under the terms of the GNU General Public License as published by
15     the Free Software Foundation, either version 3 of the License, or
16     (at your option) any later version.
17 
18     AWFFull is distributed in the hope that it will be useful,
19     but WITHOUT ANY WARRANTY; without even the implied warranty of
20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21     GNU General Public License for more details.
22 
23     You should have received a copy of the GNU General Public License
24     along with AWFFull.  If not, see <http://www.gnu.org/licenses/>.
25 
26     This software uses the gd graphics library, which is copyright by
27     Quest Protein Database Center, Cold Spring Harbor Labs.  Please
28     see the documentation supplied with the library for additional
29     information and license terms, or visit www.boutell.com/gd/ for the
30     most recent version of the library and supporting documentation.
31 
32 */
33 
34 #include <float.h>
35 #include <gd.h>
36 
37 #include "awffull.h"
38 
39 #ifdef HAVE_GDIMAGESTRINGFT
40 #ifdef HAVE_GDFTUSEFONTCONFIG
41 char *graph_font_default = GRAPH_FONT_DEFAULT;
42 char *graph_font_label = GRAPH_FONT_LABEL;
43 #else
44 char *graph_font_default = GRAPH_FONT_FULLPATH_DEFAULT;
45 char *graph_font_label = GRAPH_FONT_FULLPATH_LABEL;
46 #endif          /* HAVE_GDFTUSEFONTCONFIG */
47 #else
48 #include <gdfontt.h>
49 #include <gdfonts.h>
50 #include <gdfontmb.h>
51 #endif          /* HAVE_GDIMAGESTRINGFT */
52 
53 /* Some systems don't define this */
54 #ifndef PI
55 #define PI 3.14159265358979323846
56 #endif
57 
58 #define HITCOLOR hitcolor                       /* hits     (green)    */
59 #define FILECOLOR filecolor                     /* files    (blue)     */
60 #define SITECOLOR sitecolor                     /* sites    (orange)   */
61 #define KBYTECOLOR kbytecolor                   /* KBytes   (red)      */
62 #define PAGECOLOR pagecolor                     /* Files    (cyan)     */
63 #define VISITCOLOR visitcolor                   /* Visits   (yellow)   */
64 
65 #define CX 156                                  /* center x (for pie)  */
66 #define CY 150                                  /* center y  (chart)   */
67 #define XRAD 240                                /* X-axis radius       */
68 #define YRAD 200                                /* Y-axis radius       */
69 
70 /* forward reference internal routines */
71 
72 static void init_graph(char *, int, int);
73 static struct pie_data *calc_arc(double, double, int, int, int, int);
74 
75 /* common public declarations */
76 
77 const char *numchar[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
78     "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
79     "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"
80 };
81 
82 gdImagePtr im;                                  /* image buffer        */
83 FILE *out;                                      /* output file for PNG */
84 char maxvaltxt[32];                             /* graph values        */
85 double percent;                                 /* percent storage     */
86 unsigned long julday;                           /* julday value        */
87 
88 struct pie_data {
89     int x;
90     int y;                                      /* line x,y            */
91     int mx;
92     int my;
93 };                                              /* midpoint x,y        */
94 
95 /* colors */
96 int black, white, grey, dkgrey;
97 int kbytecolor, filecolor, sitecolor, hitcolor, pagecolor, visitcolor;
98 
99 /***************************************************************
100  * ashex2int - ascii hexadecimal to integer converter          *
101  *     does _not_ exactly do the same as strtoul()             *
102  *                                                             *
103  *     Returns a base-10 integer value from a 2 char           *
104  *     ascii hexadecimal color number                          *
105  *     this integer is usable by th GD functions               *
106  *                                                             *
107  ***************************************************************/
108 static int
ashex2int(const char * twocharstr)109 ashex2int(const char *twocharstr)
110 {
111     int val;
112 
113     switch (twocharstr[1]) {
114     case 'a':;
115     case 'A':
116         val = 10;
117         break;
118     case 'b':;
119     case 'B':
120         val = 11;
121         break;
122     case 'c':;
123     case 'C':
124         val = 12;
125         break;
126     case 'd':;
127     case 'D':
128         val = 13;
129         break;
130     case 'e':;
131     case 'E':
132         val = 14;
133         break;
134     case 'f':;
135     case 'F':
136         val = 15;
137         break;
138     default:
139         val = (int) twocharstr[1] - 48;
140     }
141     switch (twocharstr[0]) {
142     case 'a':;
143     case 'A':
144         val += 160;
145         break;
146     case 'b':;
147     case 'B':
148         val += 176;
149         break;
150     case 'c':;
151     case 'C':
152         val += 192;
153         break;
154     case 'd':;
155     case 'D':
156         val += 208;
157         break;
158     case 'e':;
159     case 'E':
160         val += 224;
161         break;
162     case 'f':;
163     case 'F':
164         val += 240;
165         break;
166     default:
167         val += (int) (twocharstr[0] - 48) * 16;
168     }
169     return val;
170 }
171 
172 /*******************************************************************
173  * shortcuts to convert ascii hex color for gdImageColorAllocate() *
174  *******************************************************************/
175 #define getred(s) (ashex2int((s[0] == '#')?s+1:s))
176 /* returns the red base-10 integer value from a html color */
177 #define getgreen(s) (ashex2int((s[0] == '#')?s+3:s+2))
178 /* returns the green base-10 integer value from a html color */
179 #define getblue(s) (ashex2int((s[0] == '#')?s+5:s+4))
180 /* returns the blue base-10 integer value from a html color */
181 
182 /****************************************************************
183  * grid_spacing                                                 *
184  *      Calulate the grid spacing to use for a given range      *
185  *                                                              *
186  *      Heavily based on algorithims found in rrdtool-1.0.40    *
187  *      function: horizontal_grid                               *
188  *                                                              *
189  ****************************************************************/
190 double
grid_spacing(unsigned long long range)191 grid_spacing(unsigned long long range)
192 {
193     double range_f;                             /* covert the range to double   */
194     double grid;                                /* grid value                   */
195 
196     range_f = (double) range;
197     grid = powf((double) 10, floorf(log10f(range_f)));
198     /* Use of DBL_EPSILON to avoid double conversion problems */
199     if (grid <= DBL_EPSILON) {                  /* range is one -> 0.1 is reasonable scale */
200         grid = 0.1;
201     }
202     if ((range_f / grid) - 5.0 <= DBL_EPSILON) {
203         grid /= 10.0;
204     }
205     if ((range_f / grid) - 10.0 > DBL_EPSILON) {
206         grid *= 10.0;
207     }
208     if ((range_f / grid) - 5.0 <= DBL_EPSILON) {
209         grid /= 5.0;
210     }
211     if ((range_f / grid) - 10.0 > DBL_EPSILON) {
212         grid *= 5.0;
213     }
214     if ((range_f / grid) - 5.0 <= DBL_EPSILON) {
215         grid /= 2.0;
216     }
217     VPRINT(VERBOSE5, _("grid: %f  range/grid: %f\n"), grid, range_f / grid);
218     return (grid);
219 }
220 
221 
222 /****************************************************************
223  * draw_horiz_grid                                              *
224  *      Draws a series of horizontal grid lines                 *
225  *                                                              *
226  ****************************************************************/
227 static int
draw_horiz_grid(unsigned long long max,int xleft,int xright,int ybottom,int height,int text_offset,int is_kbytes)228 draw_horiz_grid(unsigned long long max,         /* the max value                        */
229                 int xleft,                      /* px. leftmost point                   */
230                 int xright,                     /* px. rightmost point                  */
231                 int ybottom,                    /* px. starting bottom value            *
232                                                  *  a line will NOT be drawn here       */
233                 int height,                     /* px. Height of the "frame"            */
234                 int text_offset,                /* px. Offset for grid numbering
235                                                    Can be -'ve                          */
236                 int is_kbytes                   /* Boolean. Set to 1 if unit is Kbytes - VOLUMEBASE */
237     )
238 {
239     double grid;                                /* the grid spacing to use              */
240     double pct_offset;                          /* percentage offset from height for each grid line     */
241     double i, j;
242     double max_f = (double) max;                /* max cast to double                   */
243     double height_f = (double) height;          /* height cast to double                        */
244     int ytop = 0;                               /* y axis for current grid line         */
245     char str[10];
246     int text_centring;                          /* Use to centre the Y Axis text against
247                                                    the grid lines                       */
248     char prefixes[9 + 1] = " kMGTPEZY";         /* Prefixes for bigger numbers                                  *
249                                                  *  see: http://physics.nist.gov/cuu/Units/prefixes.html        */
250     int pref_idx = 0;
251     int prev_text_end = ybottom;                /* px. end of previous text             */
252 
253     double grid_cnt = 0.0;                      /* How many grid lines displayed. Used to avoid double probs    */
254     int bounding_rectange[8];                   /* GD Bounding Rectangle, use to identify outer limits of drawn text */
255     char *imagestr_rtn = NULL;                  /* Showing the return value from gdImageStringFT() - assists with debugging font woes */
256     static bool imagestr_errored = false;       /* Only display the gdImageStringFT error once. Repeats of the same thing don't help */
257 
258     VPRINT(VERBOSE4, "Draw Horiz Grid: Max: %llu\n", max);
259 
260     if (is_kbytes == 1) {
261         j = max_f;
262         /* Calculate new max */
263         while ((j - 1000.0 >= (DBL_EPSILON * (pref_idx + 1))) && (pref_idx <= 8)) {
264             j /= 1000.0;
265             pref_idx++;
266         }
267         max = max / ((pow((double) VOLUMEBASE, (double) pref_idx))) * pow(1000.0, (double) pref_idx);
268         max_f = (double) max;
269         VPRINT(VERBOSE5, "    Vol Horiz Grid: Max: %llu, Index: %d\n", max, pref_idx);
270     }
271 
272     grid = grid_spacing(max);
273     i = grid;
274 
275     while (i - max_f <= (DBL_EPSILON * grid_cnt)) {
276         VPRINT(VERBOSE5, " i: %f  maxval: %llu\n", i, max);
277         VPRINT(VERBOSE5, "x1: %d x2: %d y: %d y1: %d pte: %d\n", xleft, xright, ybottom, ytop, prev_text_end);
278 
279         /* Calculate the prefixes to use */
280         grid_cnt++;
281         j = i;
282         pref_idx = 0;
283         while ((j - 1000.0 >= (DBL_EPSILON * grid_cnt)) && (pref_idx <= 8)) {
284             j /= 1000.0;
285             pref_idx++;
286         }
287 
288         if (j - floorf(j) <= (DBL_EPSILON * grid_cnt)) {
289             if (pref_idx != 0) {
290                 snprintf(str, 10, "%.0f%c", j, prefixes[pref_idx]);
291             } else {
292                 snprintf(str, 10, "%.0f", j);
293             }
294         } else {
295             if (pref_idx != 0) {
296                 snprintf(str, 10, "%.1f%c", j, prefixes[pref_idx]);
297             } else {
298                 snprintf(str, 10, "%.1f", j);
299             }
300         }
301 
302         /* Work out the line height */
303         pct_offset = (i / max_f) * height_f;
304         ytop = ybottom - (int) pct_offset;
305 
306         gdImageLine(im, xleft, ytop, xright, ytop, dkgrey);
307 #ifdef HAVE_GDIMAGESTRINGFT
308         /* "print" the string to get an idea of actual size and hence find the centre */
309         imagestr_rtn = gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_default, GRAPH_FONT_SIZE_TINY, 0.0, 0, 0, str);
310         if (imagestr_rtn && imagestr_errored != true) {
311             ERRVPRINT(VERBOSE1, "Error: Invalid gdImageStringFT: %s --> %s  Font: %s\n", str, imagestr_rtn, graph_font_default);
312             imagestr_errored = true;
313         }
314         text_centring = (int) (((double) bounding_rectange[4] / 2.0) + 1.0);
315 
316         /* Final check to verify that we're not over-writing existing text */
317         if (prev_text_end >= (ytop + text_centring)) {
318             /* Not overwriting, so display! */
319             gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_default, GRAPH_FONT_SIZE_TINY, PI / 2.0, xleft - text_offset + GRAPH_FONT_SIZE_TINY, ytop + text_centring,
320                             str);
321             gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_default, GRAPH_FONT_SIZE_TINY, PI / 2.0, xleft - text_offset + GRAPH_FONT_SIZE_TINY, ytop + text_centring,
322                             str);
323             prev_text_end = bounding_rectange[5] - GRAPH_FONT_SIZE_TINY;        /* Upper Right (ignore rotate!), and a single character size - adds a nice spaceing gap. */
324         }
325 #else
326         text_centring = (int) ((((double) strlen(str) / 2.0) * 5.0) - 1.0);
327         if (prev_text_end >= (ytop + text_centring)) {
328             /* Final check to verify that we're not over-writing existing text */
329             gdImageStringUp(im, gdFontTiny, xleft - text_offset, ytop + text_centring, (unsigned char *) str, dkgrey);
330             prev_text_end = ytop - text_centring - 7;
331         }
332 #endif
333         i += grid;
334         VPRINT(VERBOSE5, "+ i: %f  maxval: %llu max-i: %g eps: %g\n", i, max, max_f - i, DBL_EPSILON);
335     }
336     return (0);
337 }
338 
339 
340 /****************************************************************
341  * calc_bar_width                                               *
342  *      Calculate the individual bar width to use               *
343  *                                                              *
344  ****************************************************************/
345 static int
calc_bar_width(int drawing_width,int nbr_bars,int * bar_gap,int * bar_sep,int nbr_grouped_bars)346 calc_bar_width(int drawing_width,               /* The width of the drawing area */
347                int nbr_bars,                    /* How many bars will we be drawing */
348                int *bar_gap,                    /* The gap between bar groupings - white space */
349                int *bar_sep,                    /* The separation between individual bars in a grouping */
350                /* bar_gap and bar_sep, are ignored as incoming values - reset to the defaults
351                 * The new/set values of both will be passed back
352                 */
353                int nbr_grouped_bars             /* The number of bars in a grouping */
354     )
355 {
356     int bar_width = 1;                          /* The width to return */
357 
358     VPRINT(VERBOSE5, "CalcBarWidth: DrawWidth: %d  NbrBars: %d\n", drawing_width, nbr_bars);
359 
360     if (nbr_grouped_bars < 0) {
361         ERRVPRINT(VERBOSE3, "Number bars in a group < 0. Bad News! Correcting.\n");
362         nbr_grouped_bars = 0;
363     }
364 
365     /* If width gets too small, steadily drop bar_gap and bar_sep - till at most 1px each, and set width to 1px too
366      * May have lotsa data points issues... FIXME! */
367     while (1) {
368         /* bar_width is based on:
369          * a.  Size of drawing "window" -> main_width
370          *      minus left/right side gaps -> (bar_gap * 2)
371          * b.  How many bars to draw - ie divisions -> (idx_end - idx_start + 1 = index_diff + 1)
372          *      minus separation/gap between a group and individual bars ->  (bar_gap + (bar_sep * 2))
373          * c.  Can be left as int - want all bars to be same size!
374          */
375 //          bar_width = ((main_width - (bar_gap * 2)) / (index_diff + 1)) - (bar_gap + (bar_sep * 1));
376         bar_width = ((drawing_width - ((*bar_gap) * 2)) / (nbr_bars + 1)) - ((*bar_gap) + ((*bar_sep) * (nbr_grouped_bars - 1)));
377         if ((nbr_grouped_bars == 1) && (bar_width > *bar_gap)) {
378             /* exit when the separation is irrelevant and the barwidth is already ok */
379             break;
380         }
381         if ((bar_width > *bar_sep) && (bar_width > *bar_gap)) {
382             /* exit once the bar width is actually bigger than the separation and the gap */
383             break;
384         }
385 
386         /* If we can't get any smaller? Width is the smallest */
387         /* FIXME:  width * nbrbars > drawing_width */
388         if ((*bar_gap == 1) && (*bar_sep == 1)) {
389             bar_width = 1;
390             break;
391         }
392 
393         /* Decrease the Gap first, then the separation */
394         if (*bar_gap > 1) {
395             (*bar_gap)--;
396         } else {
397             (*bar_sep)--;
398         }
399     }
400 
401     VPRINT(VERBOSE5, "CalcBarWidth: Returning BarWidth: %d\n", bar_width);
402     return (bar_width);
403 }
404 
405 /****************************************************************
406  *  draw_bar                                              		*
407  *      Draw a single bar										*
408  *                                                              *
409  ****************************************************************/
410 static void
draw_bar(int xleft,int ybottom,int ytop,int bar_width,double bar_percent,int do_guide,int colour)411  draw_bar(int xleft,                            /* X Left */
412           int ybottom,                          /* Y Bottom of section */
413           int ytop,                             /* Y Top of section */
414           int bar_width,                        /* Bar Width */
415           double bar_percent,                   /* The percentage that this bar fills, Y-Axis */
416           int do_guide,                         /* Display a vertical guide bar; 1 == yes */
417           int colour                            /* Colour to display this bar in */
418     ) {
419     int ybartop;                                /* 1st y location for drawing           */
420     int xright;                                 /* 2nd x location for drawing           */
421     const int c_smallest_width = 2;             /* px. Smallest width of a bar to ignore putting a box around, ie 3 or more to box. */
422 
423     if (bar_percent > 0.0) {
424         xright = xleft + bar_width - 1;
425         ybartop = ybottom - (bar_percent * (ybottom - ytop - GRAPH_TB_IN_OFFSET));
426         if (ybartop > ybottom) {
427             ybartop = ybottom;
428         }
429         if (do_guide == 1) {
430             /* Put in a vertical bar for readability */
431             gdImageLine(im, xleft, ybottom, xleft, ytop, dkgrey);
432         }
433         gdImageFilledRectangle(im, xleft, ybartop, xright, ybottom, colour);
434         /* Don't do the black outline if we're getting too thin */
435         if ((xright - xleft) > c_smallest_width) {
436             gdImageRectangle(im, xleft, ybartop, xright, ybottom, black);
437         }
438     }
439 }
440 
441 /****************************************************************
442  *                                                              *
443  * YEAR_GRAPH6x  - Year graph with six data sets                *
444  *      Dependant on history_list being set                     *
445  *      idx_start & idx_end are index's into history_list       *
446  *                                                              *
447  ****************************************************************/
448 int
year_graph6x(char * fname,char * title,int idx_start,int idx_end)449 year_graph6x(char *fname,                       /* file name use                        */
450              char *title,                       /* title for graph                      */
451              int idx_start,                     /* begin start index                    */
452              int idx_end                        /* begin end index                      */
453     )
454 {
455 
456     /* local variables */
457 #ifdef HAVE_GDIMAGESTRINGFT
458     int i, x, y;
459     int text_lengthA;                           /* px. Length of the various text strings to be printed */
460     int text_lengthB;
461     int text_lengthC;
462     int text_lengthD;
463     int edge_text_offsetTOP;                    /* px. Offset from the TOP edge for side text    */
464     int edge_text_offsetBOTTOM;                 /* px. Offset from the BOTTOM edge for side text */
465 #else
466     int i, j, k, k2;
467     int font_char_width;                        /* px. Width of an individual char in a given font - magic constant     */
468     int font_char_height;                       /* px. Height of an individual char in a given font - magic constant    */
469 #endif
470 
471     int xbarleft;                               /* Leftmost point for a given bar to draw */
472 
473     int index_diff;                             /* Difference between start/end
474                                                    Use to set a maximum bar width       */
475 
476     int main_width;                             /* width of the primary section         */
477     int main_height;                            /* Height of the primary section        */
478     int main_bottom;                            /* Baseline of the primary section      */
479 
480     int section_left;                           /* Section break is here from left      */
481     int section_right;                          /* End of Section from left, right postion      */
482     int section_middle;                         /* Inner Section break is here from top */
483     int section_bottom;                         /* Bottom of vertical section line      */
484 
485     int bar_width;                              /* Width of the individual bars         */
486     double bar_offset;                          /* basic offset of the individual bars  */
487 
488     const int c_base_bar_gap = 3;               /* Constant. Set the default Bar Gap    */
489     const int c_base_bar_sep = 3;               /* Constant. Set the default Bar Separation */
490     int bar_gap = c_base_bar_gap;               /* px. Space between bar groupings      */
491     int bar_sep = c_base_bar_sep;               /* px. Space between bars in a group    */
492 
493     int month_middle_x;                         /* Mid point offset between veritical bars                              */
494     int text_centring;                          /* Use to centre the Y Axis text against the grid lines                 */
495     int prev_text_end = 0;                      /* px. end of previous text. Used to print months on X Axis             */
496     int cur_text_start;                         /* px. start of current text. Used to print months on X Axis            */
497 
498     int edge_text_offset;                       /* px. Offset from the edge for side text        */
499 
500     int do_year;
501 
502     unsigned long maxval = 1;
503     double fmaxval = 0.0;
504     int bounding_rectange[8];                   /* GD Bounding Rectangle, use to identify outer limits of drawn text */
505 
506     /* initalize the graph */
507     init_graph(title, g_settings.graphs.index_x, g_settings.graphs.index_y);
508 
509     /* Set up base calculations */
510     main_width = ((float) (g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - (GRAPH_INNER_BOX_RIGHT + 1)) * GRAPH_INDEX_SPLIT);
511     main_height = g_settings.graphs.index_y - GRAPH_INNER_BOX_TOP - GRAPH_INNER_BOX_BOTTOM - (GRAPH_TB_IN_OFFSET * 2) - 3;
512     main_bottom = g_settings.graphs.index_y - GRAPH_INNER_BOX_BOTTOM - (GRAPH_TB_IN_OFFSET * 2);
513 
514     /* draw inner section lines */
515     section_left = main_width + GRAPH_INNER_BOX_LEFT + 2 + 1;
516     section_right = g_settings.graphs.index_x - GRAPH_INNER_BOX_RIGHT - 2 - 1;  /* 2 is width of inner box */
517     section_bottom = g_settings.graphs.index_y - GRAPH_INNER_BOX_BOTTOM - 2 - 1;        /* 2 is width of inner box */
518     section_middle = (g_settings.graphs.index_y / 2) + 2;       /* 2 is width of inner lines */
519 
520     /* Vertical divide line */
521     gdImageLine(im, section_left, GRAPH_INNER_BOX_TOP, section_left, section_bottom, black);
522     gdImageLine(im, section_left - 1, GRAPH_INNER_BOX_TOP, section_left - 1, section_bottom, white);
523 
524     /* Middle right line - horizontal */
525     gdImageLine(im, section_left, section_middle, section_right, section_middle, black);
526     gdImageLine(im, section_left, section_middle - 1, section_right, section_middle - 1, white);
527 
528     /* Middle left line - horizontal */
529     gdImageLine(im, GRAPH_INNER_BOX_LEFT + 1, section_middle, section_left - 2, section_middle, black);
530     gdImageLine(im, GRAPH_INNER_BOX_LEFT, section_middle - 1, section_left - 2, section_middle - 1, white);
531 
532 #ifdef HAVE_GDIMAGESTRINGFT
533     /* Locate TOP and BOTTOM Edge Offsets for Displaying Text */
534     gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Pages"));
535     edge_text_offsetTOP =
536         (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_TOP) - (GRAPH_SHADOW_WIDTH + ((GRAPH_INNER_BOX_TOP - (abs(bounding_rectange[7]) + abs(bounding_rectange[1]) + 1)) / 2) +
537                                                       abs(bounding_rectange[1])) + 2;
538     edge_text_offsetBOTTOM =
539         g_settings.graphs.index_y - (GRAPH_SHADOW_WIDTH + ((GRAPH_INNER_BOX_BOTTOM - (abs(bounding_rectange[7]) + abs(bounding_rectange[1] + 1))) / 2) +
540                                      abs(bounding_rectange[1])) + 1;
541 
542     if (g_settings.graphs.legend) {             /* print color coded legends? */
543         /* Volume Legend */
544         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Volume"));
545         text_lengthA = bounding_rectange[4];
546         x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthA);
547         y = edge_text_offsetBOTTOM;
548         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Volume"));
549         gdImageStringFT(im, &bounding_rectange[0], KBYTECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Volume"));
550 
551         /* Sites/Visits Legend */
552         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Visits"));
553         text_lengthA = abs(bounding_rectange[4]);
554         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Sites"));
555         text_lengthB = abs(bounding_rectange[4]);
556         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, "/");
557         text_lengthD = abs(bounding_rectange[4]);
558 
559         y = edge_text_offsetTOP;
560         x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthA + 1 + text_lengthD + text_lengthB);
561         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Visits"));
562         gdImageStringFT(im, &bounding_rectange[0], VISITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Visits"));
563         x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthB + text_lengthD);
564         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, "/");
565         gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, "/");
566         x = g_settings.graphs.index_x - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + text_lengthB);
567         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Sites"));
568         gdImageStringFT(im, &bounding_rectange[0], SITECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Sites"));
569 
570         /* Hits/Files/Pages Legend */
571         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Pages"));
572         text_lengthA = abs(bounding_rectange[4]);
573         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Files"));
574         text_lengthB = abs(bounding_rectange[4]);
575         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Hits"));
576         text_lengthC = abs(bounding_rectange[4]);
577 
578         y = edge_text_offsetBOTTOM;
579         x = section_left;
580         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Pages"));
581         gdImageStringFT(im, &bounding_rectange[0], PAGECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Pages"));
582 
583         x += text_lengthA + 1;
584         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, "/");
585         gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, "/");
586 
587         x += text_lengthD;
588         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Files"));
589         gdImageStringFT(im, &bounding_rectange[0], FILECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Files"));
590 
591         x += text_lengthB + 1;
592         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, "/");
593         gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, "/");
594 
595         x += text_lengthD;
596         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, _("Hits"));
597         gdImageStringFT(im, &bounding_rectange[0], HITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, _("Hits"));
598     }
599 #else           /* OLD - Non TrueType Text Graphing */
600     /* magic numbers */
601     font_char_width = 6;
602     font_char_height = 10;
603     if (g_settings.graphs.legend) {             /* print color coded legends? */
604         k = font_char_width / 2;                /* Half Char Sep */
605         k2 = font_char_width * 2;               /* Half Space '/' Half Space */
606 
607         /* Volume Legend */
608         i = (strlen(_("Volume")) * font_char_width);
609         edge_text_offset = main_bottom + GRAPH_TEXT_Y_OFFSET;
610         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - i, edge_text_offset + 1, (unsigned char *) _("Volume"), dkgrey);
611         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - i, edge_text_offset, (unsigned char *) _("Volume"), KBYTECOLOR);
612 
613         /* Sites/Visits Legend */
614         i = (strlen(_("Visits")) * font_char_width);
615         j = (strlen(_("Sites")) * font_char_width);
616         edge_text_offset = GRAPH_INNER_BOX_TOP - font_char_height - GRAPH_TEXT_Y_OFFSET - 1;
617         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - i - j - k2, edge_text_offset + 1, (unsigned char *) _("Visits"), dkgrey);
618         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - i - j - k2, edge_text_offset, (unsigned char *) _("Visits"), VISITCOLOR);
619         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - j - k2 + k, edge_text_offset + 1, (unsigned char *) "/", dkgrey);
620         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - j - k2 + k, edge_text_offset, (unsigned char *) "/", black);
621         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 1 - j, edge_text_offset + 1, (unsigned char *) _("Sites"), dkgrey);
622         gdImageString(im, gdFontSmall, g_settings.graphs.index_x - GRAPH_INNER_BOX_LEFT - 2 - j, edge_text_offset, (unsigned char *) _("Sites"), SITECOLOR);
623 
624         /* Hits/Files/Pages Legend */
625         i = (strlen(_("Pages")) * font_char_width);
626         j = (strlen(_("Files")) * font_char_width);
627         edge_text_offset = main_bottom + GRAPH_TEXT_Y_OFFSET;
628         gdImageString(im, gdFontSmall, section_left + 1, edge_text_offset + 1, (unsigned char *) _("Pages"), dkgrey);
629         gdImageString(im, gdFontSmall, section_left, edge_text_offset, (unsigned char *) _("Pages"), PAGECOLOR);
630         gdImageString(im, gdFontSmall, section_left + i + k + 1, edge_text_offset + 1, (unsigned char *) "/", dkgrey);
631         gdImageString(im, gdFontSmall, section_left + i + k, edge_text_offset, (unsigned char *) "/", black);
632         gdImageString(im, gdFontSmall, section_left + i + k2 + 1, edge_text_offset + 1, (unsigned char *) _("Files"), dkgrey);
633         gdImageString(im, gdFontSmall, section_left + i + k2, edge_text_offset, (unsigned char *) _("Files"), FILECOLOR);
634         gdImageString(im, gdFontSmall, section_left + i + j + k2 + k + 1, edge_text_offset + 1, (unsigned char *) "/", dkgrey);
635         gdImageString(im, gdFontSmall, section_left + i + j + k2 + k, edge_text_offset, (unsigned char *) "/", black);
636         gdImageString(im, gdFontSmall, section_left + i + j + k2 * 2 + 1, edge_text_offset + 1, (unsigned char *) _("Hits"), dkgrey);
637         gdImageString(im, gdFontSmall, section_left + i + j + k2 * 2, edge_text_offset, (unsigned char *) _("Hits"), HITCOLOR);
638     }
639 #endif
640 
641     /****************************************************
642      * Do the main Section                              *
643      *  Hits, Files                                     *
644      ****************************************************/
645     /* get max val */
646     for (i = idx_start; i < idx_end; i++) {
647         if (history_list[i].hit > maxval) {
648             maxval = history_list[i].hit;
649         }
650         if (history_list[i].file > maxval) {
651             maxval = history_list[i].file;
652         }
653     }
654     if (maxval <= 0) {
655         maxval = 1;
656     }
657 
658     if (g_settings.graphs.lines) {
659         edge_text_offset = 6 + GRAPH_TEXT_Y_OFFSET + 1;
660         /* tiny font has height of 6 */
661         draw_horiz_grid(maxval, GRAPH_INNER_BOX_LEFT + 1, GRAPH_INNER_BOX_LEFT + main_width + 1, main_bottom, section_bottom - section_middle - GRAPH_TB_IN_OFFSET - 1,
662                         edge_text_offset, 0);
663     }
664 
665     /* width/offset calculations */
666     index_diff = idx_end - idx_start;
667     if (index_diff < GRAPH_INDEX_MIN_BARS) {    /* Set the minimum # of bars */
668         index_diff = GRAPH_INDEX_MIN_BARS;
669     }
670 
671     bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 2);
672     bar_offset = (((double) main_width - ((double) bar_gap * 2.0)) / (double) index_diff);
673     /* bar_offset is based on:
674      * a.  Size of drawing "window" -> G_YEAR_MAIN_WIDTH
675      *      minus left/right side gaps -> (bar_gap * 2)
676      * b.  How many bars to draw -> (idx_end - idx_start)
677      *      We start from zero, so no need to "+ 1"
678      * c.  Must double, else we lose significant fractions and bunch up leftwards - yuk.
679      */
680 
681     month_middle_x = (bar_width + bar_sep * 1) / 2 + 1;
682     /* Calc for the mid point on which to then draw the month on the X Axis */
683 
684 //    fprintf (stderr, "start: %d  end: %d  diff: %d  idx: %d  width: %d  offset: %f  mainwidth: %d\n", idx_start, idx_end, index_diff, idx_end - idx_start, bar_width, bar_offset, main_width);
685     for (i = idx_start; i < idx_end; i++) {
686         /* HITS */
687         percent = ((double) history_list[i].hit / (double) maxval);
688 
689         if ((history_list[i].month == 1) && (index_diff > 12)) {
690             do_year = 1;
691         } else {
692             do_year = 0;
693         }
694 
695         xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + ((i - idx_start) * bar_offset);
696         draw_bar(xbarleft, main_bottom, section_middle + 1, bar_width, percent, do_year, HITCOLOR);
697 
698         /* X Axis */
699 #ifdef HAVE_GDIMAGESTRINGFT
700         gdImageStringFT(NULL, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, 0, 0, (char *) s_month[history_list[i].month - 1]);
701         text_centring = abs(bounding_rectange[4]) / 2;
702         cur_text_start = xbarleft + month_middle_x - text_centring;
703         if (prev_text_end < cur_text_start) {
704             /* Check that we're not over-writing existing text */
705             if ((prev_text_end + 3) < section_left) {
706                 /* Make sure we don't overwrite any of the Legend */
707                 gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, cur_text_start, edge_text_offsetBOTTOM,
708                                 (char *) s_month[history_list[i].month - 1]);
709                 prev_text_end = abs(bounding_rectange[2]);      /* Get the rightmost point on the text just printed. Want so as not to overlap text. */
710             }
711         }
712 #else
713         text_centring = (((double) strlen(s_month[history_list[i].month - 1])) / 2.0) * (double) font_char_width;
714         /* Probably overkill - should always be 3 for the strlen */
715         cur_text_start = xbarleft + month_middle_x - text_centring;
716         if (prev_text_end < cur_text_start) {
717             /* Final check to verify that we're not over-writing existing text */
718             gdImageString(im, gdFontSmall, cur_text_start, main_bottom + GRAPH_TEXT_Y_OFFSET, (unsigned char *) s_month[history_list[i].month - 1], black);
719             prev_text_end = xbarleft + month_middle_x + text_centring + (font_char_width / 2);
720         }
721 #endif
722 
723         /* FILES */
724         percent = ((double) history_list[i].file / (double) maxval);
725         draw_bar(xbarleft + bar_sep, main_bottom, section_middle + 1, bar_width, percent, 0, FILECOLOR);
726     }
727 
728     /****************************************************
729      * Do the Top Left Section                          *
730      *  Pages                                           *
731      ****************************************************/
732     /* get max val */
733     maxval = 0;
734     for (i = idx_start; i < idx_end; i++) {
735         if (history_list[i].page > maxval) {
736             maxval = history_list[i].page;
737         }
738     }
739     if (maxval <= 0) {
740         maxval = 1;
741     }
742     if (g_settings.graphs.lines) {
743         edge_text_offset = 6 + GRAPH_TEXT_Y_OFFSET + 1;
744         draw_horiz_grid(maxval, GRAPH_INNER_BOX_LEFT + 1, GRAPH_INNER_BOX_LEFT + main_width + 1, section_middle, section_middle - GRAPH_INNER_BOX_TOP - GRAPH_TB_IN_OFFSET - 1,
745                         edge_text_offset, 0);
746     }
747     bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 1);
748     for (i = idx_start; i < idx_end; i++) {
749         /* PAGES */
750         percent = ((double) history_list[i].page / (double) maxval);
751         if ((history_list[i].month == 1) && (index_diff > 12)) {
752             do_year = 1;
753         } else {
754             do_year = 0;
755         }
756         draw_bar(GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + ((i - idx_start) * bar_offset),
757                  section_middle - GRAPH_TB_IN_OFFSET - 1, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, do_year, PAGECOLOR);
758     }
759 
760     /****************************************************
761      * Do the Top Right Section                         *
762      *  Visits, Sites                                   *
763      ****************************************************/
764     /* get max val */
765     maxval = 0;
766     for (i = idx_start; i < idx_end; i++) {
767         if (history_list[i].site > maxval) {
768             maxval = history_list[i].site;
769         }
770         if (history_list[i].visit > maxval) {
771             maxval = history_list[i].visit;
772         }
773     }
774     if (maxval <= 0) {
775         maxval = 1;
776     }
777     if (g_settings.graphs.lines) {
778         edge_text_offset = section_left - section_right - GRAPH_TEXT_Y_OFFSET;
779         draw_horiz_grid(maxval, section_left + 1, section_right, section_middle, section_middle - GRAPH_INNER_BOX_TOP - GRAPH_TB_IN_OFFSET - 1, edge_text_offset, 0);
780     }
781     bar_gap = c_base_bar_gap;
782     bar_sep = c_base_bar_sep;
783     bar_width = calc_bar_width(section_right - section_left, index_diff, &bar_gap, &bar_sep, 2);
784     bar_offset = ((double) (section_right - section_left) - ((double) bar_gap * 2.0)) / (double) index_diff;
785     for (i = idx_start; i < idx_end; i++) {
786         /* VISITS */
787         percent = ((double) history_list[i].visit / (double) maxval);
788         if ((history_list[i].month == 1) && (index_diff > 12)) {
789             do_year = 1;
790         } else {
791             do_year = 0;
792         }
793         xbarleft = section_left + 2 + bar_gap + ((i - idx_start) * bar_offset);
794         draw_bar(xbarleft, section_middle - GRAPH_TB_IN_OFFSET - 1, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, do_year, VISITCOLOR);
795 
796         /* SITES */
797         percent = ((double) history_list[i].site / (double) maxval);
798         draw_bar(xbarleft + bar_sep, section_middle - GRAPH_TB_IN_OFFSET - 1, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, SITECOLOR);
799     }
800 
801     /****************************************************
802      * Do the Bottom Right Section                      *
803      *  Volume                                          *
804      ****************************************************/
805     /* Calculate maxval */
806     fmaxval = 0.0;
807     for (i = idx_start; i < idx_end; i++) {
808         if (history_list[i].xfer > fmaxval) {
809             fmaxval = history_list[i].xfer;
810         }
811     }
812     if (fmaxval <= 0.0) {
813         fmaxval = 1.0;
814     }
815     if (g_settings.graphs.lines) {
816         edge_text_offset = section_left - section_right - GRAPH_TEXT_Y_OFFSET;
817         draw_horiz_grid((unsigned long long) fmaxval * VOLUMEBASE, section_left + 1, section_right, section_bottom, section_bottom - section_middle - GRAPH_TB_IN_OFFSET - 1,
818                         edge_text_offset, 1);
819     }
820     bar_width = calc_bar_width(section_right - section_left, index_diff, &bar_gap, &bar_sep, 1);
821     for (i = idx_start; i < idx_end; i++) {
822         /* VOLUME */
823         percent = ((double) history_list[i].xfer / (double) fmaxval);
824         if ((history_list[i].month == 1) && (index_diff > 12)) {
825             do_year = 1;
826         } else {
827             do_year = 0;
828         }
829         draw_bar(section_left + 2 + bar_gap + ((i - idx_start) * bar_offset), main_bottom, section_middle + 1, bar_width, percent, do_year, KBYTECOLOR);
830     }
831 
832     /* save png image */
833     if ((out = fopen(fname, "wb")) != NULL) {
834         gdImagePng(im, out);
835         fclose(out);
836     }
837     /* deallocate the Image memory */
838     gdImageDestroy(im);
839 
840     return (0);
841 }
842 
843 
844 /*****************************************************************
845  *                                                               *
846  * MONTH_GRAPH6  - Month graph with six data sets                *
847  *                                                               *
848  *****************************************************************/
849 int
month_graph6(char * fname,char * title,int month,int year,bool is_month,unsigned long data1[31],unsigned long data2[31],unsigned long data3[31],unsigned long long data4[31],unsigned long data5[31],unsigned long data6[31])850 month_graph6(char *fname,                       /* filename           */
851              char *title,                       /* graph title        */
852              int month,                         /* graph month        */
853              int year,                          /* graph year         */
854              bool is_month,                     /* if true: month, if false: day */
855              unsigned long data1[31],           /* data1 (hits)       */
856              unsigned long data2[31],           /* data2 (files)      */
857              unsigned long data3[31],           /* data3 (sites)      */
858              unsigned long long data4[31],      /* data4 (kbytes)     */
859              unsigned long data5[31],           /* data5 (views)      */
860              unsigned long data6[31])
861 {                                               /* data6 (visits)     */
862 
863     /* local variables */
864 #ifdef HAVE_GDIMAGESTRINGFT
865     int i, x, y;
866     int edge_text_offsetRIGHT;
867     int edge_text_offsetBOTTOM;
868     int bounding_rectange[8];                   /* GD Bounding Rectangle, use to identify outer limits of drawn text */
869     int text_lengthA;                           /* px. Length of the various text strings to be printed */
870     int text_lengthB;
871     int text_lengthC;
872     int text_lengthD;
873     double rotation = 0.0;                      /* Radians(?) +/- PI/2 */
874     int text_height = 0;                        /* Adjust offset for Kanji */
875 #else
876     int i, j, k, k2;
877     int font_char_width;                        /* px. Width of an individual char in a given font - magic constant     */
878     int font_char_height;                       /* px. Height of an individual char in a given font - magic constant    */
879     unsigned char text_separator[2] = "/";      /* Just a simple var to hold the char to separate words. Unsigned for GD */
880 #endif
881     const char *x_label;                        /* The label (a number) to put onto the x axis */
882     int x_colour;                               /* The colour to use for the above label */
883 
884     u_long maxval = 0;
885     double fmaxval = 0.0;
886 
887     int graphx, graphy;
888     int max_i;                                  /* Maximum X Axis. To adjust for varying months et al */
889 
890     int xbarleft;
891 
892     int main_width;                             /* width of the primary section         */
893     int height_upper;                           /* Height of the hits/pages/files section       */
894     int height_middle;                          /* Height of the visits/sites section           */
895     int height_lower;                           /* Height of the volume section                 */
896     int bottom_upper;
897     int bottom_middle;
898     int bottom_lower;
899     int day_middle_x;                           /* px. Mid point offset between veritical bars                          */
900     int text_centring;                          /* px. Use to centre the Y Axis text against the grid lines             */
901     int cur_text_start;                         /* px. start of current text. Used to print months on X Axis            */
902 
903     int section_left;                           /* Section break is here from left      */
904     int section_right;                          /* End of Section from left, right postion      */
905     int section_middle;                         /* Inner Section break is here from top */
906     int section_bottom;                         /* Bottom of vertical section line      */
907 
908     int bar_width;                              /* Width of the individual bars         */
909     double bar_offset;                          /* basic offset of the individual bars  */
910     int bar_gap = 2;                            /* px. Space between bar groupings      */
911     int bar_sep = 2;                            /* px. Space between bars in a group    */
912     int index_diff;                             /* Difference between start/end
913                                                    Use to set a maximum bar width       */
914 
915     int edge_text_offset = 0;                   /* px. Offset from the edge for side text       */
916 
917     /* calc julian date for month */
918     julday = (jdate(1, month, year) % 7);
919 
920     if (is_month) {
921         switch (month) {
922         case 2:
923             max_i = 29;
924             break;
925         case 4:
926         case 6:
927         case 9:
928         case 11:
929             max_i = 30;
930             break;
931         default:
932             max_i = 31;
933             break;
934         }
935         graphx = g_settings.graphs.daily_x;
936         graphy = g_settings.graphs.daily_y;
937     } else {
938         max_i = 24;
939         graphx = g_settings.graphs.hourly_x;
940         graphy = g_settings.graphs.hourly_y;
941     }
942 
943     /* initalize the graph */
944     init_graph(title, graphx, graphy);
945 
946     /* draw inner section lines */
947     section_left = GRAPH_INNER_BOX_LEFT;
948     section_right = graphx - GRAPH_INNER_BOX_RIGHT - 2 - 1;     /* 2 is width of inner box */
949     section_middle = ((graphy - GRAPH_INNER_BOX_TOP - GRAPH_INNER_BOX_BOTTOM) * GRAPH_DAILY_SPLIT_MAIN) + GRAPH_INNER_BOX_TOP;
950     section_bottom = ((graphy - GRAPH_INNER_BOX_TOP - GRAPH_INNER_BOX_BOTTOM) * GRAPH_DAILY_SPLIT_2NDRY) + section_middle;
951     gdImageLine(im, section_left, section_middle, section_right, section_middle, black);
952     gdImageLine(im, section_left - 1, section_middle - 1, section_right, section_middle - 1, white);
953     gdImageLine(im, section_left, section_bottom, section_right, section_bottom, black);
954     gdImageLine(im, section_left, section_bottom - 1, section_right, section_bottom - 1, white);
955 
956     /* Set up base calculations */
957     main_width = graphx - (GRAPH_INNER_BOX_LEFT + 1) - (GRAPH_INNER_BOX_RIGHT + 1) - 2;
958     height_upper = section_middle - GRAPH_INNER_BOX_TOP - (GRAPH_TB_IN_OFFSET * 2) - 3;
959     bottom_upper = section_middle - (GRAPH_TB_IN_OFFSET * 2);
960     height_middle = section_bottom - section_middle - (GRAPH_TB_IN_OFFSET * 2) - 3;
961     bottom_middle = section_bottom - (GRAPH_TB_IN_OFFSET * 2);
962     height_lower = graphy - GRAPH_INNER_BOX_BOTTOM - section_bottom - (GRAPH_TB_IN_OFFSET * 2) - 3;
963     bottom_lower = graphy - GRAPH_INNER_BOX_BOTTOM - (GRAPH_TB_IN_OFFSET * 2);
964 
965 //    gdImageLine (im, GRAPH_INNER_BOX_LEFT + 1, section_middle + 15, GRAPH_INNER_BOX_LEFT + 1 + main_width, section_middle + 15, red);
966 
967 #ifdef HAVE_GDIMAGESTRINGFT
968     /* Locate RIGHT and BOTTOM Edge Offsets for Displaying Text */
969     /* Offset at 100,100 to better see =/- offsets. Adjust accordingly in edge_text_offsets.
970      *  The x/y position is at the base of the line characters rest on. So 'g' for example, goes *below* the line.
971      * '-2' is a magic offset. Unfortunately. Just looks better with the shadow etc. Could probably account for that in the math but... */
972     gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 100, 100, _("Pages"));
973 //    for (i=0; i<8; i++) { printf("%d:%d  ", i, bounding_rectange[i]); }; printf("\n");
974     edge_text_offsetRIGHT =
975         graphx - (GRAPH_SHADOW_WIDTH + (((GRAPH_INNER_BOX_RIGHT - abs(bounding_rectange[5] - bounding_rectange[1]) + 1) / 2) + abs(100 - bounding_rectange[1]) - 2));
976     edge_text_offsetBOTTOM =
977         graphy - (GRAPH_SHADOW_WIDTH + (((GRAPH_INNER_BOX_BOTTOM - abs(bounding_rectange[7] - bounding_rectange[1]) + 1) / 2) + abs(100 - bounding_rectange[1]) - 2));
978 
979     if (g_settings.graphs.legend) {             /* print color coded legends? */
980         if (g_settings.graphs.use_kanji == false) {
981             x = edge_text_offsetRIGHT;
982             y = graphy - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + 1);
983             rotation = PI / 2.0;
984         } else {
985             rotation = -PI / 2.0;               /* Invert Angle of rotation - text should flow from top down, not bottom up as per English et al */
986             gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, 100, 100, _("Pages"));
987             text_height = abs(bounding_rectange[4] - bounding_rectange[0]);
988             edge_text_offsetRIGHT =
989                 graphx - (GRAPH_SHADOW_WIDTH + (((GRAPH_INNER_BOX_RIGHT - abs(bounding_rectange[6] - bounding_rectange[0]) + 1) / 2) + abs(100 - bounding_rectange[6]) - 1));
990             x = edge_text_offsetRIGHT;
991             y = graphy - (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_RIGHT + 1);
992         }
993 
994         if (g_settings.graphs.use_kanji == true) {
995             y = bottom_lower - height_lower + text_height;
996         }
997         /* Volume Legend */
998         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Volume"));
999         gdImageStringFT(im, &bounding_rectange[0], KBYTECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Volume"));
1000 
1001         /* Sites/Visits Legend */
1002         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Visits"));
1003         text_lengthA = abs(bounding_rectange[4]);
1004         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, "/");
1005         text_lengthD = abs(bounding_rectange[4]);
1006 
1007         x = edge_text_offsetRIGHT;
1008         if (g_settings.graphs.use_kanji == false) {
1009             y = section_bottom - GRAPH_TEXT_Y_OFFSET;
1010         } else {
1011             y = bottom_middle - height_middle + text_height;
1012         }
1013         if (is_month == true) {
1014             gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Visits"));
1015             gdImageStringFT(im, &bounding_rectange[0], VISITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Visits"));
1016             if (g_settings.graphs.use_kanji == false) {
1017                 y = section_bottom - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1);
1018             } else {
1019                 y += text_lengthA + 1;
1020             }
1021             gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, "/");
1022             gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, "/");
1023             if (g_settings.graphs.use_kanji == false) {
1024                 y = section_bottom - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD);
1025             } else {
1026                 y += text_height + 1;
1027             }
1028         }
1029         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Sites"));
1030         gdImageStringFT(im, &bounding_rectange[0], SITECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Sites"));
1031 
1032         /* Hits/Files/Pages Legend */
1033         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Pages"));
1034         text_lengthA = abs(bounding_rectange[4]);
1035         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Files"));
1036         text_lengthB = abs(bounding_rectange[4]);
1037         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, _("Hits"));
1038         text_lengthC = abs(bounding_rectange[4]);
1039 
1040         x = edge_text_offsetRIGHT;
1041         y = section_middle - GRAPH_TEXT_Y_OFFSET;
1042         if (g_settings.graphs.use_kanji == false) {
1043             y = section_middle - GRAPH_TEXT_Y_OFFSET;
1044         } else {
1045             y = bottom_upper - height_upper + text_height;
1046         }
1047         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Pages"));
1048         gdImageStringFT(im, &bounding_rectange[0], PAGECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Pages"));
1049 
1050         if (g_settings.graphs.use_kanji == false) {
1051             y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1);
1052         } else {
1053             y += text_lengthA + 1;
1054         }
1055         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, "/");
1056         gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, "/");
1057 
1058         if (g_settings.graphs.use_kanji == false) {
1059             y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD);
1060         } else {
1061             y += text_height + 1;
1062         }
1063         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Files"));
1064         gdImageStringFT(im, &bounding_rectange[0], FILECOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Files"));
1065 
1066         if (g_settings.graphs.use_kanji == false) {
1067             y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD + text_lengthB + 1);
1068         } else {
1069             y += text_lengthB + 1;
1070         }
1071         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, "/");
1072         gdImageStringFT(im, &bounding_rectange[0], black, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, "/");
1073 
1074         if (g_settings.graphs.use_kanji == false) {
1075             y = section_middle - (GRAPH_TEXT_Y_OFFSET + text_lengthA + 1 + text_lengthD + text_lengthB + 1 + text_lengthD);
1076         } else {
1077             y += text_height + 1;
1078         }
1079         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x + 1, y + 1, _("Hits"));
1080         gdImageStringFT(im, &bounding_rectange[0], HITCOLOR, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, rotation, x, y, _("Hits"));
1081     }
1082 #else
1083     /* magic numbers */
1084     font_char_width = 6;
1085     font_char_height = 10;
1086     if (g_settings.graphs.legend) {             /* print color coded legends? */
1087         /* Volume Legend */
1088         /* i = (strlen (_("Volume")) * font_char_width); */
1089         edge_text_offset = graphx - GRAPH_INNER_BOX_RIGHT;      /* + GRAPH_TEXT_X_OFFSET; */
1090         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, graphy - GRAPH_INNER_BOX_BOTTOM - GRAPH_TEXT_Y_OFFSET - 1, (unsigned char *) _("Volume"), dkgrey);
1091         gdImageStringUp(im, gdFontSmall, edge_text_offset, graphy - GRAPH_INNER_BOX_BOTTOM - GRAPH_TEXT_Y_OFFSET, (unsigned char *) _("Volume"), KBYTECOLOR);
1092 
1093         /* Sites/Visits Legend */
1094         i = (strlen(_("Sites")) * font_char_width);
1095         k = font_char_width / 2;                /* Half Char Sep */
1096         k2 = font_char_width * 2;               /* Half Space '/' Half Space */
1097         /* j = (strlen (_("Sites")) * font_char_width); */
1098         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_bottom - GRAPH_TEXT_Y_OFFSET - 1, (unsigned char *) _("Sites"), dkgrey);
1099         gdImageStringUp(im, gdFontSmall, edge_text_offset, section_bottom - GRAPH_TEXT_Y_OFFSET, (unsigned char *) _("Sites"), SITECOLOR);
1100         if (is_month == true) {
1101             gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k - 1, (unsigned char *) text_separator, dkgrey);
1102             gdImageStringUp(im, gdFontSmall, edge_text_offset, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k, (unsigned char *) text_separator, black);
1103             gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k2 - 1, (unsigned char *) _("Visits"), dkgrey);
1104             gdImageStringUp(im, gdFontSmall, edge_text_offset, section_bottom - GRAPH_TEXT_Y_OFFSET - i - k2, (unsigned char *) _("Visits"), VISITCOLOR);
1105         }
1106 
1107         /* Hits/Files/Pages Legend */
1108         i = (strlen(_("Pages")) * font_char_width);
1109         j = (strlen(_("Files")) * font_char_width);
1110         /* k same as above */
1111         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - 1, (unsigned char *) _("Pages"), dkgrey);
1112         gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET, (unsigned char *) _("Pages"), PAGECOLOR);
1113         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - k - 1, (unsigned char *) text_separator, dkgrey);
1114         gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - k, (unsigned char *) text_separator, black);
1115         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - k2 - 1, (unsigned char *) _("Files"), dkgrey);
1116         gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - k2, (unsigned char *) _("Files"), FILECOLOR);
1117         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 - k - 1, (unsigned char *) text_separator, dkgrey);
1118         gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 - k - 1, (unsigned char *) text_separator, black);
1119         gdImageStringUp(im, gdFontSmall, edge_text_offset + 1, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 * 2 - 1, (unsigned char *) _("Hits"), dkgrey);
1120         gdImageStringUp(im, gdFontSmall, edge_text_offset, section_middle - GRAPH_TEXT_Y_OFFSET - i - j - k2 * 2 - 1, (unsigned char *) _("Hits"), HITCOLOR);
1121     }
1122 #endif
1123 
1124 
1125     /****************************************************
1126      * Do the Upper Section                             *
1127      *  Hits, Files, Pages                              *
1128      ****************************************************/
1129     /* get max val */
1130     for (i = 0; i < 31; i++) {
1131         if (data1[i] > maxval) {
1132             maxval = data1[i];
1133         }
1134         if (data2[i] > maxval) {
1135             maxval = data2[i];
1136         }
1137         if (data5[i] > maxval) {
1138             maxval = data5[i];
1139         }
1140     }
1141     if (maxval <= 0) {
1142         maxval = 1;
1143     }
1144 
1145     if (g_settings.graphs.lines) {
1146         edge_text_offset = 6 + GRAPH_TEXT_Y_OFFSET + 1;
1147         /* tiny font has height of 6 */
1148         draw_horiz_grid(maxval, section_left + 1, section_right, bottom_upper, height_upper, edge_text_offset, 0);
1149     }
1150     /* width/offset calculations */
1151     index_diff = max_i;
1152     bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 3);
1153     bar_offset = (((double) main_width - ((double) bar_gap * 2.0)) / (double) index_diff);
1154     day_middle_x = (bar_width + bar_sep * 2) / 2 + 1;   /* Calc for the mid point on which to then draw the month on the X Axis */
1155 
1156     for (i = 0; i < max_i; i++) {
1157         /* HITS */
1158         percent = ((double) data1[i] / (double) maxval);
1159         xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + (i * bar_offset);
1160         draw_bar(xbarleft, bottom_upper, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, HITCOLOR);
1161 
1162         /* X Axis */
1163         /* Set X Label values for Daily/Hours. Alter if is_month. */
1164         x_colour = black;
1165         x_label = numchar[i];
1166         if (is_month) {
1167             x_label = numchar[i + 1];
1168             if ((julday % 7 == 6) || (julday % 7 == 0)) {
1169                 x_colour = HITCOLOR;
1170             }
1171         }
1172 #ifdef HAVE_GDIMAGESTRINGFT
1173         gdImageStringFT(NULL, &bounding_rectange[0], x_colour, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, 0, 0, (char *) x_label);
1174         text_centring = abs(bounding_rectange[4]) / 2;
1175         cur_text_start = xbarleft + day_middle_x - text_centring;
1176         gdImageStringFT(im, &bounding_rectange[0], x_colour, graph_font_label, GRAPH_FONT_SIZE_SMALL, 0.0, cur_text_start, edge_text_offsetBOTTOM, (char *) x_label);
1177 #else
1178         text_centring = (((double) strlen(x_label)) / 2.0) * (double) font_char_width;
1179         cur_text_start = xbarleft + day_middle_x - text_centring;
1180         gdImageString(im, gdFontSmall, cur_text_start, bottom_lower + GRAPH_TEXT_Y_OFFSET, (unsigned char *) x_label, x_colour);
1181 #endif
1182         julday++;
1183 
1184         /* FILES */
1185         percent = ((double) data2[i] / (double) maxval);
1186         draw_bar(xbarleft + bar_sep, bottom_upper, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, FILECOLOR);
1187 
1188         /* PAGES */
1189         percent = ((double) data5[i] / (double) maxval);
1190         draw_bar(xbarleft + bar_sep + bar_sep, bottom_upper, GRAPH_INNER_BOX_TOP + 1, bar_width, percent, 0, PAGECOLOR);
1191     }
1192 
1193     /****************************************************
1194      * Do the Middle Section                            *
1195      *  Sites, Visits                                   *
1196      ****************************************************/
1197     /* get max val */
1198     maxval = 0;
1199     for (i = 0; i < max_i; i++) {
1200         if (data3[i] > maxval) {
1201             maxval = data3[i];
1202         }
1203         if (data6[i] > maxval) {
1204             maxval = data6[i];
1205         }
1206     }
1207     if (maxval <= 0) {
1208         maxval = 1;
1209     }
1210 
1211     if (g_settings.graphs.lines) {
1212         /* re-use edge_text_offset from previous calc */
1213         draw_horiz_grid(maxval, section_left + 1, section_right, bottom_middle, height_middle, edge_text_offset, 0);
1214     }
1215     bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 2);
1216     for (i = 0; i < max_i; i++) {
1217         /* Visits */
1218         percent = ((double) data6[i] / (double) maxval);
1219         xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + (i * bar_offset);
1220         draw_bar(xbarleft, bottom_middle, bottom_middle - height_middle, bar_width, percent, 0, VISITCOLOR);
1221 
1222         /* Sites */
1223         percent = ((double) data3[i] / (double) maxval);
1224         draw_bar(xbarleft + bar_sep, bottom_middle, bottom_middle - height_middle, bar_width, percent, 0, SITECOLOR);
1225     }
1226 
1227     /****************************************************
1228      * Do the Lower Section                             *
1229      *  Volume Transferred                              *
1230      ****************************************************/
1231     /* get max val */
1232     fmaxval = 0.0;
1233     for (i = 0; i < max_i; i++) {
1234         if (data4[i] > fmaxval) {
1235             fmaxval = data4[i];
1236         }
1237     }
1238     if (fmaxval <= 0.0) {
1239         fmaxval = 1.0;
1240     }
1241     if (g_settings.graphs.lines) {
1242         /* re-use edge_text_offset from previous calc */
1243         draw_horiz_grid((unsigned long long) fmaxval, section_left + 1, section_right, bottom_lower, height_lower, edge_text_offset, 1);
1244     }
1245     bar_width = calc_bar_width(main_width, index_diff, &bar_gap, &bar_sep, 2);
1246     for (i = 0; i < max_i; i++) {
1247         /* VOLUME */
1248         percent = ((double) data4[i] / (double) fmaxval);
1249         xbarleft = GRAPH_INNER_BOX_LEFT + GRAPH_LR_IN_OFFSET + bar_gap + (i * bar_offset);
1250         draw_bar(xbarleft, bottom_lower, bottom_lower - height_lower, bar_width, percent, 0, KBYTECOLOR);
1251     }
1252 
1253     /* open file for writing */
1254     if ((out = fopen(fname, "wb")) != NULL) {
1255         gdImagePng(im, out);
1256         fclose(out);
1257     }
1258     /* deallocate memory */
1259     gdImageDestroy(im);
1260 
1261     return (0);
1262 }
1263 
1264 
1265 /*****************************************************************
1266  *                                                               *
1267  * PIE_CHART  - draw a pie chart (10 data items max)             *
1268  *                                                               *
1269  *****************************************************************/
1270 
1271 int
pie_chart(char * fname,char * title,unsigned long long t_val,unsigned long long data1[],char * legend[])1272 pie_chart(char *fname, char *title, unsigned long long t_val, unsigned long long data1[], char *legend[])
1273 {
1274     int i, x, slice_percent, y;
1275     int centre_x;                               /* Centre for the X location */
1276     int centre_y;                               /* Centre for the Y location */
1277     int diamtr_x;                               /* diameter X Axis */
1278     int diamtr_y;                               /* diameter Y Axis */
1279     double XtoYratio = 5.0 / 6.0;               /* Ratio of set X to Y dynamic => 5/6 = 0.833... */
1280     int text_max_x;                             /* Max right point for text listing */
1281     double s_arc = 0.0;
1282     int r, g, b;
1283     int colour_index;
1284     char buffer[512];
1285 
1286     struct pie_data gdata;
1287     int bounding_rectange[8];                   /* GD Bounding Rectangle, use to identify outer limits of drawn text */
1288 
1289     /* init graph and colors */
1290     init_graph(title, g_settings.graphs.pie_x, g_settings.graphs.pie_y);
1291 
1292     /* First deallocate all the hit etc colours, and setup for using pie colours instead */
1293     for (i = 4 + 6 - 1; i > 4 - 1; i--) {
1294         gdImageColorDeallocate(im, i);
1295     }
1296     r = getred(pie_color1);
1297     g = getgreen(pie_color1);
1298     b = getblue(pie_color1);
1299     colour_index = gdImageColorAllocate(im, r, g, b);
1300     r = getred(pie_color2);
1301     g = getgreen(pie_color2);
1302     b = getblue(pie_color2);
1303     colour_index = gdImageColorAllocate(im, r, g, b);
1304     r = getred(pie_color3);
1305     g = getgreen(pie_color3);
1306     b = getblue(pie_color3);
1307     colour_index = gdImageColorAllocate(im, r, g, b);
1308     r = getred(pie_color4);
1309     g = getgreen(pie_color4);
1310     b = getblue(pie_color4);
1311     colour_index = gdImageColorAllocate(im, r, g, b);
1312 
1313     /* Default extra colours */
1314     colour_index = gdImageColorAllocate(im, 0, 192, 255);       /* cyan     */
1315     colour_index = gdImageColorAllocate(im, 255, 255, 0);       /* yellow   */
1316     colour_index = gdImageColorAllocate(im, 128, 0, 128);       /* purple   */
1317     colour_index = gdImageColorAllocate(im, 128, 255, 192);     /* ltgreen  */
1318     colour_index = gdImageColorAllocate(im, 255, 0, 255);       /* ltpurple */
1319     colour_index = gdImageColorAllocate(im, 255, 196, 128);     /* brown    */
1320 
1321     /* base caclulations */
1322     centre_x = (int) ((double) g_settings.graphs.pie_x * (1.0 / 3.0) + 1.0);
1323     centre_y = g_settings.graphs.pie_y / 2;
1324     diamtr_x = (centre_x - GRAPH_INNER_BOX_LEFT - GRAPH_PIE_X_INSET) * 2;
1325     diamtr_y = (int) ((double) diamtr_x * XtoYratio + 1.0);
1326     text_max_x = g_settings.graphs.pie_x - GRAPH_INNER_BOX_LEFT - GRAPH_PIE_X_INSET;
1327     VPRINT(VERBOSE5, "PIE: cx: %d  cy: %d  dx: %d  dy: %d  x: %d  y: %d\n", centre_x, centre_y, diamtr_x, diamtr_y, g_settings.graphs.pie_x, g_settings.graphs.pie_y);
1328 
1329     /* do the circle... */
1330     gdImageArc(im, centre_x, centre_y, diamtr_x, diamtr_y, 0, 360, black);
1331     gdImageArc(im, centre_x, centre_y + 10, diamtr_x - 2, diamtr_y - 2, 2, 178, black);
1332     gdImageFillToBorder(im, centre_x, centre_y + (diamtr_y / 2) + 1, black, black);
1333 
1334     /* slice the pie */
1335     gdata = *calc_arc(0.0, 0.0, centre_x, centre_y, diamtr_x, diamtr_y);
1336     gdImageLine(im, centre_x, centre_y, gdata.x, gdata.y, black);       /* inital line           */
1337 
1338     /* Do the fills, then come back and do the text. Else, can get text in the pie itself which occludes fills. */
1339     y = centre_y - (diamtr_y / 2);
1340     for (i = 0; i < 10; i++) {                  /* run through data array      */
1341         if ((data1[i] != 0) && (s_arc < 1.0)) { /* make sure valid slice       */
1342             slice_percent = (((double) data1[i] / t_val) + 0.005) * 100.0;
1343             if (slice_percent < 1)
1344                 break;
1345 
1346             if (s_arc + ((double) slice_percent / 100.0) >= 1.0) {
1347                 gdata = *calc_arc(s_arc, 1.0, centre_x, centre_y, diamtr_x, diamtr_y);
1348                 s_arc = 1.0;
1349             } else {
1350                 gdata = *calc_arc(s_arc, s_arc + ((double) slice_percent / 100.0), centre_x, centre_y, diamtr_x, diamtr_y);
1351                 s_arc += (double) slice_percent / 100.0;
1352             }
1353 
1354             gdImageLine(im, centre_x, centre_y, gdata.x, gdata.y, black);
1355             gdImageFill(im, gdata.mx, gdata.my, i + 4);
1356         }
1357     }
1358 
1359     if (s_arc < 1.0) {                          /* anything left over?        */
1360         gdata = *calc_arc(s_arc, 1.0, centre_x, centre_y, diamtr_x, diamtr_y);
1361         gdImageFill(im, gdata.mx, gdata.my, white);
1362     }
1363 
1364     /* Repeat drawing loop for text as longer text was stopping the fill from fully ... filling. */
1365 #ifdef HAVE_GDIMAGESTRINGFT
1366     y = centre_y - (diamtr_y / 2) + GRAPH_FONT_SIZE_MEDIUM;
1367 #else
1368     y = centre_y - (diamtr_y / 2);
1369 #endif
1370     s_arc = 0.0;
1371     for (i = 0; i < 10; i++) {                  /* run through data array      */
1372         if ((data1[i] != 0) && (s_arc < 1.0)) { /* make sure valid slice       */
1373             slice_percent = (((double) data1[i] / t_val) + 0.005) * 100.0;
1374             if (slice_percent < 1)
1375                 break;
1376             if (s_arc + ((double) slice_percent / 100.0) >= 1.0) {
1377                 s_arc = 1.0;
1378             } else {
1379                 s_arc += (double) slice_percent / 100.0;
1380             }
1381             snprintf(buffer, sizeof(buffer) - 1, "%s (%d%%)", legend[i], slice_percent);
1382 
1383 #ifdef HAVE_GDIMAGESTRINGFT
1384             /* "print" the string to get an idea of actual size and hence find the centre */
1385             gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 100, 100, buffer);
1386             x = text_max_x - abs(bounding_rectange[4] - bounding_rectange[0]);
1387 
1388             gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, buffer);
1389             gdImageStringFT(im, &bounding_rectange[0], i + 4, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, buffer);
1390             y += (GRAPH_FONT_SIZE_MEDIUM * 2);
1391 #else
1392             x = text_max_x - (strlen(buffer) * 7);
1393             gdImageString(im, gdFontMediumBold, x + 1, y + 1, (unsigned char *) buffer, black);
1394             gdImageString(im, gdFontMediumBold, x, y, (unsigned char *) buffer, i + 4);
1395             y += 20;
1396 #endif
1397         }
1398     }
1399 
1400     if (s_arc < 1.0) {                          /* anything left over?        */
1401         snprintf(buffer, sizeof(buffer) - 1, "%s (%d%%)", _("Other"), 100 - (int) (s_arc * 100));
1402 #ifdef HAVE_GDIMAGESTRINGFT
1403         gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, 0, 0, buffer);
1404         x = text_max_x - bounding_rectange[4];
1405         gdImageStringFT(im, &bounding_rectange[0], dkgrey, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x + 1, y + 1, buffer);
1406         gdImageStringFT(im, &bounding_rectange[0], white, graph_font_label, GRAPH_FONT_SIZE_MEDIUM, 0.0, x, y, buffer);
1407 #else
1408         x = text_max_x - (strlen(buffer) * 7);
1409         gdImageString(im, gdFontMediumBold, x + 1, y + 1, (unsigned char *) buffer, black);
1410         gdImageString(im, gdFontMediumBold, x, y, (unsigned char *) buffer, white);
1411 #endif
1412     }
1413 
1414     /* save png image */
1415     if ((out = fopen(fname, "wb")) != NULL) {
1416         gdImagePng(im, out);
1417         fclose(out);
1418     }
1419     /* deallocate memory */
1420     gdImageDestroy(im);
1421 
1422     return (0);
1423 }
1424 
1425 /*****************************************************************
1426  *                                                               *
1427  * CALC_ARC  - generate x,y coordinates for pie chart            *
1428  *                                                               *
1429  *****************************************************************/
1430 
1431 static struct pie_data *
calc_arc(double min,double max,int cx,int cy,int dx,int dy)1432 calc_arc(double min, double max, int cx, int cy, int dx, int dy)
1433     /* cx, cy - Centre X & Y
1434      * dx, dy - Diameter X & Y */
1435 {
1436     static struct pie_data data;
1437     double d;
1438 
1439     /* Calculate max line */
1440     d = max;
1441     data.x = cos(d * (2 * PI)) * ((dx - 2) / 2) + cx;
1442     data.y = sin(d * (2 * PI)) * ((dy - 2) / 2) + cy;
1443     /* Now get mid-point  */
1444     d = ((min + max) / 2);
1445     data.mx = cos(d * (2 * PI)) * (dx / 3) + cx;
1446     data.my = sin(d * (2 * PI)) * (dy / 3) + cy;
1447     return &data;
1448 }
1449 
1450 /****************************************************************
1451  *                                                              *
1452  * INIT_GRAPH  - initalize graph and draw borders               *
1453  *   1 px black rectange on outer edge                          *
1454  *   G_SHADOW_WIDTH px white/grey shadow effect                 *
1455  *   1 + 1 px black/white shadow'd line rectange - inner box    *
1456  *     inset G_INNER_BOX_TOP & G_INNER_BOX_LEFT                 *
1457  *     white line is inset -1 again                             *
1458  *                                                              *
1459  ****************************************************************/
1460 
1461 static void
init_graph(char * title,int xsize,int ysize)1462 init_graph(char *title, int xsize, int ysize)
1463 {
1464     int i, r, g, b;
1465     int edge_text_offset;                       /* px. Offset from the edge for descriptive text        */
1466 
1467 #ifdef HAVE_GDIMAGESTRINGFT
1468     int bounding_rectange[8];                   /* GD Bounding Rectangle, use to identify outer limits of drawn text */
1469 #else
1470     int font_char_height;                       /* px. Height of an individual char in a given font - magic constant    */
1471 #endif
1472 
1473     im = gdImageCreate(xsize, ysize);
1474 
1475     /* allocate color maps, background color first (grey) */
1476     grey = gdImageColorAllocate(im, 192, 192, 192);
1477     dkgrey = gdImageColorAllocate(im, 128, 128, 128);
1478     black = gdImageColorAllocate(im, 0, 0, 0);
1479     white = gdImageColorAllocate(im, 255, 255, 255);
1480     /* By default, we set up all graphs index'ed off hits, pages etc
1481      *  which means for pie charts we need to deallocate these 6 colours */
1482     r = getred(hit_color);
1483     g = getgreen(hit_color);
1484     b = getblue(hit_color);
1485     hitcolor = gdImageColorAllocate(im, r, g, b);
1486     r = getred(site_color);
1487     g = getgreen(site_color);
1488     b = getblue(site_color);
1489     sitecolor = gdImageColorAllocate(im, r, g, b);
1490     r = getred(file_color);
1491     g = getgreen(file_color);
1492     b = getblue(file_color);
1493     filecolor = gdImageColorAllocate(im, r, g, b);
1494     r = getred(kbyte_color);
1495     g = getgreen(kbyte_color);
1496     b = getblue(kbyte_color);
1497     kbytecolor = gdImageColorAllocate(im, r, g, b);
1498     r = getred(page_color);
1499     g = getgreen(page_color);
1500     b = getblue(page_color);
1501     pagecolor = gdImageColorAllocate(im, r, g, b);
1502     r = getred(visit_color);
1503     g = getgreen(visit_color);
1504     b = getblue(visit_color);
1505     visitcolor = gdImageColorAllocate(im, r, g, b);
1506 
1507     /* make borders */
1508 
1509     for (i = 1; i < (GRAPH_SHADOW_WIDTH + 1); i++) {    /* do shadow effect */
1510         gdImageLine(im, i, i, xsize - (i + 1), i, white);
1511         gdImageLine(im, i, i, i, ysize - (i + 1), white);
1512         gdImageLine(im, i + 1, ysize - (i + 1), xsize - (i + 1), ysize - (i + 1), dkgrey);
1513         gdImageLine(im, xsize - (i + 1), i + 1, xsize - (i + 1), ysize - (i + 1), dkgrey);
1514     }
1515 
1516     gdImageRectangle(im, GRAPH_INNER_BOX_LEFT, GRAPH_INNER_BOX_TOP, xsize - (GRAPH_INNER_BOX_LEFT + 1), ysize - (GRAPH_INNER_BOX_BOTTOM + 1), black);
1517     gdImageRectangle(im, (GRAPH_INNER_BOX_LEFT - 1), (GRAPH_INNER_BOX_TOP - 1), xsize - (GRAPH_INNER_BOX_LEFT + 2), ysize - (GRAPH_INNER_BOX_BOTTOM + 2), white);
1518     gdImageRectangle(im, 0, 0, xsize - 1, ysize - 1, black);
1519 
1520 #ifdef HAVE_GDIMAGESTRINGFT
1521     /* display the graph title */
1522     gdImageStringFT(NULL, &bounding_rectange[0], 0, graph_font_default, GRAPH_FONT_SIZE, 0.0, 0, 0, title);
1523     edge_text_offset =
1524         (GRAPH_SHADOW_WIDTH + GRAPH_INNER_BOX_TOP) - (GRAPH_SHADOW_WIDTH + ((GRAPH_INNER_BOX_TOP - (abs(bounding_rectange[5]) + abs(bounding_rectange[1]) + 1)) / 2) +
1525                                                       abs(bounding_rectange[1])) + 2;
1526     gdImageStringFT(im, &bounding_rectange[0], filecolor, graph_font_default, GRAPH_FONT_SIZE, 0.0, GRAPH_INNER_BOX_LEFT, edge_text_offset, title);
1527 #else
1528     font_char_height = 10;
1529     edge_text_offset = GRAPH_INNER_BOX_TOP - font_char_height - GRAPH_TEXT_Y_OFFSET - 1;
1530     gdImageString(im, gdFontMediumBold, GRAPH_INNER_BOX_LEFT, edge_text_offset, (unsigned char *) title, filecolor);
1531 #endif
1532 
1533     return;
1534 }
1535 
1536 
1537 /****************************************************************
1538  * initialise_graphs                                            *
1539  *                                                              *
1540  * Any graphical Initialisation is done here. Dur.              *
1541  * Run before any processing. If we need to fail & exit, better *
1542  *   done early.                                                *
1543  *                                                              *
1544  * Debugging TTF woes.                                          *
1545  * - use 'fc-list'                                              *
1546  *      by default we try and use fontconfig.                   *
1547  *      fc-list will show all avilable 'fonts' on a system      *
1548  *      Not so much by actual name, more family/alias.          *
1549  *      see /etc/fonts/... for details.                         *
1550  * - 'export FC_DEBUG=1' before an awfful run.                  *
1551  *      Use fontconfig internal debugging. If "problems", this  *
1552  *       will generate a LOT of output. Redirect to file!       *
1553  *      When working will see the actual file on disk used.     *
1554  *      eg:                                                     *
1555  *       file: "/usr/share/fonts/default/Type1/n019003l.pfb"(w) *
1556  *                                                              *
1557  * Recommended to have the 'urw-fonts' package installed.       *
1558  *                                                              *
1559  ****************************************************************/
1560 
1561 void
initialise_graphs(void)1562 initialise_graphs(void)
1563 {
1564     int FT_Config_Success;
1565 
1566 #ifdef HAVE_GDFTUSEFONTCONFIG
1567     FT_Config_Success = gdFTUseFontConfig(1);
1568     if (!FT_Config_Success) {
1569         /* Bummer. Font config failed to work. Fallback to hardcoded path. */
1570         graph_font_default = GRAPH_FONT_FULLPATH_DEFAULT;
1571         graph_font_label = GRAPH_FONT_FULLPATH_LABEL;
1572         ERRVPRINT(VERBOSE1, "%s: %d\n", _("Error in setting gdFTUseFontConfig"), FT_Config_Success);
1573         ERRVPRINT(VERBOSE2, "Default Font Path: %s\nDefault Font Label Path: %s\n", graph_font_default, graph_font_label);
1574     } else {
1575         VPRINT(VERBOSE2, "%s: %d\n", _("Success in setting gdFTUseFontConfig"), FT_Config_Success);
1576     }
1577 #endif
1578 }
1579