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"
39 #include "output/cairo-chart.h"
40 #include "output/driver-provider.h"
41 #include "output/options.h"
42 #include "output/output-item.h"
43 #include "output/pivot-output.h"
44 #include "output/pivot-table.h"
45 #include "output/table-provider.h"
46 #include "output/tex-rendering.h"
47 #include "output/tex-parsing.h"
50 #include "tex-glyphs.h"
52 #include "gl/minmax.h"
53 #include "gl/xalloc.h"
54 #include "gl/c-vasnprintf.h"
57 #define _(msgid) gettext (msgid)
59 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
63 /* The desired maximum line length in the TeX file. */
64 #define TEX_LINE_MAX 80
68 struct output_driver driver;
69 /* A hash table containing any Tex macros which need to be emitted. */
71 bool require_graphics;
74 struct file_handle *handle;
75 char *chart_file_name;
80 struct ll_list preamble_list;
81 struct ll_list token_list;
84 /* Ships the string STR to the driver. */
86 shipout (struct ll_list *list, const char *str, ...)
92 char *s = c_vasnprintf (NULL, &length, str, args);
100 static const struct output_driver_class tex_driver_class;
102 static void tex_output_table (struct tex_driver *, const struct pivot_table *);
104 static struct tex_driver *
105 tex_driver_cast (struct output_driver *driver)
107 assert (driver->class == &tex_driver_class);
108 return UP_CAST (driver, struct tex_driver, driver);
111 static struct driver_option *
112 opt (struct string_map *options, const char *key, const char *default_value)
114 return driver_option_get ("tex", options, key, default_value);
117 static struct output_driver *
118 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
119 struct string_map *o)
121 FILE *file = fn_open (fh, "w");
124 msg_error (errno, _("error opening output file `%s'"),
125 fh_get_file_name (fh));
129 struct tex_driver *tex = xmalloc (sizeof *tex);
130 *tex = (struct tex_driver) {
132 .class = &tex_driver_class,
133 .name = xstrdup (fh_get_file_name (fh)),
134 .device_type = device_type,
136 .macros = HMAP_INITIALIZER (tex->macros),
137 .bg = parse_color (opt (o, "background-color", "#FFFFFFFFFFFF")),
138 .fg = parse_color (opt (o, "foreground-color", "#000000000000")),
140 .chart_file_name = parse_chart_file_name (opt (o, "charts",
141 fh_get_file_name (fh))),
144 .preamble_list = LL_INITIALIZER (tex->preamble_list),
145 .token_list = LL_INITIALIZER (tex->token_list),
151 /* Emit all the tokens in LIST to FILE.
152 Then destroy LIST and its contents. */
154 post_process_tokens (FILE *file, struct ll_list *list)
157 struct tex_token *tt;
158 struct tex_token *ttnext;
159 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
161 if (tt->cat == CAT_SPACE)
163 /* Count the number of characters up to the next space,
164 and if it'll not fit on to the line, then make a line
167 struct tex_token *prev_x = NULL;
168 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
171 struct tex_token *nt = ll_data (x, struct tex_token, ll);
172 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
174 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
176 ds_destroy (&prev_x->str);
180 word_len += ds_length (&nt->str);
184 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
190 ds_destroy (&tt->str);
197 line_len += ds_length (&tt->str);
198 if (tt->cat == CAT_EOL)
200 if (line_len >= TEX_LINE_MAX)
203 line_len = ds_length (&tt->str);
205 if (tt->cat == CAT_COMMENT)
207 fputs (ds_cstr (&tt->str), file);
208 ds_destroy (&tt->str);
215 tex_destroy (struct output_driver *driver)
217 struct tex_driver *tex = tex_driver_cast (driver);
219 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
220 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
221 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
223 char *ln = get_language ();
225 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
227 shipout (&tex->preamble_list, "\n");
229 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
230 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
231 shipout (&tex->preamble_list,
232 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
233 "\\advance\\hsize by \\psppcolumnspace"
234 "\\divide\\hsize by #1"
235 "\\noindent\\raggedright\\hskip0pt}\n\n");
238 shipout (&tex->preamble_list,
239 "%%%% Render the text centre justified\n"
240 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
241 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
242 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
243 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
247 shipout (&tex->preamble_list,
248 "%%%% Render the text right justified\n"
249 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
250 "\\parfillskip=0pt}\n");
251 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
252 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
255 /* Emit all the macro defintions. */
257 struct tex_macro *next;
258 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
260 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
261 shipout (&tex->preamble_list, "\n\n");
264 hmap_destroy (&tex->macros);
266 if (tex->require_graphics)
267 shipout (&tex->preamble_list, "\\input graphicx\n\n");
269 post_process_tokens (tex->file, &tex->preamble_list);
271 shipout (&tex->token_list, "\n\\bye\n");
273 post_process_tokens (tex->file, &tex->token_list);
275 fn_close (tex->handle, tex->file);
277 free (tex->chart_file_name);
278 fh_unref (tex->handle);
282 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
283 if TABULAR is true, then this text is within a table. */
285 tex_escape_string (struct tex_driver *tex, const char *text,
288 size_t n = strlen (text);
291 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
292 shipout (&tex->token_list, "%s", frag);
293 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
295 /* Peek ahead to the next code sequence */
297 const char *t = text;
298 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
299 /* If a period followed by whitespace is encountered within tabular
300 material, then it is reasonable to assume, that it is an
301 abbreviation (like "Sig." or "Std. Deviation") rather than the
302 end of a sentance. */
303 if (next && 0 == strcmp (" ", next))
305 shipout (&tex->token_list, "\\ ");
312 tex_submit (struct output_driver *driver, const struct output_item *item)
314 struct tex_driver *tex = tex_driver_cast (driver);
318 case OUTPUT_ITEM_CHART:
319 if (tex->chart_file_name != NULL)
321 char *file_name = xr_draw_png_chart (item->chart,
322 tex->chart_file_name,
325 if (file_name != NULL)
327 //const char *title = chart_item_get_title (chart_item);
328 // printf ("The chart title is %s\n", title);
330 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
331 tex->require_graphics = true;
337 case OUTPUT_ITEM_GROUP:
340 case OUTPUT_ITEM_IMAGE:
342 char *file_name = xr_write_png_image (
343 item->image, tex->chart_file_name, tex->n_charts++);
344 if (file_name != NULL)
346 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
347 tex->require_graphics = true;
353 case OUTPUT_ITEM_MESSAGE:
355 char *s = msg_to_string (item->message);
356 tex_escape_string (tex, s, false);
357 shipout (&tex->token_list, "\\par\n");
362 case OUTPUT_ITEM_PAGE_BREAK:
365 case OUTPUT_ITEM_TABLE:
366 tex_output_table (tex, item->table);
369 case OUTPUT_ITEM_TEXT:
371 char *s = text_item_get_plain_text (item);
373 switch (item->text.subtype)
375 case TEXT_ITEM_PAGE_TITLE:
376 shipout (&tex->token_list, "\\headline={\\bf ");
377 tex_escape_string (tex, s, false);
378 shipout (&tex->token_list, "\\hfil}\n");
382 shipout (&tex->token_list, "{\\tt ");
383 tex_escape_string (tex, s, false);
384 shipout (&tex->token_list, "}\\par\n\n");
387 case TEXT_ITEM_SYNTAX:
388 /* So far as I'm aware, this can never happen. */
390 printf ("Unhandled type %d\n", item->text.subtype);
400 tex_put_footnote_markers (struct tex_driver *tex,
401 const struct pivot_table *pt,
402 const struct pivot_value_ex *ex)
404 size_t n_visible = 0;
405 for (size_t i = 0; i < ex->n_footnotes; i++)
407 const struct pivot_footnote *f = pt->footnotes[ex->footnote_indexes[i]];
411 shipout (&tex->token_list, "$^{");
413 char *marker = pivot_footnote_marker_string (f, pt);
414 tex_escape_string (tex, marker, true);
419 shipout (&tex->token_list, "}$");
423 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
424 const struct table_cell *cell)
426 struct string s = DS_EMPTY_INITIALIZER;
427 pivot_value_format_body (cell->value, pt, &s);
428 tex_escape_string (tex, ds_cstr (&s), false);
431 tex_put_footnote_markers (tex, pt, pivot_value_ex (cell->value));
435 tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
436 const size_t *layer_indexes)
438 /* Tables are rendered in TeX with the \halign command.
439 This is described in the TeXbook Ch. 22 */
440 struct table *title, *layers, *body, *caption;
441 struct pivot_footnote **footnotes;
443 pivot_output (pt, layer_indexes, true, &title, &layers, &body,
444 &caption, NULL, &footnotes, &n_footnotes);
446 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
450 shipout (&tex->token_list, "{\\sl ");
451 struct table_cell cell;
452 table_get_cell (caption, 0, 0, &cell);
453 tex_put_table_cell (tex, pt, &cell);
454 shipout (&tex->token_list, "}\n\n");
461 shipout (&tex->token_list, "{\\bf ");
462 struct table_cell cell;
463 table_get_cell (title, 0, 0, &cell);
464 tex_put_table_cell (tex, pt, &cell);
465 shipout (&tex->token_list, "}\\par\n");
470 for (size_t y = 0; y < layers->n[V]; y++)
472 shipout (&tex->token_list, "{");
473 struct table_cell cell;
474 table_get_cell (layers, 0, y, &cell);
475 tex_put_table_cell (tex, pt, &cell);
476 shipout (&tex->token_list, "}\\par\n");
481 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
483 /* Generate the preamble */
484 for (int x = 0; x < body->n[H]; ++x)
486 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
488 if (x < body->n[H] - 1)
490 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
491 shipout (&tex->token_list, "&\\vrule\n");
494 shipout (&tex->token_list, "\\cr\n");
497 /* Emit the row data */
498 for (int y = 0; y < body->n[V]; y++)
500 enum { H = TABLE_HORZ, V = TABLE_VERT };
501 bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
504 for (int x = 0; x < body->n[H];)
506 struct table_cell cell;
508 table_get_cell (body, x, y, &cell);
510 int colspan = table_cell_colspan (&cell);
512 shipout (&tex->token_list, "&");
514 for (int i = 0; i < skipped - colspan; ++i)
515 shipout (&tex->token_list, "&");
518 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
521 /* bool is_header = (y < body->h[V][0] */
522 /* || y >= body->n[V] - body->h[V][1] */
523 /* || x < body->h[H][0] */
524 /* || x >= body->n[H] - body->h[H][1]); */
526 struct string s = DS_EMPTY_INITIALIZER;
527 bool numeric = pivot_value_format_body (cell.value, pt, &s);
529 enum table_halign halign = table_halign_interpret (
530 cell.cell_style->halign, numeric);
532 /* int rowspan = table_cell_rowspan (&cell); */
534 /* if (rowspan > 1) */
535 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
539 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
540 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
541 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
545 if (halign == TABLE_HALIGN_CENTER)
546 shipout (&tex->token_list, "\\centre{");
548 if (halign == TABLE_HALIGN_RIGHT)
549 shipout (&tex->token_list, "\\right{");
551 /* Output cell contents. */
552 tex_escape_string (tex, ds_cstr (&s), true);
555 tex_put_footnote_markers (tex, pt, pivot_value_ex (cell.value));
556 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
558 shipout (&tex->token_list, "}");
562 skipped = x - prev_x;
564 x = cell.d[TABLE_HORZ][1];
566 shipout (&tex->token_list, "\\cr\n");
567 if (is_column_header)
568 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
571 shipout (&tex->token_list, "}%% End of \\halign\n");
573 /* Shipout any footnotes. */
575 shipout (&tex->token_list, "\\vskip 0.5ex\n");
577 for (int i = 0; i < n_footnotes; ++i)
579 char *marker = pivot_footnote_marker_string (footnotes[i], pt);
580 char *content = pivot_value_to_string (footnotes[i]->content, pt);
582 shipout (&tex->token_list, "$^{");
583 tex_escape_string (tex, marker, false);
584 shipout (&tex->token_list, "}$");
585 tex_escape_string (tex, content, false);
591 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
594 table_unref (layers);
596 table_unref (caption);
601 tex_output_table (struct tex_driver *tex, const struct pivot_table *pt)
603 size_t *layer_indexes;
604 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, pt, true)
605 tex_output_table_layer (tex, pt, layer_indexes);
608 struct output_driver_factory tex_driver_factory =
609 { "tex", "pspp.tex", tex_create };
611 static const struct output_driver_class tex_driver_class =
614 .destroy = tex_destroy,
615 .submit = tex_submit,