1 /***             analog 6.0             http://www.analog.cx/             ***/
2 /*** This program is copyright (c) Stephen R. E. Turner 1995 - 2004 except as
3  *** stated otherwise.
4  ***
5  *** This program is free software. You can redistribute it and/or modify it
6  *** under the terms of version 2 of the GNU General Public License, which you
7  *** should have received with it.
8  ***
9  *** This program is distributed in the hope that it will be useful, but
10  *** without any warranty, expressed or implied.   ***/
11 
12 /*** outhtml.c; HTML output ***/
13 
14 #include "anlghea3.h"
15 
16 /* Page width */
html_pagewidth(Outchoices * od)17 unsigned int html_pagewidth(Outchoices *od) {
18   return od->htmlpagewidth;
19 }
20 
21 /* The top of the output if we are in CGI mode */
html_cgihead(FILE * outf,Outchoices * od)22 void html_cgihead(FILE *outf, Outchoices *od) {
23   fprintf(outf, "Content-Type: text/html; charset=%s\n\n",
24 	  od->lngstr[charset_]);
25 }
26 
27 /* Stuff this output style needs in the page header */
html_stylehead(FILE * outf,Outchoices * od)28 void html_stylehead(FILE *outf, Outchoices *od) {
29   fputs("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n", outf);
30   fputs("<html>\n<head>\n", outf);
31   fprintf(outf, "<meta http-equiv=\"Content-Type\" "
32 	  "content=\"text/html; charset=%s\">\n", od->lngstr[charset_]);
33   if (od->norobots)
34     fputs("<meta name=\"robots\" content=\"noindex,nofollow\">\n", outf);
35   fprintf(outf, "<meta name=\"GENERATOR\" content=\"analog %s\">\n",
36 	  VERSION);
37   fprintf(outf, "<title>%s ", od->lngstr[webstatsfor_]);
38   htmlputs(outf, od, od->hostname, FROM_CFG);
39   fputs("</title>\n", outf);
40   if (!strcaseeq(od->stylesheet, "none")) {
41     fputs("<link href=\"", outf);
42     htmlputs(outf, od, od->stylesheet, IN_HREF);
43     fputs("\" rel=\"stylesheet\">\n", outf);
44   }
45   fputs("</head>\n", outf);
46   fputs("<body>\n", outf);
47 }
48 
49 /* The title of the page, plus the user's HEADERFILE */
html_pagetitle(FILE * outf,Outchoices * od)50 void html_pagetitle(FILE *outf, Outchoices *od) {
51   fputs("<h1><a NAME=\"Top\">", outf);
52   if (!strcaseeq(od->logo, "none")) {
53     fputs("<IMG src=\"", outf);
54     if (od->logo[0] != '/' && strstr(od->logo, "://") == NULL)
55       htmlputs(outf, od, od->imagedir, IN_HREF);
56     htmlputs(outf, od, od->logo, IN_HREF);
57     if (STREQ(od->logo, "analogo"))
58       fprintf(outf, ".%s", od->pngimages?"png":"gif");
59     /* Above: '.' not EXTSEP even on RISC OS */
60     fputs("\" alt=\"\"> ", outf);
61   }
62   if (strcaseeq(od->hosturl, "none")) {
63     fprintf(outf, "%s</a> ", od->lngstr[webstatsfor_]);
64     htmlputs(outf, od, od->hostname, FROM_CFG);
65   }
66   else {
67     fprintf(outf, "%s</a> <a HREF=\"", od->lngstr[webstatsfor_]);
68     htmlputs(outf, od, od->hosturl, IN_HREF);
69     fputs("\">", outf);
70     htmlputs(outf, od, od->hostname, FROM_CFG);
71     fputs("</a>", outf);
72   }
73   fputs("</h1>\n\n", outf);
74 
75   if (!strcaseeq(od->headerfile, "none"))
76     html_includefile(outf, od, od->headerfile, 'h');
77 }
78 
79 /* Program start time, and logfile start and end times */
html_timings(FILE * outf,Outchoices * od,Dateman * dman)80 void html_timings(FILE *outf, Outchoices *od, Dateman *dman) {
81   extern timecode_t starttimec;
82 
83   char **lngstr = od->lngstr;
84 
85   double t0;
86   int t1, t2;
87 
88   if (od->runtime)
89     fprintf(outf, "%s %s.\n<br>", lngstr[progstart_],
90 	    timesprintf(od, lngstr[datefmt2_], starttimec, UNSET));
91 
92   if (dman->firsttime <= dman->lasttime) {
93     fprintf(outf, "%s %s ", lngstr[reqstart_],
94 	    timesprintf(od, lngstr[datefmt2_], dman->firsttime, UNSET));
95     fprintf(outf, "%s %s", lngstr[to_],
96 	    timesprintf(od, lngstr[datefmt2_], dman->lasttime, UNSET));
97     t0 = (dman->lasttime - dman->firsttime) / 1440.0 + 0.005;
98     t1 = (int)t0;
99     t2 = (int)(100 * (t0 - (double)t1));
100     fprintf(outf, " (%d", t1);
101     html_putch(outf, od->decpt);
102     fprintf(outf, "%02d %s).\n", t2, lngstr[days_]);
103   }
104 }
105 
106 /* Finishing the top of the page */
html_closehead(FILE * outf,Outchoices * od)107 void html_closehead(FILE *outf, Outchoices *od) {
108   if (od->gotos == FEW)
109     html_gotos(outf, od, -1);
110 
111   html_hrule(outf, od);
112 }
113 
114 /* Starting the bottom of the page */
html_pagebotstart(FILE * outf,Outchoices * od)115 void html_pagebotstart(FILE *outf, Outchoices *od) {
116 }
117 
118 /* The credit line at the bottom of the page */
html_credit(FILE * outf,Outchoices * od)119 void html_credit(FILE *outf, Outchoices *od) {
120   fprintf(outf, "<i>%s <a HREF=\"%s\">analog %s</a>.\n", od->lngstr[credit_],
121 	  ANALOGURL, VNUMBER);
122 }
123 
124 /* The program run time */
html_runtime(FILE * outf,Outchoices * od,long secs)125 void html_runtime(FILE *outf, Outchoices *od, long secs) {
126   char **lngstr = od->lngstr;
127 
128   fprintf(outf, "<br><b>%s:</b> ", lngstr[runtime_]);
129 
130   if (secs == 0)
131     fprintf(outf, "%s %s.\n", lngstr[lessone_], lngstr[second_]);
132   else if (secs < 60)
133     fprintf(outf, "%ld %s.\n", secs,
134 	    (secs == 1)?lngstr[second_]:lngstr[seconds_]);
135   else
136     fprintf(outf, "%ld %s, %ld %s.\n", secs / 60,
137 	    (secs < 120)?lngstr[minute_]:lngstr[minutes_], secs % 60,
138 	    (secs % 60 == 1)?lngstr[second_]:lngstr[seconds_]);
139 }
140 
141 /* The page footer, including the user's FOOTERFILE */
html_pagefoot(FILE * outf,Outchoices * od)142 void html_pagefoot(FILE *outf, Outchoices *od) {
143   fputs("</i>\n", outf);
144   if (od->gotos != FALSE)
145     html_gotos(outf, od, -1);
146 
147   if (!strcaseeq(od->footerfile, "none"))
148     html_includefile(outf, od, od->footerfile, 'f');
149 
150   fputs("<p><a href=\"http://validator.w3.org/\">\n", outf);
151   fputs("<img src=\"", outf);
152   htmlputs(outf, od, od->imagedir, IN_HREF);
153   fprintf(outf, "html2.%s\"\n", od->pngimages?"png":"gif");
154   /* Above: '.' not EXTSEP even on RISC OS */
155   fputs("alt=\"HTML 2.0 Conformant!\"></a>\n", outf);
156 }
157 
158 /* Footer material for this output style */
html_stylefoot(FILE * outf,Outchoices * od)159 void html_stylefoot(FILE *outf, Outchoices *od) {
160   fputs("</body>\n</html>\n", outf);
161 }
162 
163 /* Report title */
html_reporttitle(FILE * outf,Outchoices * od,choice rep)164 void html_reporttitle(FILE *outf, Outchoices *od, choice rep) {
165   extern char *anchorname[];
166   extern unsigned int *rep2lng;
167 
168   fprintf(outf, "<h2><a NAME=\"%s\">%s</a></h2>\n", anchorname[rep],
169 	  od->lngstr[rep2lng[rep]]);
170   if (od->gotos == TRUE)
171     html_gotos(outf, od, rep);
172 }
173 
174 /* Report footer */
html_reportfooter(FILE * outf,Outchoices * od,choice rep)175 void html_reportfooter(FILE *outf, Outchoices *od, choice rep) {
176 }
177 
178 /* Report description */
html_reportdesc(FILE * outf,Outchoices * od,choice rep)179 void html_reportdesc(FILE *outf, Outchoices *od, choice rep) {
180   fprintf(outf, "<p><em>%s</em>\n", od->descstr[rep]);
181 }
182 
183 /* The time period spanned by the report */
html_reportspan(FILE * outf,Outchoices * od,choice rep,timecode_t maxd,timecode_t mind)184 void html_reportspan(FILE *outf, Outchoices *od, choice rep, timecode_t maxd,
185 		     timecode_t mind) {
186   /* NB Can't combine next two lines because timesprintf uses static buffer. */
187   fprintf(outf, "<p><em>%s %s ", od->lngstr[repspan_],
188 	  timesprintf(od, od->lngstr[datefmt2_], mind, UNSET));
189   fprintf(outf, "%s %s.</em>\n", od->lngstr[to_],
190 	  timesprintf(od, od->lngstr[datefmt2_], maxd, UNSET));
191 }
192 
193 /* General Summary header */
html_gensumhead(FILE * outf,Outchoices * od)194 void html_gensumhead(FILE *outf, Outchoices *od) {
195 }
196 
197 /* General Summary footer */
html_gensumfoot(FILE * outf,Outchoices * od)198 void html_gensumfoot(FILE *outf, Outchoices *od) {
199 }
200 
201 /* Single General Summary line, long data */
html_gensumline(FILE * outf,Outchoices * od,int namecode,unsigned long x,unsigned long x7,logical isaverage)202 void html_gensumline(FILE *outf, Outchoices *od, int namecode,
203 		     unsigned long x, unsigned long x7, logical isaverage) {
204 
205   /* If this is the first Gen Sum line, and there is no seven-day data, then
206      start a new paragraph. Otherwise the new paragraph has already been
207      started in html_lastseven(). */
208   if (namecode == succreqs_ && x7 == (unsigned long)UNSET)
209     fprintf(outf, "<p><b>%s%s</b> ", od->lngstr[namecode], od->lngstr[colon_]);
210   else
211     fprintf(outf, "<br><b>%s%s</b> ",  od->lngstr[namecode],
212 	    od->lngstr[colon_]);
213 
214   f3printf(outf, od, (double)x, 0, od->sepchar);
215 
216   if (x7 != (unsigned long)UNSET) {
217     fputs(" (", outf);
218     f3printf(outf, od, (double)x7, 0, od->sepchar);
219     putc(')', outf);
220   }
221   putc('\n', outf);
222 }
223 
224 /* Single General Summary line, bytes data */
html_gensumlineb(FILE * outf,Outchoices * od,int namecode,double x,double x7,logical isaverage)225 void html_gensumlineb(FILE *outf, Outchoices *od, int namecode, double x,
226 		      double x7, logical isaverage) {
227   char **lngstr = od->lngstr;
228 
229   unsigned int bm;
230   char *c;
231 
232   fprintf(outf, "<br><b>%s%s</b> ",  lngstr[namecode], lngstr[colon_]);
233 
234   bm = (od->rawbytes)?0:findbmult(x, od->bytesdp);
235   printbytes(outf, od, x, bm, 0, od->sepchar);
236 
237   if (bm > 0) {
238     c = strchr(lngstr[xbytes_], '?');
239     *c = '\0';
240     fprintf(outf, " %s%s%s", lngstr[xbytes_], lngstr[byteprefix_ + bm], c + 1);
241     *c = '?';
242   }
243   else
244     fprintf(outf, " %s", lngstr[bytes_]);
245 
246   if (x7 != UNSET) {
247     fputs(" (", outf);
248     bm = (od->rawbytes)?0:findbmult(x7, od->bytesdp);
249     printbytes(outf, od, x7, bm, 0, od->sepchar);
250     if (bm > 0) {
251       c = strchr(lngstr[xbytes_], '?');
252       *c = '\0';
253       fprintf(outf, " %s%s%s)", lngstr[xbytes_], lngstr[byteprefix_ + bm],
254 	      c + 1);
255       *c = '?';
256     }
257     else
258       fprintf(outf, " %s)", lngstr[bytes_]);
259   }
260 
261   putc('\n', outf);
262 }
263 
264 /* "Last seven" explanation line */
html_lastseven(FILE * outf,Outchoices * od,timecode_t last7to)265 void html_lastseven(FILE *outf, Outchoices *od, timecode_t last7to) {
266   fputs("<p>", outf);
267   fprintf(outf, "(%s %s %s).\n", od->lngstr[brackets_],
268 	  od->lngstr[sevendaysto_],
269 	  timesprintf(od, od->lngstr[datefmt1_], last7to, UNSET));
270 }
271 
272 /* Start of a <pre> section */
html_prestart(FILE * outf,Outchoices * od)273 void html_prestart(FILE *outf, Outchoices *od) {
274   fputs("<pre><tt>", outf);
275 }
276 
277 /* End of a <pre> section */
html_preend(FILE * outf,Outchoices * od)278 void html_preend(FILE *outf, Outchoices *od) {
279   fputs("</tt></pre>\n", outf);
280 }
281 
282 /* A horizontal rule */
html_hrule(FILE * outf,Outchoices * od)283 void html_hrule(FILE *outf, Outchoices *od) {
284   fputs("<hr>\n", outf);
285 }
286 
287 /* An en dash */
html_endash(void)288 char *html_endash(void) {
289   return "-";
290   /* Should be &ndash; but not all browsers implement &ndash;, and when
291      they do it's usually just a regular dash. */
292 }
293 
294 /* putc with special characters escaped */
html_putch(FILE * outf,char c)295 void html_putch(FILE *outf, char c) {
296   if (c == '<')
297     fputs("&lt;", outf);
298   else if (c == '>')
299     fputs("&gt;", outf);
300   else if (c == '&')
301     fputs("&amp;", outf);
302   else if (c == '"')
303     fputs("&quot;", outf);
304   else
305     putc(c, outf);
306 }
307 
308 /* strlen for HTML strings. Assume string contains no &'s except as markup. */
309 /* NB This may not work well for multibyte charsets, but it's hard to know
310    whether an & is markup or just a byte of a multibyte character. Special
311    cases for particular charsets are given below and selected in init.c. */
312 /* NOTE html_strlengthgth_utf8 and _jis in outhtml only. Other outputters just
313    have _strlengthgth */
html_strlength(const char * s)314 size_t html_strlength(const char *s) {
315   const char *t;
316   logical f;
317   size_t i;
318 
319   for (t = s, f = TRUE, i = 0; *t != '\0'; t++) {
320     if (*t == '&')
321       f = FALSE;
322     else if (*t == ';')
323       f = TRUE;
324     if (f)
325       i++;
326   }
327   return(f?i:strlen(s));
328   /* If !f, something went wrong (eg multibyte). Maybe the & wasn't markup. */
329 }
330 
html_strlength_utf8(const char * s)331 size_t html_strlength_utf8(const char *s) {
332   /* This only knows about jp chars in the range 1110xxxx 10xxxxxx 10xxxxxx.
333      Other languages using UTF-8 would need new code. */
334   const unsigned char *t;
335   size_t i;
336 
337   for (i = 0, t = (const unsigned char *)s; *t != '\0'; t++) {
338     if ((*t & 0xf0) == 0xe0 && (*(t + 1) & 0xc0) == 0x80 &&
339 	(*(t + 2) & 0xc0) == 0x80) {
340       t += 2;  /* plus 1 in loop */
341       i += 2;
342       /* three-character jp sequence = one jp char = length 2 wrt ASCII */
343     }
344     else
345       i++;
346   }
347   return(i);
348 }
349 
html_strlength_jis(const char * s)350 size_t html_strlength_jis(const char *s) {
351   size_t i;
352 
353   for (i = 0; *s != '\0'; s++) {
354     if (*s == 0x1B && (*(s + 1) == '$' || *(s + 1) == '(') && *(s + 2) == 'B')
355       s += 2; /* plus 1 in loop */   /* ignore ESC $ B and ESC ( B switches */
356     else
357       i++;
358   }
359   return(i);  /* returns length in bytes, because one jp char = two bytes and
360 		 width of one jp char = width of two ASCII chars */
361 }
362 
363 /* Allow month in dates? DO NOT enable for human-readable text because of
364    i18n problems. */
html_allowmonth(void)365 logical html_allowmonth(void) {
366   return FALSE;
367 }
368 
369 /* Calculate column widths */
html_calcwidths(Outchoices * od,choice rep,unsigned int width[],unsigned int * bmult,unsigned int * bmult7,double * unit,unsigned long maxr,unsigned long maxr7,unsigned long maxp,unsigned long maxp7,double maxb,double maxb7,unsigned long howmany)370 void html_calcwidths(Outchoices *od, choice rep, unsigned int width[],
371 		     unsigned int *bmult, unsigned int *bmult7, double *unit,
372 		     unsigned long maxr, unsigned long maxr7,
373 		     unsigned long maxp, unsigned long maxp7, double maxb,
374 		     double maxb7, unsigned long howmany) {
375   calcwidths(od, rep, width, bmult, bmult7, unit, maxr, maxr7, maxp, maxp7,
376 	     maxb, maxb7, howmany);
377 }
378 
379 /* "Each unit represents" line */
html_declareunit(FILE * outf,Outchoices * od,char graphby,double unit,unsigned int bmult)380 void html_declareunit(FILE *outf, Outchoices *od, char graphby, double unit,
381 		      unsigned int bmult) {
382   char **lngstr = od->lngstr;
383 
384   char *s;
385 
386   fputs("<p>\n", outf);
387   fprintf(outf, "%s (", lngstr[eachunit_]);
388   if (ISLOWER(graphby))
389     fprintf(outf, "<tt>%c</tt>", od->markchar);
390   else {
391     fprintf(outf, "<img src=\"");
392     htmlputs(outf, od, od->imagedir, IN_HREF);
393     fprintf(outf, "bar%c1.%s\" alt=\"%c\">", od->barstyle,
394 	    od->pngimages?"png":"gif", od->markchar);
395     /* Above: '.' not EXTSEP even on RISC OS */
396   }
397   fprintf(outf, ") %s ", lngstr[represents_]);
398 
399   if (graphby == 'R' || graphby == 'r') {
400     f3printf(outf, od, unit, 0, od->sepchar);
401     if (unit == 1.)
402       fprintf(outf, " %s.", lngstr[request_]);
403     else
404       fprintf(outf, " %s %s.", lngstr[requests_], lngstr[partof_]);
405   }
406   else if (graphby == 'P' || graphby == 'p') {
407     f3printf(outf, od, unit, 0, od->sepchar);
408     if (unit == 1.)
409       fprintf(outf, " %s.", lngstr[pagereq_]);
410     else
411       fprintf(outf, " %s %s.", lngstr[pagereqs_], lngstr[partof_]);
412   }
413   else {
414     if (bmult > 0) {
415       html_printdouble(outf, od, unit);
416       s = strchr(lngstr[xbytes_], '?');  /* checked in initialisation */
417       *s = '\0';
418       fprintf(outf, " %s%s%s %s.", lngstr[xbytes_],
419 	      lngstr[byteprefix_ + bmult], s + 1, lngstr[partof_]);
420       *s = '?';
421     }
422     else {
423       f3printf(outf, od, unit, 0, od->sepchar);
424       fprintf(outf, " %s %s.", lngstr[bytes_], lngstr[partof_]);
425     }
426   }
427   putc('\n', outf);
428 }
429 
430 /* Start of column header line */
html_colheadstart(FILE * outf,Outchoices * od,choice rep)431 void html_colheadstart(FILE *outf, Outchoices *od, choice rep) {
432 }
433 
434 /* Column header line: individual column */
html_colheadcol(FILE * outf,Outchoices * od,choice rep,choice col,unsigned int width,char * colname,logical unterminated)435 void html_colheadcol(FILE *outf, Outchoices *od, choice rep, choice col,
436 		     unsigned int width, char *colname, logical unterminated) {
437   if (unterminated)
438     fputs(colname, outf);
439   else
440     fprintf(outf, "%*s: ",
441 	    width + strlen(colname) - od->outputter->strlength(colname),
442 	    colname);
443 }
444 
445 /* End of column header line */
html_colheadend(FILE * outf,Outchoices * od,choice rep)446 void html_colheadend(FILE *outf, Outchoices *od, choice rep) {
447   putc('\n', outf);
448 }
449 
450 /* Start of column header underlining line */
html_colheadustart(FILE * outf,Outchoices * od,choice rep)451 void html_colheadustart(FILE *outf, Outchoices *od, choice rep) {
452 }
453 
454 /* Underlining of one column header. */
455 /* If column is terminated, set width and leave name blank; and conversely. */
html_colheadunderline(FILE * outf,Outchoices * od,choice rep,choice col,unsigned int width,char * name)456 void html_colheadunderline(FILE *outf, Outchoices *od, choice rep, choice col,
457 			   unsigned int width, char *name) {
458   if (width > 0) {
459     matchlengthn(outf, od, (size_t)width, '-');
460     fputs(": ", outf);
461   }
462   else
463     matchlength(outf, od, name, '-');
464 }
465 
466 /* End of column header underlining line */
html_colheaduend(FILE * outf,Outchoices * od,choice rep)467 void html_colheaduend(FILE *outf, Outchoices *od, choice rep) {
468   putc('\n', outf);
469 }
470 
471 /* Start of a table row */
html_rowstart(FILE * outf,Outchoices * od,choice rep,choice * cols,int level,char * name,char * datefmt,char * timefmt)472 void html_rowstart(FILE *outf, Outchoices *od, choice rep, choice *cols,
473 		   int level, char *name, char *datefmt, char *timefmt) {
474 }
475 
476 /* Print level in hierarchy represented by this row */
html_levelcell(FILE * outf,Outchoices * od,choice rep,int level)477 void html_levelcell(FILE *outf, Outchoices *od, choice rep, int level) {
478 }
479 
480 /* Name column */
html_namecell(FILE * outf,Outchoices * od,choice rep,char * name,choice source,unsigned int width,logical name1st,logical isfirst,logical rightalign,Alias * aliashead,Include * linkhead,logical ispage,unsigned int spaces,char * baseurl)481 void html_namecell(FILE *outf, Outchoices *od, choice rep, char *name,
482 		   choice source, unsigned int width, logical name1st,
483 		   logical isfirst, logical rightalign, Alias *aliashead,
484 		   Include *linkhead, logical ispage, unsigned int spaces,
485 		   char *baseurl) {
486   extern char *workspace;
487 
488   choice savemultibyte;
489   logical linked;
490   int i;
491 
492   if (name1st != isfirst)
493     return;
494 
495   if (isfirst)
496     matchlengthn(outf, od, width - od->outputter->strlength(name), ' ');
497   savemultibyte = od->multibyte;
498   if (rep == REP_SIZE || rep == REP_PROCTIME)
499     /* Kludge: for these two reports, we know the texts are things like
500        "< 1" and we want to convert > and < */
501     od->multibyte = FALSE;
502 
503   strcpy(workspace, name);
504   do_aliasx(workspace, aliashead);
505 
506   if (!isfirst) {
507     if (rightalign)
508       i = (int)width - (int)od->outputter->strlength(workspace) - (int)spaces;
509     else
510       i = (int)spaces;
511     matchlengthn(outf, od, i, ' ');
512   }
513 
514   linked = (linkhead != NULL && included(name, ispage, linkhead));
515   if (linked) {
516     /* We link to the unaliased name, because the OUTPUTALIAS is usually in
517        the nature of an annotation. */
518     fputs("<a href=\"", outf);
519     if (baseurl != NULL)
520       htmlputs(outf, od, baseurl, IN_HREF);
521     html_escfprintf(outf, name);
522     fputs("\">", outf);
523   }
524 
525   htmlputs(outf, od, workspace, source);
526 
527   if (linked)
528     fputs("</a>", outf);
529 
530   if (isfirst)
531     fputs(": ", outf);
532 
533   od->multibyte = savemultibyte;  /* restore multibyte */
534 }
535 
536 /* Single cell, unsigned long argument */
html_ulcell(FILE * outf,Outchoices * od,choice rep,choice col,unsigned long x,unsigned int width)537 void html_ulcell(FILE *outf, Outchoices *od, choice rep, choice col,
538 		 unsigned long x, unsigned int width) {
539   f3printf(outf, od, (double)x, width, od->repsepchar);
540   fputs(": ", outf);
541 }
542 
543 /* Single cell, TRUSTED string argument */
html_strcell(FILE * outf,Outchoices * od,choice rep,choice col,char * s,unsigned int width)544 void html_strcell(FILE *outf, Outchoices *od, choice rep, choice col,
545 		  char *s, unsigned int width) {
546   matchlengthn(outf, od, width - od->outputter->strlength(s), ' ');
547   htmlputs(outf, od, s, TRUSTED);
548   fputs(": ", outf);
549 }
550 
551 /* Single cell, listing bytes */
html_bytescell(FILE * outf,Outchoices * od,choice rep,choice col,double b,double bmult,unsigned int width)552 void html_bytescell(FILE *outf, Outchoices *od, choice rep, choice col,
553 		    double b, double bmult, unsigned int width) {
554   printbytes(outf, od, b, bmult, width, od->repsepchar);
555   fputs(": ", outf);
556 }
557 
558 /* Single cell, listing percentage */
html_pccell(FILE * outf,Outchoices * od,choice rep,choice col,double n,double tot,unsigned int width)559 void html_pccell(FILE *outf, Outchoices *od, choice rep, choice col, double n,
560 		 double tot, unsigned int width) {
561   double pc;
562   unsigned int pc1, pc2;
563 
564   matchlengthn(outf, od, width - 6, ' ');
565   if (tot == 0)
566     pc = 0.0;
567   else
568     pc = n * 10000.0 / tot;
569   if (pc >= 9999.5)
570     fputs("  100%", outf);
571   else if (pc < 0.5)
572     fputs("      ", outf);
573   else {
574     pc1 = ((int)(pc + 0.5)) / 100;
575     pc2 = ((int)(pc + 0.5)) % 100;
576     fprintf(outf, "%2d", pc1);
577     html_putch(outf, od->decpt);
578     fprintf(outf, "%02d%%", pc2);
579   }
580   fputs(": ", outf);
581 }
582 
583 /* Single cell, index */
html_indexcell(FILE * outf,Outchoices * od,choice rep,choice col,long index,unsigned int width)584 void html_indexcell(FILE *outf, Outchoices *od, choice rep, choice col,
585 		    long index, unsigned int width) {
586   /* If index is 0 (i.e. sub-item), just print spaces */
587   if (index <= 0)
588     matchlengthn(outf, od, width, ' ');
589   else
590     f3printf(outf, od, (double)index, width, od->repsepchar);
591   fputs(": ", outf);
592 }
593 
594 /* End of a table row */
html_rowend(FILE * outf,Outchoices * od,choice rep)595 void html_rowend(FILE *outf, Outchoices *od, choice rep) {
596   putc('\n', outf);
597 }
598 
599 /* Blank line in time reports */
html_blankline(FILE * outf,Outchoices * od,choice * cols)600 void html_blankline(FILE *outf, Outchoices *od, choice *cols) {
601   putc('\n', outf);
602 }
603 
604 /* Barchart in time reports */
html_barchart(FILE * outf,Outchoices * od,int y,char graphby)605 void html_barchart(FILE *outf, Outchoices *od, int y, char graphby) {
606   int i, j;
607   logical first = TRUE;
608 
609   if (ISLOWER(graphby)) {
610     for (i = 0; i < y; i++)
611       html_putch(outf, od->markchar);
612     return;
613   }
614 
615   for (j = 32; j >= 1; j /= 2) {
616     while (y >= j) {
617       fputs("<img src=\"", outf);
618       htmlputs(outf, od, od->imagedir, IN_HREF);
619       fprintf(outf, "bar%c%d.%s\" alt=\"", od->barstyle, j,
620 	      od->pngimages?"png":"gif"); /* '.' not EXTSEP even on RISC OS */
621       if (first) {
622 	for (i = 0; i < y; i++)
623 	  html_putch(outf, od->markchar);
624 	first = FALSE;
625       }
626       fputs("\">", outf);
627       y -= j;
628     }
629   }
630 }
631 
632 /* "Busiest time period" line */
html_busyprintf(FILE * outf,Outchoices * od,choice rep,char * datefmt,unsigned long reqs,unsigned long pages,double bys,datecode_t date,unsigned int hr,unsigned int min,datecode_t newdate,unsigned int newhr,unsigned int newmin,char graphby)633 void html_busyprintf(FILE *outf, Outchoices *od, choice rep, char *datefmt,
634 		     unsigned long reqs, unsigned long pages, double bys,
635 		     datecode_t date, unsigned int hr, unsigned int min,
636 		     datecode_t newdate, unsigned int newhr,
637 		     unsigned int newmin, char graphby) {
638   extern unsigned int *rep2busystr;
639 
640   char **lngstr = od->lngstr;
641   char sepchar = od->sepchar;
642 
643   unsigned int bmult;
644   char *s;
645 
646   fprintf(outf, "%s %s (", lngstr[rep2busystr[rep]],
647 	  datesprintf(od, datefmt, date, hr, min, newdate, newhr, newmin,
648 		      TRUE, UNSET));
649   if (TOLOWER(graphby) == 'r') {
650     f3printf(outf, od, (double)reqs, 0, sepchar);
651     fprintf(outf, " %s).\n", (reqs == 1)?lngstr[request_]:lngstr[requests_]);
652   }
653   else if (TOLOWER(graphby) == 'p') {
654     f3printf(outf, od, (double)pages, 0, sepchar);
655     fprintf(outf, " %s).\n",
656 	    (pages == 1)?lngstr[pagereq_]:lngstr[pagereqs_]);
657   }
658   else /* TOLOWER(graphby) == 'b' */ {
659     if (od->rawbytes)
660       bmult = 0;
661     else
662       bmult = findbmult(bys, od->bytesdp);
663     printbytes(outf, od, bys, bmult, 0, sepchar);
664     putc(' ', outf);
665     if (bmult >= 1) {
666       s = strchr(lngstr[xbytes_], '?');  /* checked in initialisation */
667       *s = '\0';
668       fprintf(outf, "%s%s%s).\n", lngstr[xbytes_],
669 	      lngstr[byteprefix_ + bmult], s + 1);
670       *s = '?';
671     }
672     else
673       fprintf(outf, "%s).\n", lngstr[bytes_]);
674   }
675 }
676 
677 /* End of "Not listed" line. */
html_notlistedstr(FILE * outf,Outchoices * od,choice rep,unsigned long badn)678 void html_notlistedstr(FILE *outf, Outchoices *od, choice rep,
679 		       unsigned long badn) {
680   extern unsigned int *rep2lng, *rep2colhead;
681 
682   char **lngstr = od->lngstr;
683   char *colhead = lngstr[rep2colhead[rep]];
684   char *colheadp = lngstr[rep2colhead[rep] + 1];
685   char gender = lngstr[rep2lng[rep] + 3][0];
686 
687   char *notlistedstr;
688 
689   if (gender == 'm')
690     notlistedstr = lngstr[notlistedm_];
691   else if (gender == 'f')
692     notlistedstr = lngstr[notlistedf_];
693   else
694     notlistedstr = lngstr[notlistedn_];
695 
696   fprintf(outf, "[%s: ", notlistedstr);
697   f3printf(outf, od, (double)badn, 0, od->sepchar);
698   fprintf(outf, " %s]", (badn == 1)?colhead:colheadp);
699 }
700 
701 /* The line declaring the floor and sort for a report */
html_whatincluded(FILE * outf,Outchoices * od,choice rep,unsigned long n,Dateman * dman)702 void html_whatincluded(FILE *outf, Outchoices *od, choice rep,
703 		       unsigned long n, Dateman *dman) {
704   whatincluded(outf, od, rep, n, dman);
705 }
706 
707 /* Spacing at the start of the whatincluded line */
html_whatinchead(FILE * outf,Outchoices * od)708 void html_whatinchead(FILE *outf, Outchoices *od) {
709   fputs("<p>\n", outf);
710 }
711 
712 /* Finishing the whatincluded line */
html_whatincfoot(FILE * outf,Outchoices * od)713 void html_whatincfoot(FILE *outf, Outchoices *od) {
714   putc('\n', outf);
715 }
716 
717 /* Printing part of the whatincluded line */
html_whatincprintstr(FILE * outf,Outchoices * od,char * s)718 void html_whatincprintstr(FILE *outf, Outchoices *od, char *s) {
719   htmlputs(outf, od, s, TRUSTED);
720 }
721 
722 /* Print a double to a nice number of decimal places */
html_printdouble(FILE * outf,Outchoices * od,double x)723 void html_printdouble(FILE *outf, Outchoices *od, double x) {
724   unsigned int prec;
725   double d;
726 
727   /* first calculate how many decimal places we need */
728 
729   for (prec = 0, d = x - (double)((int)(x));
730        d - (double)((int)(d + 0.000005)) > 0.00001; d *= 10)
731     prec++;
732 
733   /* now print it */
734 
735   if (prec > 0) {
736     fprintf(outf, "%d", (int)x);
737     html_putch(outf, od->decpt);
738     fprintf(outf, "%0*d", prec, (int)(d + EPSILON));
739   }
740   else
741     fprintf(outf, "%d", (int)(x + EPSILON));
742 }
743 
744 /* Include a header file or footer file */
html_includefile(FILE * outf,Outchoices * od,char * name,char type)745 void html_includefile(FILE *outf, Outchoices *od, char *name, char type) {
746   FILE *inf;
747   char buffer[BLOCKSIZE];
748   size_t n;
749 
750   if ((inf = my_fopen(name, (type == 'h')?"header file":"footer file")) !=
751       NULL) {
752     od->html = FALSE;
753     html_hrule(outf, od);
754     while ((n = fread(buffer, 1, BLOCKSIZE, inf)))  /* single equals */
755       fwrite((void *)buffer, 1, n, outf);
756     if (type == 'h')
757       html_hrule(outf, od);
758     (void)my_fclose(inf, name, (type == 'h')?"header file":"footer file");
759   }
760 }
761 
762 /* Filetype for RISC OS */
html_riscosfiletype(void)763 unsigned int html_riscosfiletype(void) {
764   return 0xfaf;
765 }
766 
767 /* ======================================================================= */
768 /* Supporting functions for HTML */
769 
770 /* Print "goto"s. Assume we've checked that we want gotos here. */
html_gotos(FILE * outf,Outchoices * od,choice rep)771 void html_gotos(FILE *outf, Outchoices *od, choice rep) {
772   extern unsigned int *rep2lng;
773   extern char *anchorname[];
774 
775   choice *reporder = od->reporder;
776   char **lngstr = od->lngstr;
777   int i;
778 
779   fprintf(outf, "<p>(<b>%s</b>", lngstr[goto_]);
780   fprintf(outf, "%s <a HREF=\"#Top\">%s</a>", lngstr[colon_], lngstr[top_]);
781   for (i = 0; reporder[i] != -1; i++) {
782     if (reporder[i] == rep)
783       fprintf(outf, "%s %s", lngstr[colon_], lngstr[rep2lng[reporder[i]]]);
784     else if (od->repq[reporder[i]])
785       fprintf(outf, "%s <a HREF=\"#%s\">%s</a>", lngstr[colon_],
786 	      anchorname[reporder[i]], lngstr[rep2lng[reporder[i]]]);
787   }
788   fputs(")\n", outf);
789 }
790 
791 /* Escape names for use in hyperlinks. As with htmlputs(), don't try and
792    print character by character. */
html_escfprintf(FILE * outf,char * name)793 void html_escfprintf(FILE *outf, char *name) {
794 #ifdef EBCDIC
795   extern unsigned char os_toascii[];
796 #endif
797   char w1[64];
798   char *w = w1;
799   int len = 0;
800 
801   for ( ; *name != '\0'; name++) {
802     if (ISALNUM(*name) || *name == '/' || *name == '.' || *name == ':' ||
803 	*name == '-' || *name == '~' || *name == '_' || *name == '?' ||
804 	*name == '%' || *name == '=' || *name == '+' ||
805 	*name == ';' ||	*name == '@' || *name == '$' || *name == ',') {
806       /* All reserved and some unreserved chars from RFC 2396 Sec 2. */
807       /* Reserved chars are not escaped because if they are in the logfile they
808 	 must have their special meanings (path delimiters etc.), and escaping
809 	 them would change the semantics of the URL. */
810       PUTc(w, *name);
811       len += 1;
812     }
813     else if (*name == '&') {
814       PUTs(w, "&amp;", 0);
815       len += 5;
816     }
817     else {
818 #ifdef EBCDIC
819       sprintf(w, "%%%.2X", os_toascii[*name]);
820 #else
821       sprintf(w, "%%%.2X", (unsigned char)(*name));
822 #endif
823       w += 3;
824       len += 3;
825     }
826     if (len > 58) {
827       *w = '\0';
828       fputs(w1, outf);
829       w = w1;
830       len = 0;
831     }
832   }
833   *w = '\0';
834   fputs(w1, outf);
835 }
836 
837 /* htmlputs(): print a string with an appropriate amount of HTML encoding.
838    Much quicker than using html_putch(). */
839 
840 /** What to convert has SECURITY IMPLICATIONS. An attacker must not be allowed
841  ** to insert abitrary data in the output.
842  **
843  ** So data is categorised according to its source, via an enum in anlghea3.h.
844  ** In the following descriptions of the security levels, "convert" means
845  ** converting e.g. < to &lt; and "escape" means using \< to prevent this
846  ** happening. "unprintable" means characters set as unprintable in init.c:
847  ** note that this is only known unprintable characters 0x00 - 0x1F, 0x7F,
848  ** and in ISO-8859-* also 0x80-0x9F.
849  **
850  ** 1) AS_IS: Completely trusted string. Output as-is.
851  ** 2) TRUSTED: E.g. a string from a language file. Also completely trusted. In
852  **    single-byte mode, convert characters (for convenience not security), but
853  **    allow any of the special characters to be escaped, even \< .
854  **    In multibyte mode, output the string as-is.
855  ** 3) FROM_CFG: An item read in from configuration. Unless we're in CGI mode,
856  **    treat as case 2. In CGI mode, the input could have come from the form,
857  **    so be more cautious to avoid cross-site scripting attacks. Specifically,
858  **    convert all characters, allow only \& and \\ escapes, and use '?' in
859  **    place of unprintable characters.
860  ** 4) UNTRUSTED: E.g. data from the logfile. Do all conversions, don't allow
861  **    any escapes, and use '?' in place of all unprintable characters.
862  ** 5) IN_HREF: Special case for data from the config file which is being put
863  **    inside an <a href=""> or <img src="">. As 3), but use %nm in place of
864  **    unprintable characters. (NB data from the logfile which is put inside
865  **    an href uses escfprintf() instead of this function.)
866  **/
htmlputs(FILE * outf,Outchoices * od,char * s,choice source)867 void htmlputs(FILE *outf, Outchoices *od, char *s, choice source) {
868 #ifdef EBCDIC
869   extern unsigned char os_toascii[];
870 #endif
871   extern logical cgi;
872   extern logical unprintable[256];
873 
874   char w1[64];
875   char *c;
876   char *w = w1;
877   int len = 0;
878 
879   if (source == FROM_CFG && !cgi)
880     source = TRUSTED;
881 
882   if (source == TRUSTED && od->multibyte)
883     source = AS_IS;
884 
885   if (source == AS_IS) {
886     fputs(s, outf);
887     return;
888   }
889 
890   for (c = s; *c != '\0'; c++) {
891     if (*c == '<') {
892       PUTs(w, "&lt;", 0);
893       len += 4;
894     }
895     else if (*c == '>') {
896       PUTs(w, "&gt;", 0);
897       len += 4;
898     }
899     else if (*c == '&') {
900       PUTs(w, "&amp;", 0);
901       len += 5;
902     }
903     else if (*c == '"') {
904       PUTs(w, "&quot;", 0);
905       len += 6;
906     }
907     else if (*c == '\\' &&  /* escape these chars in these circumstances: */
908 	     ((source == TRUSTED && (*(c + 1) == '<' || *(c + 1) == '>' ||
909 				     *(c + 1) == '&' || *(c + 1) == '"' ||
910 				     *(c + 1) == '\\'))
911 	      || (source == FROM_CFG && (*(c + 1) == '&' ||
912 					 *(c + 1) == '\\')))) {
913       od->html = FALSE;
914       PUTc(w, *(++c));
915       len += 1;
916     }
917     else if (unprintable[(unsigned char)(*c)] && source != TRUSTED) {
918       /* unprintable chars */
919       if (source == IN_HREF) {
920 #ifdef EBCDIC
921 	sprintf(w, "%%%.2X", os_toascii[*c]);
922 #else
923 	sprintf(w, "%%%.2X", (unsigned char)(*c));
924 #endif
925 	w += 3;
926 	len += 3;
927       }
928       else {  /* source == FROM_CFG or UNTRUSTED */
929 	PUTc(w, '?');
930 	len += 1;
931       }
932     }
933     else {  /* output non-special characters as-is */
934       PUTc(w, *c);
935       len += 1;
936     }
937     if (len > 56) {
938       *w = '\0';
939       fputs(w1, outf);
940       w = w1;
941       len = 0;
942     }
943   }
944   *w = '\0';
945   fputs(w1, outf);
946 }
947 
948 /* ======================================================================= */
949 /* Pie charts */
950 /* Shared by HTML and XHTML */
951 
952 #ifndef NOGRAPHICS
953 /* First some #defines and globals */
954 #define XSIZE (600)                 /* Size of whole graphic */
955 #define YSIZE (270)
956 #define SHORTXSIZE (2 * (XCENTRE))  /* Size if no text on picture */
957 #define SHORTYSIZE (2 * (YCENTRE))
958 #define XCENTRE (125)               /* Centre of pie */
959 #define YCENTRE (125)
960 #define DIAMETER (200)              /* Diameter of pie */
961 #define BORDER (4)                  /* Size of border */
962 #define BOXESLEFT (XCENTRE + DIAMETER / 2 + 25)    /* The coloured boxes */
963 #define BOXESTOP (YCENTRE - DIAMETER / 2 + 16)
964 #define BOXESSIZE (10)
965 #define TEXTLEFT ((BOXESLEFT) + 2 * (BOXESSIZE))   /* The labels */
966 #define TEXTOFFSET (-1)
967 #define TEXTGAP (16)     /* Vertical period between successive boxes/labels */
968 #define CAPTIONLEFT (XCENTRE - DIAMETER / 2)       /* Bottom caption */
969 #define CAPTIONTOP (YCENTRE + DIAMETER / 2 + 12)
970 #define NO_COLOURS (10)  /* Number of text strings, excluding "Other" */
971 #define MAXCHARS (54)    /* Max length of a label, INCLUDING \0. */
972 #define TWOPI (6.283185)
973 #define MINANGLE (0.01)  /* Min fraction of circle we are prepared to plot */
974 #define PIE_EPSILON (0.0001)
975 gdImagePtr im;
976 gdFontPtr font;
977 logical normalchart;
978 int white, black, grey, lightgrey, colours[NO_COLOURS], col, boxesy;
979 double totangle;
980 
piechart_init(char * filename)981 FILE *piechart_init(char *filename) {
982   FILE *pieoutf;
983   int xsize, ysize, b1, b2;
984 
985   if ((pieoutf = FOPENWB(filename)) == NULL) {
986     warn('F', TRUE, "Failed to open pie chart file %s for writing: "
987 	 "ignoring it", filename);
988     return(pieoutf);
989   }
990   debug('F', "Opening %s as pie chart file", filename);
991 #ifdef RISCOS
992   _swix(OS_File, _INR(0,2), 18, filename, 0xb60);  /* set PNG filetype */
993 #endif
994 
995   xsize = normalchart?XSIZE:SHORTXSIZE;
996   ysize = normalchart?YSIZE:SHORTYSIZE;
997   im = gdImageCreate(xsize, ysize);
998   /* The first colour allocated in a new image is the background colour. */
999   white = gdImageColorAllocate(im, 255, 255, 255);           /* white */
1000   black = gdImageColorAllocate(im, 0, 0, 0);                 /* black */
1001   grey = gdImageColorAllocate(im, 128, 128, 128);            /* grey */
1002   lightgrey = gdImageColorAllocate(im, 217, 217, 217);       /* light grey */
1003   col = 0;
1004   /* Wedge colours. If these change, so must images/sq*. */
1005   colours[col++] = gdImageColorAllocate(im, 255, 0, 0);      /* red */
1006   colours[col++] = gdImageColorAllocate(im, 0, 0, 255);      /* mid blue */
1007   colours[col++] = gdImageColorAllocate(im, 0, 128, 0);      /* green */
1008   colours[col++] = gdImageColorAllocate(im, 255, 128, 0);    /* orange */
1009   colours[col++] = gdImageColorAllocate(im, 0, 0, 128);      /* navy blue */
1010   colours[col++] = gdImageColorAllocate(im, 0, 255, 0);      /* pale green */
1011   colours[col++] = gdImageColorAllocate(im, 255, 128, 128);  /* pink */
1012   colours[col++] = gdImageColorAllocate(im, 0, 255, 255);    /* cyan */
1013   colours[col++] = gdImageColorAllocate(im, 128, 0, 128);    /* purple */
1014   colours[col++] = gdImageColorAllocate(im, 255, 255, 0);    /* yellow */
1015   col = 0;
1016   totangle = 0.75;  /* starting at the top */
1017   boxesy = BOXESTOP;
1018   b1 = xsize - 1 - BORDER;
1019   b2 = ysize - 1 - BORDER;
1020   /* Plot outline of pie, and border of image */
1021   gdImageArc(im, XCENTRE, YCENTRE, DIAMETER + 2, DIAMETER + 2, 0, 360, black);
1022   gdImageRectangle(im, BORDER, BORDER, b1, b2, black);
1023   gdImageLine(im, xsize - 1, 0, b1, BORDER, black);
1024   gdImageLine(im, 0, ysize - 1, BORDER, b2, black);
1025   gdImageFill(im, 0, 0, lightgrey);
1026   gdImageFill(im, xsize - 1, ysize - 1, grey);
1027   gdImageLine(im, 0, 0, BORDER, BORDER, black);
1028   gdImageLine(im, xsize - 1, ysize - 1, b1, b2, black);
1029   return(pieoutf);
1030 }
1031 
findwedges(Wedge wedge[NO_COLOURS],choice rep,Hashindex * items,choice chartby,Strlist * expandlist,unsigned int level,Strlist * partname,unsigned long tot,double totb,double totb7)1032 void findwedges(Wedge wedge[NO_COLOURS], choice rep, Hashindex *items,
1033 		choice chartby, Strlist *expandlist, unsigned int level,
1034 		Strlist *partname, unsigned long tot, double totb,
1035 		double totb7) {
1036   /* Calculate which wedges we actually want, i.e. the ten with the biggest
1037      angles. But we also preserve the sort order of the "items" list. (Be
1038      careful between > and >= so as to use that order for breaking ties).
1039      Construction of name same as in printtree(). */
1040   static double smallestangle;
1041   static int smallestwedge;
1042 
1043   char *name;
1044   double angle;
1045   Strlist *pn, s;
1046   size_t need = (size_t)level + 3;
1047   Hashindex *p;
1048   int i;
1049 
1050   if (level == 0) {  /* not recursing: initialise wedges to 0 */
1051     for (i = 0; i < NO_COLOURS; i++) {
1052       wedge[i].angle = 0.0;
1053       wedge[i].name = NULL;
1054     }
1055     smallestangle = 0.0;
1056     smallestwedge = NO_COLOURS - 1;
1057   }
1058 
1059   for (pn = partname; pn != NULL; TO_NEXT(pn))
1060     need += strlen(pn->name);
1061   for (p = items; p != NULL; TO_NEXT(p)) {
1062     name = maketreename(partname, p, &pn, &s, need, rep, TRUE);
1063     if (incstrlist(name, expandlist) && p->other != NULL &&
1064 	((Hashtable *)(p->other))->head[0] != NULL)
1065       /* then find wedges in lower level of tree instead. ->head[0] != NULL
1066 	 must come after p->other != NULL; o/wise it may not be a treerep */
1067       findwedges(wedge, rep, ((Hashtable *)(p->other))->head[0], chartby,
1068 		 expandlist, level + 1, pn, tot, totb, totb7);
1069     else {
1070       if (chartby == BYTES)
1071 	angle = p->own->bytes / totb;
1072       else if (chartby == BYTES7)
1073 	angle = p->own->bytes7 / totb7;
1074       else
1075 	angle = ((double)(p->own->data[chartby])) / ((double)tot);
1076       if (angle > smallestangle) {/* remove smallest, move along, put p last */
1077 	/* We probably don't do this very often so we don't bother with keeping
1078 	   hold of the memory and reusing it later. */
1079 	free(wedge[smallestwedge].name);
1080 	for (i = smallestwedge; i < NO_COLOURS - 1; i++) {
1081 	  wedge[i].name = wedge[i + 1].name;
1082 	  wedge[i].angle = wedge[i + 1].angle;
1083 	}
1084 	COPYSTR(wedge[NO_COLOURS - 1].name, name);
1085 	/* malloc's necessary space. Needed because next call to maketreename()
1086 	   will overwrite name. */
1087 	wedge[NO_COLOURS - 1].angle = angle;
1088 	smallestangle = wedge[0].angle;  /* Recalculate smallest */
1089 	smallestwedge = 0;
1090 	for (i = 1; i < NO_COLOURS; i++) {
1091 	  if (wedge[i].angle <= smallestangle) {
1092 	    smallestangle = wedge[i].angle;
1093 	    smallestwedge = i;
1094 	  }
1095 	}
1096       }
1097     }
1098   }
1099 }
1100 
piechart_caption(FILE * outf,choice rep,choice chartby,char ** lngstr)1101 void piechart_caption(FILE *outf, choice rep, choice chartby, char **lngstr) {
1102   extern choice *rep2reqs, *rep2reqs7;
1103   extern unsigned int *method2sort;
1104 
1105   static char *caption = NULL;
1106   static size_t len = 0;
1107 
1108   choice requests = rep2reqs[G(rep)];
1109   choice requests7 = rep2reqs7[G(rep)];
1110 
1111   ENSURE_LEN(caption, len, strlen(lngstr[chartby_]) +
1112 	     strlen(lngstr[method2sort[requests]]) +
1113 	     strlen(lngstr[method2sort[requests7]]) +
1114 	     strlen(lngstr[method2sort[chartby]]) + 3);
1115   /* More than we need, but that's OK. */
1116   strcpy(caption, lngstr[chartby_]);
1117   strcat(caption, " ");
1118   if (chartby == REQUESTS)
1119     strcat(caption, lngstr[method2sort[requests]]);
1120   else if (chartby == REQUESTS7)
1121     strcat(caption, lngstr[method2sort[requests7]]);
1122   else
1123     strcat(caption, lngstr[method2sort[chartby]]);
1124   strcat(caption, ".");
1125   if (normalchart) {
1126 #ifdef EBCDIC
1127     (void)strtoascii(caption);
1128 #endif
1129     gdImageString(im, font, CAPTIONLEFT, CAPTIONTOP, (unsigned char *)caption,
1130 		  black);
1131   }
1132   else
1133     fprintf(outf, "<p><em>%s</em></p>\n", caption);
1134 }
1135 
piechart_wedge(FILE * outf,Outchoices * od,double angle,char * s)1136 int piechart_wedge(FILE *outf, Outchoices *od, double angle, char *s) {
1137   /* The angle is expressed between 0 and 1. Returns col if wedge was big
1138      enough to be coloured in, NO_COLOURS for grey, else -1. */
1139   double x, y, newangle, medangle;
1140   int colour = black, rc = -1;
1141   char t[MAXCHARS];
1142   size_t len;
1143 
1144   if (angle < 0) {
1145     angle = 1.75 - totangle;  /* rest of the circle because started at 0.75 */
1146     colour = grey;
1147   }
1148   else if (col >= NO_COLOURS)
1149     angle = 0;  /* As a signal not to make a wedge. Can this happen? */
1150   else if (angle >= MINANGLE)
1151     colour = colours[col];
1152 
1153   if (angle >= MINANGLE || (colour == grey && angle > EPSILON)) {
1154     /* plot one edge of wedge */
1155     x = (double)XCENTRE + (double)DIAMETER * cos(totangle * TWOPI) / 2. +
1156       PIE_EPSILON;
1157     y = (double)YCENTRE + (double)DIAMETER * sin(totangle * TWOPI) / 2. +
1158       PIE_EPSILON;
1159     gdImageLine(im, XCENTRE, YCENTRE, (int)x, (int)y, black);
1160 
1161     /* plot other edge of wedge */
1162     newangle = totangle + angle;
1163     x = (double)XCENTRE + (double)DIAMETER * cos(newangle * TWOPI) / 2. +
1164       PIE_EPSILON;
1165     y = (double)YCENTRE + (double)DIAMETER * sin(newangle * TWOPI) / 2. +
1166       PIE_EPSILON;
1167     gdImageLine(im, XCENTRE, YCENTRE, (int)x, (int)y, black);
1168 
1169     /* Fill wedge */
1170     medangle = totangle + angle / 2.;
1171     x = (double)XCENTRE + (double)DIAMETER * cos(medangle * TWOPI) / 2.5;
1172     y = (double)YCENTRE + (double)DIAMETER * sin(medangle * TWOPI) / 2.5;
1173     if (gdImageGetPixel(im, (int)x, (int)y) == white) {  /* room to colour */
1174       gdImageFill(im, (int)x, (int)y, colour);
1175       /* Make label for wedge. If !normalchart, this is done in piechart_key()
1176 	 below instead. (See long comment near bottom of piechart().) */
1177       if (normalchart) {
1178 	gdImageFilledRectangle(im, BOXESLEFT, boxesy, BOXESLEFT + BOXESSIZE,
1179 			       boxesy + BOXESSIZE, colour);
1180 	if ((len = strlen(s)) <= MAXCHARS - 1)
1181 	  strcpy(t, s);
1182 	else {
1183 	  strncpy(t, s, (MAXCHARS - 3) / 2);
1184 	  strcpy(t + (MAXCHARS - 3) / 2, "...");
1185 	  strncpy(t + (MAXCHARS + 3) / 2, s + len - (MAXCHARS - 4) / 2,
1186 		  (MAXCHARS - 4) / 2);
1187 	  t[MAXCHARS - 1] = '\0';
1188 	}
1189 #ifdef EBCDIC
1190 	strtoascii(t);
1191 #endif
1192 	gdImageString(im, font, TEXTLEFT, boxesy + TEXTOFFSET,
1193 		      (unsigned char *)t, black);
1194 	boxesy += TEXTGAP;
1195       }
1196       rc = (colour == grey)?NO_COLOURS:col;
1197     }   /* end if (room to colour) */
1198     totangle = newangle;
1199     col++;
1200   }
1201   return(rc);
1202 }
1203 
piechart_key(FILE * outf,Outchoices * od,int col,char * name,char * extension,Alias * aliashead)1204 void piechart_key(FILE *outf, Outchoices *od, int col, char *name,
1205 		  char *extension, Alias *aliashead) {
1206   extern char *workspace;
1207 
1208   /* Only called if !normalchart and wedge was included on chart */
1209   if (od->outstyle == HTML)
1210     fputs("<br><img src=\"", outf);
1211   else /* od->outstyle == XHTML */
1212     fputs("<br /><img src=\"", outf);
1213   htmlputs(outf, od, od->imagedir, IN_HREF);
1214   if (col == NO_COLOURS)
1215     fprintf(outf, "sqg.%s", extension);
1216   else         /* Above and below: '.' not EXTSEP even on RISC OS */
1217     fprintf(outf, "sq%d.%s", col, extension);
1218   if (od->outstyle == HTML)
1219     fputs("\" alt=\"\"> ", outf);
1220   else /* od->outstyle == XHTML */
1221     fputs("\" alt=\"\" /> ", outf);
1222   strcpy(workspace, name);
1223   do_aliasx(workspace, aliashead);
1224   htmlputs(outf, od, workspace, UNTRUSTED);
1225   fputs("\n", outf);
1226 }
1227 
piechart_write(FILE * pieoutf,char * filename,logical jpegcharts)1228 void piechart_write(FILE *pieoutf, char *filename, logical jpegcharts) {
1229 #ifdef HAVE_GD
1230   if (jpegcharts)
1231     gdImageJpeg(im, pieoutf, 100);
1232   else
1233 #endif
1234   gdImagePng(im, pieoutf);
1235   debug('F', "Closing %s", filename);
1236   fclose(pieoutf);
1237   gdImageDestroy(im);
1238 }
1239 
piechart_cleanup(Wedge wedge[NO_COLOURS])1240 void piechart_cleanup(Wedge wedge[NO_COLOURS]) {
1241   int i;
1242 
1243   /* free the wedge names allocated in findwedges() */
1244   for (i = 0; i < NO_COLOURS; i++)
1245     free(wedge[i].name);
1246 }
1247 
piechart(FILE * outf,Outchoices * od,choice rep,Hashindex * items,choice requests,choice requests7,choice pages,choice pages7,unsigned long totr,unsigned long totr7,unsigned long totp,unsigned long totp7,double totb,double totb7)1248 void piechart(FILE *outf, Outchoices *od, choice rep, Hashindex *items,
1249 	      choice requests, choice requests7, choice pages, choice pages7,
1250 	      unsigned long totr, unsigned long totr7, unsigned long totp,
1251 	      unsigned long totp7, double totb, double totb7) {
1252   extern unsigned int *rep2lng;
1253   extern char *repname[];
1254   extern char *workspace;
1255   extern char *anchorname[];
1256   static char *filename = NULL;
1257 
1258   char **lngstr = od->lngstr;
1259   choice chartby = od->chartby[G(rep)];
1260   Strlist *expandlist = od->expandhead[G(rep)];
1261   char gender = lngstr[rep2lng[rep] + 3][0];
1262   char *extension = (od->jpegcharts)?"jpg":"png";
1263 
1264   Wedge wedge[NO_COLOURS];
1265   FILE *pieoutf;
1266   int key[NO_COLOURS], keyg;
1267   double largestangle;
1268   unsigned long tot = 1;
1269   char *otherstr;
1270   int i;
1271 
1272   /* Sort out what the chartby really means */
1273   if (chartby == CHART_NONE)
1274     return;  /* We didn't want a pie chart after all */
1275   if (chartby == REQUESTS) {
1276     chartby = requests;
1277     tot = totr;
1278   }
1279   else if (chartby == REQUESTS7) {
1280     chartby = requests7;
1281     tot = totr7;
1282   }
1283   else if (chartby == PAGES) {
1284     chartby = pages;
1285     tot = totp;
1286   }
1287   else if (chartby == PAGES7) {
1288     chartby = pages7;
1289     tot = totp7;
1290   }
1291   if (tot == 0 || (chartby == BYTES && totb < 0.5) ||
1292       (chartby == BYTES7 && totb7 < 0.5)) {
1293     warn('R', TRUE, "In %s, turning off empty pie chart", repname[rep]);
1294     return;
1295   }
1296 
1297   /* Calculate which wedges to include */
1298   findwedges(wedge, rep, items, chartby, expandlist, 0, NULL, tot, totb,
1299 	     totb7);
1300 
1301   /* Check whether we still want a chart */
1302   largestangle = wedge[0].angle;
1303   for (i = 1; i < NO_COLOURS; i++)
1304     largestangle = MAX(wedge[i].angle, largestangle);
1305   if (largestangle >= 1 - EPSILON) {
1306     warn('R', TRUE, "In %s, turning off pie chart of only one wedge",
1307 	 repname[rep]);
1308     return;
1309   }
1310   if (largestangle == 0.) {
1311     warn('R', TRUE, "In %s, turning off pie chart with no wedges",
1312 	 repname[rep]);
1313     return;
1314   }
1315   if (largestangle < MINANGLE) {
1316     warn('R', TRUE, "In %s, turning off pie chart because no wedge "
1317 	 "large enough", repname[rep]);
1318     return;
1319   }
1320 
1321   /* font and normalchart are the same for every chart, but calculating them
1322      here allows us to keep the variables only in this file */
1323   normalchart = TRUE;
1324   if (strcaseeq(lngstr[charset_], "ISO-8859-2"))
1325     font = gdFontSmall;
1326   else {
1327     font = gdFontFixed;
1328     if (!strcaseeq(lngstr[charset_], "ISO-8859-1") &&
1329 	!strcaseeq(lngstr[charset_], "US-ASCII"))
1330       normalchart = FALSE;
1331   }
1332 
1333   if (filename == NULL)
1334     filename = (char *)xmalloc(strlen(od->localchartdir) + 13);
1335   /* max poss length = localchartdir + anchorname ( <= 8 ) + ".png\0" */
1336   sprintf(filename, "%s%s%c%s", od->localchartdir, anchorname[rep], EXTSEP,
1337 	  extension);
1338   if ((pieoutf = piechart_init(filename)) == NULL)
1339     return;  /* Warning message is given in piechart_init() */
1340 
1341   /* Now we can finally get round to plotting the chart */
1342   for (i = 0; i < NO_COLOURS; i++) {
1343     key[i] = -1;
1344     if (wedge[i].name != NULL) {
1345       strcpy(workspace, wedge[i].name);
1346       do_aliasx(workspace, od->aliashead[G(rep)]);
1347       key[i] = piechart_wedge(outf, od, wedge[i].angle, workspace);
1348       /* retain i -> colour mapping for calling piechart_key() below */
1349     }
1350   }
1351   if (normalchart)
1352     piechart_caption(outf, rep, od->chartby[G(rep)], lngstr);
1353 
1354   /* Plot the catch-all wedge and close the file */
1355   if (gender == 'm')
1356     otherstr = lngstr[otherm_];
1357   else if (gender == 'f')
1358     otherstr = lngstr[otherf_];
1359   else
1360     otherstr = lngstr[othern_];
1361   keyg = piechart_wedge(outf, od, -1., otherstr);
1362   piechart_write(pieoutf, filename, od->jpegcharts);
1363 
1364   /* Now the text on the page. In CGI mode, this must be done _after_ the image
1365      is closed, or the browser may fail to find the image. This is why printing
1366      the caption and key must be done twice; above here if normalchart, below
1367      here otherwise. */
1368   if (od->outstyle == HTML)
1369     fprintf(outf, "<p><img src=\"%s%s.%s\" alt=\"\">\n", od->chartdir,
1370 	    anchorname[rep], extension);   /* '.' not EXTSEP even on RISC OS */
1371   else /* od->outstyle == XHTML */
1372     fprintf(outf, "<p><img src=\"%s%s.%s\" alt=\"\" /></p>\n", od->chartdir,
1373 	    anchorname[rep], extension);
1374 
1375   if (!normalchart) {
1376     piechart_caption(outf, rep, od->chartby[G(rep)], lngstr);
1377     for (i = 0; i < NO_COLOURS; i++) {
1378       if (key[i] != -1)
1379 	piechart_key(outf, od, key[i], wedge[i].name, extension,
1380 		     od->aliashead[G(rep)]);
1381     }
1382     if (keyg != -1)
1383       piechart_key(outf, od, keyg, otherstr, extension, NULL);
1384   }
1385 
1386   piechart_cleanup(wedge);
1387 }
1388 #endif  /* NOGRAPHICS */
1389