Make the Cairo and Pango libraries required rather than optional.
[pspp] / src / output / tex.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2020 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <errno.h>
20 #include <stdint.h>
21 #include <stdlib.h>
22 #include <ctype.h>
23 #include <time.h>
24 #include <unistd.h>
25 #include <locale.h>
26
27 #include "gl/mbiter.h"
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/hmap.h"
34 #include "libpspp/ll.h"
35 #include "libpspp/i18n.h"
36 #include "libpspp/message.h"
37 #include "libpspp/temp-file.h"
38 #include "libpspp/version.h"
39 #include "output/cairo-chart.h"
40 #include "output/chart-item.h"
41 #include "output/driver-provider.h"
42 #include "output/image-item.h"
43 #include "output/message-item.h"
44 #include "output/options.h"
45 #include "output/output-item-provider.h"
46 #include "output/pivot-output.h"
47 #include "output/pivot-table.h"
48 #include "output/table-provider.h"
49 #include "output/table-item.h"
50 #include "output/text-item.h"
51 #include "output/tex-rendering.h"
52 #include "output/tex-parsing.h"
53
54
55 #include "tex-glyphs.h"
56
57 #include "gl/minmax.h"
58 #include "gl/xalloc.h"
59 #include "gl/c-vasnprintf.h"
60
61 #include "gettext.h"
62 #define _(msgid) gettext (msgid)
63
64 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
65 #define H TABLE_HORZ
66 #define V TABLE_VERT
67
68 /* The desired maximum line length in the TeX file.  */
69 #define TEX_LINE_MAX 80
70
71 struct tex_driver
72   {
73     struct output_driver driver;
74     /* A hash table containing any Tex macros which need to be emitted.  */
75     struct hmap macros;
76     bool require_graphics;
77     struct cell_color fg;
78     struct cell_color bg;
79     struct file_handle *handle;
80     char *chart_file_name;
81
82     FILE *file;
83     size_t chart_cnt;
84
85     struct ll_list preamble_list;
86     struct ll_list token_list;
87   };
88
89 /* Ships the string STR to the driver.  */
90 static void
91 shipout (struct ll_list *list, const char *str, ...)
92 {
93   va_list args;
94   va_start (args, str);
95
96   size_t length;
97   char *s = c_vasnprintf (NULL, &length, str, args);
98
99   tex_parse (s, list);
100
101   va_end (args);
102   free (s);
103 }
104
105 static const struct output_driver_class tex_driver_class;
106
107 static void tex_output_table (struct tex_driver *, const struct table_item *);
108
109 static struct tex_driver *
110 tex_driver_cast (struct output_driver *driver)
111 {
112   assert (driver->class == &tex_driver_class);
113   return UP_CAST (driver, struct tex_driver, driver);
114 }
115
116 static struct driver_option *
117 opt (struct output_driver *d, struct string_map *options, const char *key,
118      const char *default_value)
119 {
120   return driver_option_get (d, options, key, default_value);
121 }
122
123 static struct output_driver *
124 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
125              struct string_map *o)
126 {
127   struct output_driver *d;
128   struct tex_driver *tex = XZALLOC (struct tex_driver);
129   hmap_init (&tex->macros);
130   ll_init (&tex->preamble_list);
131   ll_init (&tex->token_list);
132
133   d = &tex->driver;
134   output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
135                       device_type);
136   tex->handle = fh;
137   tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
138                                                       fh_get_file_name (fh)));
139   tex->chart_cnt = 1;
140   tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
141   tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
142
143   tex->file = fn_open (tex->handle, "w");
144   if (tex->file == NULL)
145     {
146       msg_error (errno, _("error opening output file `%s'"),
147                  fh_get_file_name (tex->handle));
148       goto error;
149     }
150
151   return d;
152
153  error:
154   output_driver_destroy (d);
155   return NULL;
156 }
157
158
159 /* Emit all the tokens in LIST to FILE.
160    Then destroy LIST and its contents.  */
161 static void
162 post_process_tokens (FILE *file, struct ll_list *list)
163 {
164   size_t line_len = 0;
165   struct tex_token *tt;
166   struct tex_token *ttnext;
167   ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
168     {
169       if (tt->cat == CAT_SPACE)
170         {
171           /* Count the number of characters up to the next space,
172              and if it'll not fit on to the line, then make a line
173              break here.  */
174           size_t word_len = 0;
175           struct tex_token *prev_x = NULL;
176           for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
177                x = ll_next (x))
178             {
179               struct tex_token *nt = ll_data (x, struct tex_token, ll);
180               if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
181                 break;
182               if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
183                 break;
184               word_len += ds_length (&nt->str);
185               prev_x = nt;
186             }
187
188           if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
189             {
190               fputs ("\n", file);
191               line_len = 0;
192               continue;
193             }
194         }
195
196       line_len += ds_length (&tt->str);
197       if (tt->cat == CAT_EOL)
198         line_len = 0;
199       if (line_len >= TEX_LINE_MAX)
200         {
201           fputs ("%\n", file);
202           line_len = ds_length (&tt->str);
203         }
204       if (tt->cat == CAT_COMMENT)
205         line_len = 0;
206       fputs (ds_cstr (&tt->str), file);
207       ds_destroy (&tt->str);
208       free (tt);
209     }
210 }
211
212
213 static void
214 tex_destroy (struct output_driver *driver)
215 {
216   struct tex_driver *tex = tex_driver_cast (driver);
217
218   shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
219   shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
220   shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
221
222   char *ln = get_language ();
223   if (ln)
224     shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
225   free (ln);
226   shipout (&tex->preamble_list, "\n");
227
228   shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
229   shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
230   shipout (&tex->preamble_list,
231            "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
232            "\\advance\\hsize by \\psppcolumnspace"
233            "\\divide\\hsize by #1"
234            "\\noindent\\raggedright\\hskip0pt}\n\n");
235
236   /* centre macro */
237   shipout (&tex->preamble_list,
238            "%%%% Render the text centre justified\n"
239            "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
240            "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
241   shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
242   shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
243
244
245   /* right macro */
246   shipout (&tex->preamble_list,
247            "%%%% Render the text right justified\n"
248            "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
249            "\\parfillskip=0pt}\n");
250   shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
251   shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
252
253
254   /* Emit all the macro defintions.  */
255   struct tex_macro *m;
256   struct tex_macro *next;
257   HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
258     {
259       shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
260       shipout (&tex->preamble_list, "\n\n");
261       free (m);
262     }
263   hmap_destroy (&tex->macros);
264
265   if (tex->require_graphics)
266     shipout (&tex->preamble_list, "\\input graphicx\n\n");
267
268   post_process_tokens (tex->file, &tex->preamble_list);
269
270   shipout (&tex->token_list, "\n\\bye\n");
271
272   post_process_tokens (tex->file, &tex->token_list);
273
274   fn_close (tex->handle, tex->file);
275
276   free (tex->chart_file_name);
277   fh_unref (tex->handle);
278   free (tex);
279 }
280
281 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
282    if TABULAR is true, then this text is within a table.  */
283 static void
284 tex_escape_string (struct tex_driver *tex, const char *text,
285                    bool tabular)
286 {
287   size_t n = strlen (text);
288   while (n > 0)
289     {
290       const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
291       shipout (&tex->token_list, "%s", frag);
292       if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
293         {
294           /* Peek ahead to the next code sequence */
295           size_t nn = n;
296           const char *t = text;
297           const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
298           /* If a period followed by whitespace is encountered within tabular
299              material, then it is reasonable to assume, that it is an
300              abbreviation (like "Sig." or "Std. Deviation") rather than the
301              end of a sentance.  */
302           if (next && 0 == strcmp (" ", next))
303             {
304               shipout (&tex->token_list, "\\ ");
305             }
306         }
307     }
308 }
309
310 static void
311 tex_submit (struct output_driver *driver,
312              const struct output_item *output_item)
313 {
314   struct tex_driver *tex = tex_driver_cast (driver);
315
316   if (is_table_item (output_item))
317     {
318       struct table_item *table_item = to_table_item (output_item);
319       tex_output_table (tex, table_item);
320     }
321   else if (is_image_item (output_item) && tex->chart_file_name != NULL)
322     {
323       struct image_item *image_item = to_image_item (output_item);
324       char *file_name = xr_write_png_image (
325         image_item->image, tex->chart_file_name, tex->chart_cnt++);
326       if (file_name != NULL)
327         {
328           shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
329           tex->require_graphics = true;
330           free (file_name);
331         }
332     }
333   else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
334     {
335       struct chart_item *chart_item = to_chart_item (output_item);
336       char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
337                                            tex->chart_cnt++,
338                                            &tex->fg,
339                                            &tex->bg);
340       if (file_name != NULL)
341         {
342           //const char *title = chart_item_get_title (chart_item);
343           //          printf ("The chart title is %s\n", title);
344
345           shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
346           tex->require_graphics = true;
347           free (file_name);
348         }
349     }
350   else if (is_text_item (output_item))
351     {
352       struct text_item *text_item = to_text_item (output_item);
353       const char *s = text_item_get_text (text_item);
354
355       switch (text_item_get_type (text_item))
356         {
357         case TEXT_ITEM_PAGE_TITLE:
358           shipout (&tex->token_list, "\\headline={\\bf ");
359           tex_escape_string (tex, s, false);
360           shipout (&tex->token_list, "\\hfil}\n");
361           break;
362
363         case TEXT_ITEM_LOG:
364           shipout (&tex->token_list, "{\\tt ");
365           tex_escape_string (tex, s, false);
366           shipout (&tex->token_list, "}\\par\n\n");
367           break;
368
369         case TEXT_ITEM_SYNTAX:
370           /* So far as I'm aware, this can never happen.  */
371         default:
372           printf ("Unhandled type %d\n", text_item_get_type (text_item));
373           break;
374         }
375     }
376   else if (is_message_item (output_item))
377     {
378       const struct message_item *message_item = to_message_item (output_item);
379       char *s = msg_to_string (message_item_get_msg (message_item));
380       tex_escape_string (tex, s, false);
381       shipout (&tex->token_list, "\\par\n");
382       free (s);
383     }
384 }
385
386 static void
387 tex_put_footnote_markers (struct tex_driver *tex,
388                           const struct pivot_table *pt,
389                           const size_t *footnote_indexes,
390                           size_t n_footnotes)
391 {
392   if (n_footnotes > 0)
393     shipout (&tex->token_list, "$^{");
394   for (size_t i = 0; i < n_footnotes; i++)
395     {
396       const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
397       char *marker = pivot_value_to_string (f->marker, pt);
398       tex_escape_string (tex, marker, true);
399       free (marker);
400     }
401   if (n_footnotes > 0)
402     shipout (&tex->token_list, "}$");
403 }
404
405 static void
406 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
407                     const struct table_cell *cell)
408 {
409   struct string s = DS_EMPTY_INITIALIZER;
410   pivot_value_format_body (cell->value, pt, &s);
411   tex_escape_string (tex, ds_cstr (&s), false);
412   ds_destroy (&s);
413
414   tex_put_footnote_markers (tex, pt,
415                             cell->value->footnote_indexes,
416                             cell->value->n_footnotes);
417 }
418
419 static void
420 tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
421                         const size_t *layer_indexes)
422 {
423   /* Tables are rendered in TeX with the \halign command.
424      This is described in the TeXbook Ch. 22 */
425   struct table *title, *layers, *body, *caption;
426   struct pivot_footnote **footnotes;
427   size_t n_footnotes;
428   pivot_output (pt, layer_indexes, true, &title, &layers, &body,
429                 &caption, NULL, &footnotes, &n_footnotes);
430
431   shipout (&tex->token_list, "\n{\\parindent=0pt\n");
432
433   if (caption)
434     {
435       shipout (&tex->token_list, "{\\sl ");
436       struct table_cell cell;
437       table_get_cell (caption, 0, 0, &cell);
438       tex_put_table_cell (tex, pt, &cell);
439       shipout (&tex->token_list, "}\n\n");
440     }
441
442   if (title || layers)
443     {
444       if (title)
445         {
446           shipout (&tex->token_list, "{\\bf ");
447           struct table_cell cell;
448           table_get_cell (title, 0, 0, &cell);
449           tex_put_table_cell (tex, pt, &cell);
450           shipout (&tex->token_list, "}\\par\n");
451         }
452
453       if (layers)
454         {
455           for (size_t y = 0; y < layers->n[V]; y++)
456             {
457               shipout (&tex->token_list, "{");
458               struct table_cell cell;
459               table_get_cell (layers, 0, y, &cell);
460               tex_put_table_cell (tex, pt, &cell);
461               shipout (&tex->token_list, "}\\par\n");
462             }
463         }
464     }
465
466   shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
467
468   /* Generate the preamble */
469   for (int x = 0; x < body->n[H]; ++x)
470     {
471       shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
472
473       if (x < body->n[H] - 1)
474         {
475           shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
476           shipout (&tex->token_list, "&\\vrule\n");
477         }
478       else
479         shipout (&tex->token_list, "\\cr\n");
480     }
481
482   /* Emit the row data */
483   for (int y = 0; y < body->n[V]; y++)
484     {
485       enum { H = TABLE_HORZ, V = TABLE_VERT };
486       bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
487       int prev_x = -1;
488       int skipped = 0;
489       for (int x = 0; x < body->n[H];)
490         {
491           struct table_cell cell;
492
493           table_get_cell (body, x, y, &cell);
494
495           int colspan = table_cell_colspan (&cell);
496           if (x > 0)
497             shipout (&tex->token_list, "&");
498           else
499             for (int i = 0; i < skipped - colspan; ++i)
500               shipout (&tex->token_list, "&");
501
502
503           if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
504             goto next_1;
505
506           /* bool is_header = (y < body->h[V][0] */
507           /*                   || y >= body->n[V] - body->h[V][1] */
508           /*                   || x < body->h[H][0] */
509           /*                   || x >= body->n[H] - body->h[H][1]); */
510
511           struct string s = DS_EMPTY_INITIALIZER;
512           bool numeric = pivot_value_format_body (cell.value, pt, &s);
513
514           enum table_halign halign = table_halign_interpret (
515             cell.cell_style->halign, numeric);
516
517           /* int rowspan = table_cell_rowspan (&cell); */
518
519           /* if (rowspan > 1) */
520           /*   fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
521
522           if (colspan > 1)
523             {
524               shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
525               shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
526               shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
527                        colspan - 1);
528             }
529
530           if (halign == TABLE_HALIGN_CENTER)
531             shipout (&tex->token_list, "\\centre{");
532
533           if (halign == TABLE_HALIGN_RIGHT)
534             shipout (&tex->token_list, "\\right{");
535
536           /* Output cell contents. */
537           tex_escape_string (tex, ds_cstr (&s), true);
538           ds_destroy (&s);
539
540           tex_put_footnote_markers (tex, pt, cell.value->footnote_indexes,
541                                     cell.value->n_footnotes);
542           if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
543             {
544               shipout (&tex->token_list, "}");
545             }
546
547         next_1:
548           skipped = x - prev_x;
549           prev_x = x;
550           x = cell.d[TABLE_HORZ][1];
551         }
552       shipout (&tex->token_list, "\\cr\n");
553       if (is_column_header)
554         shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
555     }
556
557   shipout (&tex->token_list, "}%% End of \\halign\n");
558
559   /* Shipout any footnotes.  */
560   if (n_footnotes > 0)
561     shipout (&tex->token_list, "\\vskip 0.5ex\n");
562
563   for (int i = 0; i < n_footnotes; ++i)
564     {
565       char *marker = pivot_value_to_string (footnotes[i]->marker, pt);
566       char *content = pivot_value_to_string (footnotes[i]->content, pt);
567
568       shipout (&tex->token_list, "$^{");
569       tex_escape_string (tex, marker, false);
570       shipout (&tex->token_list, "}$");
571       tex_escape_string (tex, content, false);
572
573       free (content);
574       free (marker);
575     }
576
577   shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
578
579   table_unref (title);
580   table_unref (layers);
581   table_unref (body);
582   table_unref (caption);
583   free (footnotes);
584 }
585
586 static void
587 tex_output_table (struct tex_driver *tex, const struct table_item *item)
588 {
589   size_t *layer_indexes;
590   PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
591     tex_output_table_layer (tex, item->pt, layer_indexes);
592 }
593
594 struct output_driver_factory tex_driver_factory =
595   { "tex", "pspp.tex", tex_create };
596
597 static const struct output_driver_class tex_driver_class =
598   {
599     "tex",
600     tex_destroy,
601     tex_submit,
602     NULL,
603   };