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 output_driver *d, struct string_map *options, const char *key,
113 const char *default_value)
115 return driver_option_get (d, options, key, default_value);
118 static struct output_driver *
119 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
120 struct string_map *o)
122 struct output_driver *d;
123 struct tex_driver *tex = XZALLOC (struct tex_driver);
124 hmap_init (&tex->macros);
125 ll_init (&tex->preamble_list);
126 ll_init (&tex->token_list);
129 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
132 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
133 fh_get_file_name (fh)));
135 tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
136 tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
138 tex->file = fn_open (tex->handle, "w");
139 if (tex->file == NULL)
141 msg_error (errno, _("error opening output file `%s'"),
142 fh_get_file_name (tex->handle));
149 output_driver_destroy (d);
154 /* Emit all the tokens in LIST to FILE.
155 Then destroy LIST and its contents. */
157 post_process_tokens (FILE *file, struct ll_list *list)
160 struct tex_token *tt;
161 struct tex_token *ttnext;
162 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
164 if (tt->cat == CAT_SPACE)
166 /* Count the number of characters up to the next space,
167 and if it'll not fit on to the line, then make a line
170 struct tex_token *prev_x = NULL;
171 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
174 struct tex_token *nt = ll_data (x, struct tex_token, ll);
175 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
177 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
179 ds_destroy (&prev_x->str);
183 word_len += ds_length (&nt->str);
187 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
193 ds_destroy (&tt->str);
200 line_len += ds_length (&tt->str);
201 if (tt->cat == CAT_EOL)
203 if (line_len >= TEX_LINE_MAX)
206 line_len = ds_length (&tt->str);
208 if (tt->cat == CAT_COMMENT)
210 fputs (ds_cstr (&tt->str), file);
211 ds_destroy (&tt->str);
218 tex_destroy (struct output_driver *driver)
220 struct tex_driver *tex = tex_driver_cast (driver);
222 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
223 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
224 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
226 char *ln = get_language ();
228 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
230 shipout (&tex->preamble_list, "\n");
232 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
233 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
234 shipout (&tex->preamble_list,
235 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
236 "\\advance\\hsize by \\psppcolumnspace"
237 "\\divide\\hsize by #1"
238 "\\noindent\\raggedright\\hskip0pt}\n\n");
241 shipout (&tex->preamble_list,
242 "%%%% Render the text centre justified\n"
243 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
244 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
245 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
246 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
250 shipout (&tex->preamble_list,
251 "%%%% Render the text right justified\n"
252 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
253 "\\parfillskip=0pt}\n");
254 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
255 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
258 /* Emit all the macro defintions. */
260 struct tex_macro *next;
261 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
263 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
264 shipout (&tex->preamble_list, "\n\n");
267 hmap_destroy (&tex->macros);
269 if (tex->require_graphics)
270 shipout (&tex->preamble_list, "\\input graphicx\n\n");
272 post_process_tokens (tex->file, &tex->preamble_list);
274 shipout (&tex->token_list, "\n\\bye\n");
276 post_process_tokens (tex->file, &tex->token_list);
278 fn_close (tex->handle, tex->file);
280 free (tex->chart_file_name);
281 fh_unref (tex->handle);
285 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
286 if TABULAR is true, then this text is within a table. */
288 tex_escape_string (struct tex_driver *tex, const char *text,
291 size_t n = strlen (text);
294 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
295 shipout (&tex->token_list, "%s", frag);
296 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
298 /* Peek ahead to the next code sequence */
300 const char *t = text;
301 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
302 /* If a period followed by whitespace is encountered within tabular
303 material, then it is reasonable to assume, that it is an
304 abbreviation (like "Sig." or "Std. Deviation") rather than the
305 end of a sentance. */
306 if (next && 0 == strcmp (" ", next))
308 shipout (&tex->token_list, "\\ ");
315 tex_submit (struct output_driver *driver, const struct output_item *item)
317 struct tex_driver *tex = tex_driver_cast (driver);
321 case OUTPUT_ITEM_CHART:
322 if (tex->chart_file_name != NULL)
324 char *file_name = xr_draw_png_chart (item->chart,
325 tex->chart_file_name,
328 if (file_name != NULL)
330 //const char *title = chart_item_get_title (chart_item);
331 // printf ("The chart title is %s\n", title);
333 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
334 tex->require_graphics = true;
340 case OUTPUT_ITEM_GROUP:
343 case OUTPUT_ITEM_IMAGE:
345 char *file_name = xr_write_png_image (
346 item->image, tex->chart_file_name, tex->n_charts++);
347 if (file_name != NULL)
349 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
350 tex->require_graphics = true;
356 case OUTPUT_ITEM_MESSAGE:
358 char *s = msg_to_string (item->message);
359 tex_escape_string (tex, s, false);
360 shipout (&tex->token_list, "\\par\n");
365 case OUTPUT_ITEM_PAGE_BREAK:
368 case OUTPUT_ITEM_TABLE:
369 tex_output_table (tex, item->table);
372 case OUTPUT_ITEM_TEXT:
374 char *s = text_item_get_plain_text (item);
376 switch (item->text.subtype)
378 case TEXT_ITEM_PAGE_TITLE:
379 shipout (&tex->token_list, "\\headline={\\bf ");
380 tex_escape_string (tex, s, false);
381 shipout (&tex->token_list, "\\hfil}\n");
385 shipout (&tex->token_list, "{\\tt ");
386 tex_escape_string (tex, s, false);
387 shipout (&tex->token_list, "}\\par\n\n");
390 case TEXT_ITEM_SYNTAX:
391 /* So far as I'm aware, this can never happen. */
393 printf ("Unhandled type %d\n", item->text.subtype);
403 tex_put_footnote_markers (struct tex_driver *tex,
404 const struct pivot_table *pt,
405 const struct pivot_value_ex *ex)
407 size_t n_visible = 0;
408 for (size_t i = 0; i < ex->n_footnotes; i++)
410 const struct pivot_footnote *f = pt->footnotes[ex->footnote_indexes[i]];
414 shipout (&tex->token_list, "$^{");
416 char *marker = pivot_footnote_marker_string (f, pt);
417 tex_escape_string (tex, marker, true);
422 shipout (&tex->token_list, "}$");
426 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
427 const struct table_cell *cell)
429 struct string s = DS_EMPTY_INITIALIZER;
430 pivot_value_format_body (cell->value, pt, &s);
431 tex_escape_string (tex, ds_cstr (&s), false);
434 tex_put_footnote_markers (tex, pt, pivot_value_ex (cell->value));
438 tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
439 const size_t *layer_indexes)
441 /* Tables are rendered in TeX with the \halign command.
442 This is described in the TeXbook Ch. 22 */
443 struct table *title, *layers, *body, *caption;
444 struct pivot_footnote **footnotes;
446 pivot_output (pt, layer_indexes, true, &title, &layers, &body,
447 &caption, NULL, &footnotes, &n_footnotes);
449 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
453 shipout (&tex->token_list, "{\\sl ");
454 struct table_cell cell;
455 table_get_cell (caption, 0, 0, &cell);
456 tex_put_table_cell (tex, pt, &cell);
457 shipout (&tex->token_list, "}\n\n");
464 shipout (&tex->token_list, "{\\bf ");
465 struct table_cell cell;
466 table_get_cell (title, 0, 0, &cell);
467 tex_put_table_cell (tex, pt, &cell);
468 shipout (&tex->token_list, "}\\par\n");
473 for (size_t y = 0; y < layers->n[V]; y++)
475 shipout (&tex->token_list, "{");
476 struct table_cell cell;
477 table_get_cell (layers, 0, y, &cell);
478 tex_put_table_cell (tex, pt, &cell);
479 shipout (&tex->token_list, "}\\par\n");
484 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
486 /* Generate the preamble */
487 for (int x = 0; x < body->n[H]; ++x)
489 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
491 if (x < body->n[H] - 1)
493 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
494 shipout (&tex->token_list, "&\\vrule\n");
497 shipout (&tex->token_list, "\\cr\n");
500 /* Emit the row data */
501 for (int y = 0; y < body->n[V]; y++)
503 enum { H = TABLE_HORZ, V = TABLE_VERT };
504 bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
507 for (int x = 0; x < body->n[H];)
509 struct table_cell cell;
511 table_get_cell (body, x, y, &cell);
513 int colspan = table_cell_colspan (&cell);
515 shipout (&tex->token_list, "&");
517 for (int i = 0; i < skipped - colspan; ++i)
518 shipout (&tex->token_list, "&");
521 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
524 /* bool is_header = (y < body->h[V][0] */
525 /* || y >= body->n[V] - body->h[V][1] */
526 /* || x < body->h[H][0] */
527 /* || x >= body->n[H] - body->h[H][1]); */
529 struct string s = DS_EMPTY_INITIALIZER;
530 bool numeric = pivot_value_format_body (cell.value, pt, &s);
532 enum table_halign halign = table_halign_interpret (
533 cell.cell_style->halign, numeric);
535 /* int rowspan = table_cell_rowspan (&cell); */
537 /* if (rowspan > 1) */
538 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
542 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
543 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
544 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
548 if (halign == TABLE_HALIGN_CENTER)
549 shipout (&tex->token_list, "\\centre{");
551 if (halign == TABLE_HALIGN_RIGHT)
552 shipout (&tex->token_list, "\\right{");
554 /* Output cell contents. */
555 tex_escape_string (tex, ds_cstr (&s), true);
558 tex_put_footnote_markers (tex, pt, pivot_value_ex (cell.value));
559 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
561 shipout (&tex->token_list, "}");
565 skipped = x - prev_x;
567 x = cell.d[TABLE_HORZ][1];
569 shipout (&tex->token_list, "\\cr\n");
570 if (is_column_header)
571 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
574 shipout (&tex->token_list, "}%% End of \\halign\n");
576 /* Shipout any footnotes. */
578 shipout (&tex->token_list, "\\vskip 0.5ex\n");
580 for (int i = 0; i < n_footnotes; ++i)
582 char *marker = pivot_footnote_marker_string (footnotes[i], pt);
583 char *content = pivot_value_to_string (footnotes[i]->content, pt);
585 shipout (&tex->token_list, "$^{");
586 tex_escape_string (tex, marker, false);
587 shipout (&tex->token_list, "}$");
588 tex_escape_string (tex, content, false);
594 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
597 table_unref (layers);
599 table_unref (caption);
604 tex_output_table (struct tex_driver *tex, const struct pivot_table *pt)
606 size_t *layer_indexes;
607 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, pt, true)
608 tex_output_table_layer (tex, pt, layer_indexes);
611 struct output_driver_factory tex_driver_factory =
612 { "tex", "pspp.tex", tex_create };
614 static const struct output_driver_class tex_driver_class =
617 .destroy = tex_destroy,
618 .submit = tex_submit,