1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2020 Free Software Foundation, Inc.
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.
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.
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/>. */
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"
40 #include "output/cairo-chart.h"
42 #include "output/chart-item.h"
43 #include "output/driver-provider.h"
44 #include "output/message-item.h"
45 #include "output/options.h"
46 #include "output/output-item-provider.h"
47 #include "output/pivot-output.h"
48 #include "output/pivot-table.h"
49 #include "output/table-provider.h"
50 #include "output/table-item.h"
51 #include "output/text-item.h"
52 #include "output/tex-rendering.h"
53 #include "output/tex-parsing.h"
56 #include "tex-glyphs.h"
58 #include "gl/minmax.h"
59 #include "gl/xalloc.h"
60 #include "gl/c-vasnprintf.h"
63 #define _(msgid) gettext (msgid)
65 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
69 /* The desired maximum line length in the TeX file. */
70 #define TEX_LINE_MAX 80
74 struct output_driver driver;
75 /* A hash table containing any Tex macros which need to be emitted. */
77 bool require_graphics;
82 struct file_handle *handle;
83 char *chart_file_name;
88 struct ll_list preamble_list;
89 struct ll_list token_list;
92 /* Ships the string STR to the driver. */
94 shipout (struct ll_list *list, const char *str, ...)
100 char *s = c_vasnprintf (NULL, &length, str, args);
108 static const struct output_driver_class tex_driver_class;
110 static void tex_output_table (struct tex_driver *, const struct table_item *);
112 static struct tex_driver *
113 tex_driver_cast (struct output_driver *driver)
115 assert (driver->class == &tex_driver_class);
116 return UP_CAST (driver, struct tex_driver, driver);
119 static struct driver_option *
120 opt (struct output_driver *d, struct string_map *options, const char *key,
121 const char *default_value)
123 return driver_option_get (d, options, key, default_value);
126 static struct output_driver *
127 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
128 struct string_map *o)
130 struct output_driver *d;
131 struct tex_driver *tex = XZALLOC (struct tex_driver);
132 hmap_init (&tex->macros);
133 ll_init (&tex->preamble_list);
134 ll_init (&tex->token_list);
137 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
140 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
141 fh_get_file_name (fh)));
144 tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
145 tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
148 tex->file = fn_open (tex->handle, "w");
149 if (tex->file == NULL)
151 msg_error (errno, _("error opening output file `%s'"),
152 fh_get_file_name (tex->handle));
159 output_driver_destroy (d);
164 /* Emit all the tokens in LIST to FILE.
165 Then destroy LIST and its contents. */
167 post_process_tokens (FILE *file, struct ll_list *list)
170 struct tex_token *tt;
171 struct tex_token *ttnext;
172 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
174 if (tt->cat == CAT_SPACE)
176 /* Count the number of characters up to the next space,
177 and if it'll not fit on to the line, then make a line
180 struct tex_token *prev_x = NULL;
181 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
184 struct tex_token *nt = ll_data (x, struct tex_token, ll);
185 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
187 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
189 word_len += ds_length (&nt->str);
193 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
201 line_len += ds_length (&tt->str);
202 if (tt->cat == CAT_EOL)
204 if (line_len >= TEX_LINE_MAX)
207 line_len = ds_length (&tt->str);
209 if (tt->cat == CAT_COMMENT)
211 fputs (ds_cstr (&tt->str), file);
212 ds_destroy (&tt->str);
219 tex_destroy (struct output_driver *driver)
221 struct tex_driver *tex = tex_driver_cast (driver);
223 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
224 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
225 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
227 char *ln = get_language ();
229 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
231 shipout (&tex->preamble_list, "\n");
233 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
234 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
235 shipout (&tex->preamble_list,
236 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
237 "\\advance\\hsize by \\psppcolumnspace"
238 "\\divide\\hsize by #1"
239 "\\noindent\\raggedright\\hskip0pt}\n\n");
242 shipout (&tex->preamble_list,
243 "%%%% Render the text centre justified\n"
244 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
245 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
246 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
247 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
251 shipout (&tex->preamble_list,
252 "%%%% Render the text right justified\n"
253 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
254 "\\parfillskip=0pt}\n");
255 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
256 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
259 /* Emit all the macro defintions. */
261 struct tex_macro *next;
262 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
264 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
265 shipout (&tex->preamble_list, "\n\n");
268 hmap_destroy (&tex->macros);
270 if (tex->require_graphics)
271 shipout (&tex->preamble_list, "\\input graphicx\n\n");
273 post_process_tokens (tex->file, &tex->preamble_list);
275 shipout (&tex->token_list, "\n\\bye\n");
277 post_process_tokens (tex->file, &tex->token_list);
279 fn_close (tex->handle, tex->file);
281 free (tex->chart_file_name);
282 fh_unref (tex->handle);
286 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
287 if TABULAR is true, then this text is within a table. */
289 tex_escape_string (struct tex_driver *tex, const char *text,
292 size_t n = strlen (text);
295 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
296 shipout (&tex->token_list, "%s", frag);
297 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
299 /* Peek ahead to the next code sequence */
301 const char *t = text;
302 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
303 /* If a period followed by whitespace is encountered within tabular
304 material, then it is reasonable to assume, that it is an
305 abbreviation (like "Sig." or "Std. Deviation") rather than the
306 end of a sentance. */
307 if (next && 0 == strcmp (" ", next))
309 shipout (&tex->token_list, "\\ ");
316 tex_submit (struct output_driver *driver,
317 const struct output_item *output_item)
319 struct tex_driver *tex = tex_driver_cast (driver);
321 if (is_table_item (output_item))
323 struct table_item *table_item = to_table_item (output_item);
324 tex_output_table (tex, table_item);
327 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
329 struct chart_item *chart_item = to_chart_item (output_item);
330 char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
334 if (file_name != NULL)
336 //const char *title = chart_item_get_title (chart_item);
337 // printf ("The chart title is %s\n", title);
339 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
340 tex->require_graphics = true;
344 #endif /* HAVE_CAIRO */
345 else if (is_text_item (output_item))
347 struct text_item *text_item = to_text_item (output_item);
348 const char *s = text_item_get_text (text_item);
350 switch (text_item_get_type (text_item))
352 case TEXT_ITEM_PAGE_TITLE:
353 shipout (&tex->token_list, "\\headline={\\bf ");
354 tex_escape_string (tex, s, false);
355 shipout (&tex->token_list, "\\hfil}\n");
359 shipout (&tex->token_list, "{\\tt ");
360 tex_escape_string (tex, s, false);
361 shipout (&tex->token_list, "}\\par\n\n");
364 case TEXT_ITEM_SYNTAX:
365 /* So far as I'm aware, this can never happen. */
367 printf ("Unhandled type %d\n", text_item_get_type (text_item));
371 else if (is_message_item (output_item))
373 const struct message_item *message_item = to_message_item (output_item);
374 char *s = msg_to_string (message_item_get_msg (message_item));
375 tex_escape_string (tex, s, false);
376 shipout (&tex->token_list, "\\par\n");
382 tex_put_footnote_markers (struct tex_driver *tex,
383 const struct pivot_table *pt,
384 const size_t *footnote_indexes,
388 shipout (&tex->token_list, "$^{");
389 for (size_t i = 0; i < n_footnotes; i++)
391 const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
392 char *marker = pivot_value_to_string (f->marker, pt);
393 tex_escape_string (tex, marker, true);
397 shipout (&tex->token_list, "}$");
401 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
402 const struct table_cell *cell)
404 struct string s = DS_EMPTY_INITIALIZER;
405 pivot_value_format_body (cell->value, pt, &s);
406 tex_escape_string (tex, ds_cstr (&s), false);
409 tex_put_footnote_markers (tex, pt,
410 cell->value->footnote_indexes,
411 cell->value->n_footnotes);
415 tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
416 const size_t *layer_indexes)
418 /* Tables are rendered in TeX with the \halign command.
419 This is described in the TeXbook Ch. 22 */
420 struct table *title, *layers, *body, *caption;
421 struct pivot_footnote **footnotes;
423 pivot_output (pt, layer_indexes, true, &title, &layers, &body,
424 &caption, NULL, &footnotes, &n_footnotes);
426 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
430 shipout (&tex->token_list, "{\\sl ");
431 struct table_cell cell;
432 table_get_cell (caption, 0, 0, &cell);
433 tex_put_table_cell (tex, pt, &cell);
434 shipout (&tex->token_list, "}\n\n");
441 shipout (&tex->token_list, "{\\bf ");
442 struct table_cell cell;
443 table_get_cell (title, 0, 0, &cell);
444 tex_put_table_cell (tex, pt, &cell);
445 shipout (&tex->token_list, "}\\par\n");
450 for (size_t y = 0; y < layers->n[V]; y++)
452 shipout (&tex->token_list, "{");
453 struct table_cell cell;
454 table_get_cell (layers, 0, y, &cell);
455 tex_put_table_cell (tex, pt, &cell);
456 shipout (&tex->token_list, "}\\par\n");
461 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
463 /* Generate the preamble */
464 for (int x = 0; x < body->n[H]; ++x)
466 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
468 if (x < body->n[H] - 1)
470 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
471 shipout (&tex->token_list, "&\\vrule\n");
474 shipout (&tex->token_list, "\\cr\n");
477 /* Emit the row data */
478 for (int y = 0; y < body->n[V]; y++)
480 enum { H = TABLE_HORZ, V = TABLE_VERT };
481 bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
484 for (int x = 0; x < body->n[H];)
486 struct table_cell cell;
488 table_get_cell (body, x, y, &cell);
490 int colspan = table_cell_colspan (&cell);
492 shipout (&tex->token_list, "&");
494 for (int i = 0; i < skipped - colspan; ++i)
495 shipout (&tex->token_list, "&");
498 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
501 /* bool is_header = (y < body->h[V][0] */
502 /* || y >= body->n[V] - body->h[V][1] */
503 /* || x < body->h[H][0] */
504 /* || x >= body->n[H] - body->h[H][1]); */
506 struct string s = DS_EMPTY_INITIALIZER;
507 bool numeric = pivot_value_format_body (cell.value, pt, &s);
509 enum table_halign halign = table_halign_interpret (
510 cell.cell_style->halign, numeric);
512 /* int rowspan = table_cell_rowspan (&cell); */
514 /* if (rowspan > 1) */
515 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
519 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
520 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
521 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
525 if (halign == TABLE_HALIGN_CENTER)
526 shipout (&tex->token_list, "\\centre{");
528 if (halign == TABLE_HALIGN_RIGHT)
529 shipout (&tex->token_list, "\\right{");
531 /* Output cell contents. */
532 tex_escape_string (tex, ds_cstr (&s), true);
535 tex_put_footnote_markers (tex, pt, cell.value->footnote_indexes,
536 cell.value->n_footnotes);
537 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
539 shipout (&tex->token_list, "}");
543 skipped = x - prev_x;
545 x = cell.d[TABLE_HORZ][1];
547 shipout (&tex->token_list, "\\cr\n");
548 if (is_column_header)
549 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
552 shipout (&tex->token_list, "}%% End of \\halign\n");
554 /* Shipout any footnotes. */
556 shipout (&tex->token_list, "\\vskip 0.5ex\n");
558 for (int i = 0; i < n_footnotes; ++i)
560 char *marker = pivot_value_to_string (footnotes[i]->marker, pt);
561 char *content = pivot_value_to_string (footnotes[i]->content, pt);
563 shipout (&tex->token_list, "$^{");
564 tex_escape_string (tex, marker, false);
565 shipout (&tex->token_list, "}$");
566 tex_escape_string (tex, content, false);
572 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
575 table_unref (layers);
577 table_unref (caption);
582 tex_output_table (struct tex_driver *tex, const struct table_item *item)
584 size_t *layer_indexes;
585 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
586 tex_output_table_layer (tex, item->pt, layer_indexes);
589 struct output_driver_factory tex_driver_factory =
590 { "tex", "pspp.tex", tex_create };
592 static const struct output_driver_class tex_driver_class =