Make the Cairo and Pango libraries required rather than optional.
[pspp] / src / output / html.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012, 2013, 2014, 2017,
3    2020 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 #include <errno.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <ctype.h>
24 #include <time.h>
25 #include <unistd.h>
26 #include <locale.h>
27
28 #include "data/file-name.h"
29 #include "data/file-handle-def.h"
30 #include "libpspp/assertion.h"
31 #include "libpspp/cast.h"
32 #include "libpspp/compiler.h"
33 #include "libpspp/i18n.h"
34 #include "libpspp/message.h"
35 #include "libpspp/version.h"
36 #include "output/cairo-chart.h"
37 #include "output/chart-item.h"
38 #include "output/driver-provider.h"
39 #include "output/image-item.h"
40 #include "output/message-item.h"
41 #include "output/options.h"
42 #include "output/output-item-provider.h"
43 #include "output/pivot-output.h"
44 #include "output/pivot-table.h"
45 #include "output/table-provider.h"
46 #include "output/table-item.h"
47 #include "output/text-item.h"
48
49 #include "gl/minmax.h"
50 #include "gl/xalloc.h"
51
52 #include "gettext.h"
53 #define _(msgid) gettext (msgid)
54
55 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
56 #define H TABLE_HORZ
57 #define V TABLE_VERT
58
59 struct html_driver
60   {
61     struct output_driver driver;
62     struct cell_color fg;
63     struct cell_color bg;
64     struct file_handle *handle;
65     char *chart_file_name;
66
67     FILE *file;
68     size_t chart_cnt;
69
70     bool bare;
71     bool css;
72     bool borders;
73   };
74
75 static const struct output_driver_class html_driver_class;
76
77 static void html_output_table (struct html_driver *, const struct table_item *);
78 static void escape_string (FILE *file, const char *text,
79                            const char *space, const char *newline);
80 static void print_title_tag (FILE *file, const char *name,
81                              const char *content);
82
83 static struct html_driver *
84 html_driver_cast (struct output_driver *driver)
85 {
86   assert (driver->class == &html_driver_class);
87   return UP_CAST (driver, struct html_driver, driver);
88 }
89
90 static struct driver_option *
91 opt (struct output_driver *d, struct string_map *options, const char *key,
92      const char *default_value)
93 {
94   return driver_option_get (d, options, key, default_value);
95 }
96
97 static void
98 put_header (struct html_driver *html)
99 {
100   fputs ("<!doctype html>\n", html->file);
101   fprintf (html->file, "<html");
102   char *ln = get_language ();
103   if (ln)
104     fprintf (html->file, " lang=\"%s\"", ln);
105   free (ln);
106   fprintf (html->file, ">\n");
107   fputs ("<head>\n", html->file);
108   print_title_tag (html->file, "title", _("PSPP Output"));
109   fprintf (html->file, "<meta name=\"generator\" content=\"%s\">\n", version);
110   fputs ("<meta http-equiv=\"content-type\" "
111          "content=\"text/html; charset=utf-8\">\n", html->file);
112
113   if (html->css)
114     {
115       fputs ("<style>\n"
116              "<!--\n"
117              "body {\n"
118              "  background: white;\n"
119              "  color: black;\n"
120              "  padding: 0em 12em 0em 3em;\n"
121              "  margin: 0\n"
122              "}\n"
123              "body>p {\n"
124              "  margin: 0pt 0pt 0pt 0em\n"
125              "}\n"
126              "body>p + p {\n"
127              "  text-indent: 1.5em;\n"
128              "}\n"
129              "h1 {\n"
130              "  font-size: 150%;\n"
131              "  margin-left: -1.33em\n"
132              "}\n"
133              "h2 {\n"
134              "  font-size: 125%;\n"
135              "  font-weight: bold;\n"
136              "  margin-left: -.8em\n"
137              "}\n"
138              "h3 {\n"
139              "  font-size: 100%;\n"
140              "  font-weight: bold;\n"
141              "  margin-left: -.5em }\n"
142              "h4 {\n"
143              "  font-size: 100%;\n"
144              "  margin-left: 0em\n"
145              "}\n"
146              "h1, h2, h3, h4, h5, h6 {\n"
147              "  font-family: sans-serif;\n"
148              "  color: blue\n"
149              "}\n"
150              "html {\n"
151              "  margin: 0\n"
152              "}\n"
153              "code {\n"
154              "  font-family: sans-serif\n"
155              "}\n"
156              "table {\n"
157              "  border-collapse: collapse;\n"
158              "  margin-bottom: 1em\n"
159              "}\n"
160              "caption {\n"
161              "  text-align: left\n"
162              "}\n"
163              "th { font-weight: normal }\n"
164              "a:link {\n"
165              "  color: #1f00ff;\n"
166              "}\n"
167              "a:visited {\n"
168              "  color: #9900dd;\n"
169              "}\n"
170              "a:active {\n"
171              "  color: red;\n"
172              "}\n"
173              "-->\n"
174              "</style>\n",
175              html->file);
176     }
177   fputs ("</head>\n", html->file);
178   fputs ("<body>\n", html->file);
179 }
180
181 static struct output_driver *
182 html_create (struct file_handle *fh, enum settings_output_devices device_type,
183              struct string_map *o)
184 {
185   struct output_driver *d;
186   struct html_driver *html;
187
188   html = xzalloc (sizeof *html);
189   d = &html->driver;
190   output_driver_init (&html->driver, &html_driver_class, fh_get_file_name (fh),
191                       device_type);
192   html->bare = parse_boolean (opt (d, o, "bare", "false"));
193   html->css = parse_boolean (opt (d, o, "css", "true"));
194   html->borders = parse_boolean (opt (d, o, "borders", "true"));
195
196   html->handle = fh;
197   html->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
198                                                       fh_get_file_name (fh)));
199   html->file = NULL;
200   html->chart_cnt = 1;
201   html->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
202   html->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
203   html->file = fn_open (html->handle, "w");
204   if (html->file == NULL)
205     {
206       msg_error (errno, _("error opening output file `%s'"), fh_get_file_name (html->handle));
207       goto error;
208     }
209
210   if (!html->bare)
211     put_header (html);
212
213   return d;
214
215  error:
216   output_driver_destroy (d);
217   return NULL;
218 }
219
220 /* Emits <NAME>CONTENT</NAME> to the output, escaping CONTENT as
221    necessary for HTML. */
222 static void
223 print_title_tag (FILE *file, const char *name, const char *content)
224 {
225   if (content != NULL)
226     {
227        fprintf (file, "<%s>", name);
228       escape_string (file, content, " ", " - ");
229       fprintf (file, "</%s>\n", name);
230     }
231 }
232
233 static void
234 html_destroy (struct output_driver *driver)
235 {
236   struct html_driver *html = html_driver_cast (driver);
237
238   if (html->file != NULL)
239     {
240       if (!html->bare)
241         fprintf (html->file,
242                  "</body>\n"
243                  "</html>\n"
244                  "<!-- end of file -->\n");
245       fn_close (html->handle, html->file);
246     }
247   free (html->chart_file_name);
248   fh_unref (html->handle);
249   free (html);
250 }
251
252 static void
253 html_submit (struct output_driver *driver,
254              const struct output_item *output_item)
255 {
256   struct html_driver *html = html_driver_cast (driver);
257
258   if (is_table_item (output_item))
259     {
260       struct table_item *table_item = to_table_item (output_item);
261       html_output_table (html, table_item);
262     }
263   else if (is_image_item (output_item) && html->chart_file_name != NULL)
264     {
265       struct image_item *image_item = to_image_item (output_item);
266       char *file_name = xr_write_png_image (
267         image_item->image, html->chart_file_name, ++html->chart_cnt);
268       if (file_name != NULL)
269         {
270           fprintf (html->file, "<img src=\"%s\">", file_name);
271           free (file_name);
272         }
273     }
274   else if (is_chart_item (output_item) && html->chart_file_name != NULL)
275     {
276       struct chart_item *chart_item = to_chart_item (output_item);
277       char *file_name;
278
279       file_name = xr_draw_png_chart (chart_item, html->chart_file_name,
280                                      html->chart_cnt++,
281                                      &html->fg,
282                                      &html->bg
283                                 );
284       if (file_name != NULL)
285         {
286           const char *title = chart_item_get_title (chart_item);
287           fprintf (html->file, "<img src=\"%s\" alt=\"chart: %s\">",
288                    file_name, title ? title : _("No description"));
289           free (file_name);
290         }
291     }
292   else if (is_text_item (output_item))
293     {
294       struct text_item *text_item = to_text_item (output_item);
295       const char *s = text_item_get_text (text_item);
296
297       switch (text_item_get_type (text_item))
298         {
299         case TEXT_ITEM_PAGE_TITLE:
300           break;
301
302         case TEXT_ITEM_TITLE:
303           {
304             int level = MIN (5, output_get_group_level ()) + 1;
305             char tag[3] = { 'H', level + '1', '\0' };
306             print_title_tag (html->file, tag, s);
307           }
308           break;
309
310         case TEXT_ITEM_SYNTAX:
311           fprintf (html->file, "<pre class=\"syntax\">");
312           escape_string (html->file, s, " ", "<br>");
313           fprintf (html->file, "</pre>\n");
314           break;
315
316         case TEXT_ITEM_LOG:
317           fprintf (html->file, "<p>");
318           escape_string (html->file, s, " ", "<br>");
319           fprintf (html->file, "</p>\n");
320           break;
321         }
322     }
323   else if (is_message_item (output_item))
324     {
325       const struct message_item *message_item = to_message_item (output_item);
326       char *s = msg_to_string (message_item_get_msg (message_item));
327       fprintf (html->file, "<p>");
328       escape_string (html->file, s, " ", "<br>");
329       fprintf (html->file, "</p>\n");
330       free (s);
331     }
332 }
333
334 /* Write TEXT to file F, escaping characters as necessary for HTML.  Spaces are
335    replaced by SPACE, which should be " " or "&nbsp;" New-lines are replaced by
336    NEWLINE, which might be "<BR>" or "\n" or something else appropriate. */
337 static void
338 escape_string (FILE *file, const char *text,
339                const char *space, const char *newline)
340 {
341   for (;;)
342     {
343       char c = *text++;
344       switch (c)
345         {
346         case 0:
347           return;
348         case '\n':
349           fputs (newline, file);
350           break;
351         case '&':
352           fputs ("&amp;", file);
353           break;
354         case '<':
355           fputs ("&lt;", file);
356           break;
357         case '>':
358           fputs ("&gt;", file);
359           break;
360         case ' ':
361           fputs (space, file);
362           break;
363         case '"':
364           fputs ("&quot;", file);
365           break;
366         default:
367           putc (c, file);
368           break;
369         }
370     }
371 }
372
373 static const char *
374 border_to_css (int border)
375 {
376   switch (border)
377     {
378     case TABLE_STROKE_NONE:
379       return NULL;
380
381     case TABLE_STROKE_SOLID:
382       return "solid";
383
384     case TABLE_STROKE_DASHED:
385       return "dashed";
386
387     case TABLE_STROKE_THICK:
388       return "thick solid";
389
390     case TABLE_STROKE_THIN:
391       return "thin solid";
392
393     case TABLE_STROKE_DOUBLE:
394       return "double";
395
396     default:
397       return NULL;
398     }
399
400 }
401
402 struct css_style
403 {
404   FILE *file;
405   int n_styles;
406 };
407
408 static void
409 style_start (struct css_style *cs, FILE *file)
410 {
411   *cs = (struct css_style) {
412     .file = file,
413     .n_styles = 0,
414   };
415 }
416
417 static void
418 style_end (struct css_style *cs)
419 {
420   if (cs->n_styles > 0)
421     fputs ("'", cs->file);
422 }
423
424 static void
425 next_style (struct css_style *st)
426 {
427   bool first = !st->n_styles++;
428   fputs (first ? " style='" : "; ", st->file);
429 }
430
431 static void
432 put_style (struct css_style *st, const char *name, const char *value)
433 {
434   next_style (st);
435   fprintf (st->file, "%s: %s", name, value);
436 }
437
438 static bool
439 format_color (const struct cell_color color,
440               const struct cell_color default_color,
441               char *buf, size_t bufsize)
442 {
443   bool retval = !cell_color_equal (&color, &default_color);
444   if (retval)
445     {
446       if (color.alpha == 255)
447         snprintf (buf, bufsize, "#%02x%02x%02x", color.r, color.g, color.b);
448       else
449         snprintf (buf, bufsize, "rgba(%d, %d, %d, %.3f)",
450                   color.r, color.g, color.b, color.alpha / 255.0);
451     }
452   return retval;
453 }
454
455 static void
456 put_border (const struct table *table, const struct table_cell *cell,
457             struct css_style *style,
458             enum table_axis axis, int h, int v,
459             const char *border_name)
460 {
461   struct cell_color color;
462   const char *css = border_to_css (
463     table_get_rule (table, axis, cell->d[H][h], cell->d[V][v], &color));
464   if (css)
465     {
466       next_style (style);
467       fprintf (style->file, "border-%s: %s", border_name, css);
468
469       char buf[32];
470       if (format_color (color, (struct cell_color) CELL_COLOR_BLACK,
471                         buf, sizeof buf))
472         fprintf (style->file, " %s", buf);
473     }
474 }
475
476 static void
477 html_put_table_cell (struct html_driver *html, const struct pivot_table *pt,
478                      const struct table_cell *cell,
479                      const char *tag, const struct table *t)
480 {
481   fprintf (html->file, "<%s", tag);
482
483   struct css_style style;
484   style_start (&style, html->file);
485
486   struct string body = DS_EMPTY_INITIALIZER;
487   bool numeric = pivot_value_format_body (cell->value, pt, &body);
488
489   enum table_halign halign = table_halign_interpret (cell->cell_style->halign,
490                                                      numeric);
491
492   switch (halign)
493     {
494     case TABLE_HALIGN_RIGHT:
495       put_style (&style, "text-align", "right");
496       break;
497     case TABLE_HALIGN_CENTER:
498       put_style (&style, "text-align", "center");
499       break;
500     default:
501       /* Do nothing */
502       break;
503     }
504
505   if (cell->options & TAB_ROTATE)
506     put_style (&style, "writing-mode", "sideways-lr");
507
508   if (cell->cell_style->valign != TABLE_VALIGN_TOP)
509     {
510       put_style (&style, "vertical-align",
511                  (cell->cell_style->valign == TABLE_VALIGN_BOTTOM
512                   ? "bottom" : "middle"));
513     }
514
515   const struct font_style *fs = cell->font_style;
516   char bgcolor[32];
517   if (format_color (fs->bg[cell->d[V][0] % 2],
518                     (struct cell_color) CELL_COLOR_WHITE,
519                     bgcolor, sizeof bgcolor))
520     put_style (&style, "background", bgcolor);
521
522   char fgcolor[32];
523   if (format_color (fs->fg[cell->d[V][0] % 2],
524                     (struct cell_color) CELL_COLOR_BLACK,
525                     fgcolor, sizeof fgcolor))
526     put_style (&style, "color", fgcolor);
527
528   if (fs->typeface)
529     {
530       put_style (&style, "font-family", "\"");
531       escape_string (html->file, fs->typeface, " ", "\n");
532       putc ('"', html->file);
533     }
534   if (fs->bold)
535     put_style (&style, "font-weight", "bold");
536   if (fs->italic)
537     put_style (&style, "font-style", "italic");
538   if (fs->underline)
539     put_style (&style, "text-decoration", "underline");
540   if (fs->size)
541     {
542       char buf[32];
543       snprintf (buf, sizeof buf, "%dpt", fs->size);
544       put_style (&style, "font-size", buf);
545     }
546
547   if (t && html->borders)
548     {
549       put_border (t, cell, &style, V, 0, 0, "top");
550       put_border (t, cell, &style, H, 0, 0, "left");
551
552       if (cell->d[V][1] == t->n[V])
553         put_border (t, cell, &style, V, 0, 1, "bottom");
554       if (cell->d[H][1] == t->n[H])
555         put_border (t, cell, &style, H, 1, 0, "right");
556     }
557   style_end (&style);
558
559   int colspan = table_cell_colspan (cell);
560   if (colspan > 1)
561     fprintf (html->file, " colspan=\"%d\"", colspan);
562
563   int rowspan = table_cell_rowspan (cell);
564   if (rowspan > 1)
565     fprintf (html->file, " rowspan=\"%d\"", rowspan);
566
567   putc ('>', html->file);
568
569   const char *s = ds_cstr (&body);
570   s += strspn (s, CC_SPACES);
571   escape_string (html->file, s, " ", "<br>");
572   ds_destroy (&body);
573
574   if (cell->value->n_subscripts)
575     {
576       fputs ("<sub>", html->file);
577       for (size_t i = 0; i < cell->value->n_subscripts; i++)
578         {
579           if (i)
580             putc (',', html->file);
581           escape_string (html->file, cell->value->subscripts[i],
582                          "&nbsp;", "<br>");
583         }
584       fputs ("</sub>", html->file);
585     }
586   if (cell->value->n_footnotes > 0)
587     {
588       fputs ("<sup>", html->file);
589       for (size_t i = 0; i < cell->value->n_footnotes; i++)
590         {
591           if (i > 0)
592             putc (',', html->file);
593
594           size_t idx = cell->value->footnote_indexes[i];
595           const struct pivot_footnote *f = pt->footnotes[idx];
596           char *marker = pivot_value_to_string (f->marker, pt);
597           escape_string (html->file, marker, " ", "<br>");
598           free (marker);
599         }
600       fputs ("</sup>", html->file);
601     }
602
603   /* output </th> or </td>. */
604   fprintf (html->file, "</%s>\n", tag);
605 }
606
607 static void
608 html_output_table_layer (struct html_driver *html, const struct pivot_table *pt,
609                          const size_t *layer_indexes)
610 {
611   struct table *title, *layers, *body, *caption, *footnotes;
612   pivot_output (pt, layer_indexes, true, &title, &layers, &body,
613                 &caption, &footnotes, NULL, NULL);
614
615   fputs ("<table", html->file);
616   if (pt->notes)
617     {
618       fputs (" title=\"", html->file);
619       escape_string (html->file, pt->notes, " ", "\n");
620       putc ('"', html->file);
621     }
622   fputs (">\n", html->file);
623
624   if (title)
625     {
626       struct table_cell cell;
627       table_get_cell (title, 0, 0, &cell);
628       html_put_table_cell (html, pt, &cell, "caption", NULL);
629     }
630
631   if (layers)
632     {
633       fputs ("<thead>\n", html->file);
634       for (size_t y = 0; y < layers->n[V]; y++)
635         {
636           fputs ("<tr>\n", html->file);
637
638           struct table_cell cell;
639           table_get_cell (layers, 0, y, &cell);
640           cell.d[H][1] = body->n[H];
641           html_put_table_cell (html, pt, &cell, "td", NULL);
642
643           fputs ("</tr>\n", html->file);
644         }
645       fputs ("</thead>\n", html->file);
646     }
647
648   fputs ("<tbody>\n", html->file);
649   for (int y = 0; y < body->n[V]; y++)
650     {
651       fputs ("<tr>\n", html->file);
652       for (int x = 0; x < body->n[H]; )
653         {
654           struct table_cell cell;
655           table_get_cell (body, x, y, &cell);
656           if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0])
657             {
658               bool is_header = (y < body->h[V][0]
659                                 || y >= body->n[V] - body->h[V][1]
660                                 || x < body->h[H][0]
661                                 || x >= body->n[H] - body->h[H][1]);
662               const char *tag = is_header ? "th" : "td";
663               html_put_table_cell (html, pt, &cell, tag, body);
664             }
665
666           x = cell.d[TABLE_HORZ][1];
667         }
668       fputs ("</tr>\n", html->file);
669     }
670   fputs ("</tbody>\n", html->file);
671
672   if (caption || footnotes)
673     {
674       fprintf (html->file, "<tfoot>\n");
675
676       if (caption)
677         {
678           fputs ("<tr>\n", html->file);
679
680           struct table_cell cell;
681           table_get_cell (caption, 0, 0, &cell);
682           cell.d[H][1] = body->n[H];
683           html_put_table_cell (html, pt, &cell, "td", NULL);
684
685           fputs ("</tr>\n", html->file);
686         }
687
688       if (footnotes)
689         for (size_t y = 0; y < footnotes->n[V]; y++)
690           {
691             fputs ("<tr>\n", html->file);
692
693             struct table_cell cell;
694             table_get_cell (footnotes, 0, y, &cell);
695             cell.d[H][1] = body->n[H];
696             html_put_table_cell (html, pt, &cell, "td", NULL);
697
698             fputs ("</tr>\n", html->file);
699           }
700       fputs ("</tfoot>\n", html->file);
701     }
702
703   fputs ("</table>\n\n", html->file);
704
705   table_unref (title);
706   table_unref (layers);
707   table_unref (body);
708   table_unref (caption);
709   table_unref (footnotes);
710 }
711
712 static void
713 html_output_table (struct html_driver *html, const struct table_item *item)
714 {
715   size_t *layer_indexes;
716   PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
717     html_output_table_layer (html, item->pt, layer_indexes);
718 }
719
720 struct output_driver_factory html_driver_factory =
721   { "html", "pspp.html", html_create };
722
723 static const struct output_driver_class html_driver_class =
724   {
725     "html",
726     html_destroy,
727     html_submit,
728     NULL,
729   };