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/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"
55 #include "tex-glyphs.h"
57 #include "gl/minmax.h"
58 #include "gl/xalloc.h"
59 #include "gl/c-vasnprintf.h"
62 #define _(msgid) gettext (msgid)
64 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
68 /* The desired maximum line length in the TeX file. */
69 #define TEX_LINE_MAX 80
73 struct output_driver driver;
74 /* A hash table containing any Tex macros which need to be emitted. */
76 bool require_graphics;
79 struct file_handle *handle;
80 char *chart_file_name;
85 struct ll_list preamble_list;
86 struct ll_list token_list;
89 /* Ships the string STR to the driver. */
91 shipout (struct ll_list *list, const char *str, ...)
97 char *s = c_vasnprintf (NULL, &length, str, args);
105 static const struct output_driver_class tex_driver_class;
107 static void tex_output_table (struct tex_driver *, const struct table_item *);
109 static struct tex_driver *
110 tex_driver_cast (struct output_driver *driver)
112 assert (driver->class == &tex_driver_class);
113 return UP_CAST (driver, struct tex_driver, driver);
116 static struct driver_option *
117 opt (struct output_driver *d, struct string_map *options, const char *key,
118 const char *default_value)
120 return driver_option_get (d, options, key, default_value);
123 static struct output_driver *
124 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
125 struct string_map *o)
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);
134 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
137 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
138 fh_get_file_name (fh)));
140 tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
141 tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
143 tex->file = fn_open (tex->handle, "w");
144 if (tex->file == NULL)
146 msg_error (errno, _("error opening output file `%s'"),
147 fh_get_file_name (tex->handle));
154 output_driver_destroy (d);
159 /* Emit all the tokens in LIST to FILE.
160 Then destroy LIST and its contents. */
162 post_process_tokens (FILE *file, struct ll_list *list)
165 struct tex_token *tt;
166 struct tex_token *ttnext;
167 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
169 if (tt->cat == CAT_SPACE)
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
175 struct tex_token *prev_x = NULL;
176 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
179 struct tex_token *nt = ll_data (x, struct tex_token, ll);
180 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
182 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
184 word_len += ds_length (&nt->str);
188 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
196 line_len += ds_length (&tt->str);
197 if (tt->cat == CAT_EOL)
199 if (line_len >= TEX_LINE_MAX)
202 line_len = ds_length (&tt->str);
204 if (tt->cat == CAT_COMMENT)
206 fputs (ds_cstr (&tt->str), file);
207 ds_destroy (&tt->str);
214 tex_destroy (struct output_driver *driver)
216 struct tex_driver *tex = tex_driver_cast (driver);
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");
222 char *ln = get_language ();
224 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
226 shipout (&tex->preamble_list, "\n");
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");
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");
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");
254 /* Emit all the macro defintions. */
256 struct tex_macro *next;
257 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
259 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
260 shipout (&tex->preamble_list, "\n\n");
263 hmap_destroy (&tex->macros);
265 if (tex->require_graphics)
266 shipout (&tex->preamble_list, "\\input graphicx\n\n");
268 post_process_tokens (tex->file, &tex->preamble_list);
270 shipout (&tex->token_list, "\n\\bye\n");
272 post_process_tokens (tex->file, &tex->token_list);
274 fn_close (tex->handle, tex->file);
276 free (tex->chart_file_name);
277 fh_unref (tex->handle);
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. */
284 tex_escape_string (struct tex_driver *tex, const char *text,
287 size_t n = strlen (text);
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, "."))
294 /* Peek ahead to the next code sequence */
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))
304 shipout (&tex->token_list, "\\ ");
311 tex_submit (struct output_driver *driver,
312 const struct output_item *output_item)
314 struct tex_driver *tex = tex_driver_cast (driver);
316 if (is_table_item (output_item))
318 struct table_item *table_item = to_table_item (output_item);
319 tex_output_table (tex, table_item);
321 else if (is_image_item (output_item) && tex->chart_file_name != NULL)
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)
328 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
329 tex->require_graphics = true;
333 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
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,
340 if (file_name != NULL)
342 //const char *title = chart_item_get_title (chart_item);
343 // printf ("The chart title is %s\n", title);
345 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
346 tex->require_graphics = true;
350 else if (is_text_item (output_item))
352 struct text_item *text_item = to_text_item (output_item);
353 char *s = text_item_get_plain_text (text_item);
355 switch (text_item_get_type (text_item))
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");
364 shipout (&tex->token_list, "{\\tt ");
365 tex_escape_string (tex, s, false);
366 shipout (&tex->token_list, "}\\par\n\n");
369 case TEXT_ITEM_SYNTAX:
370 /* So far as I'm aware, this can never happen. */
372 printf ("Unhandled type %d\n", text_item_get_type (text_item));
377 else if (is_message_item (output_item))
379 const struct message_item *message_item = to_message_item (output_item);
380 char *s = msg_to_string (message_item_get_msg (message_item));
381 tex_escape_string (tex, s, false);
382 shipout (&tex->token_list, "\\par\n");
388 tex_put_footnote_markers (struct tex_driver *tex,
389 const struct pivot_table *pt,
390 const size_t *footnote_indexes,
393 size_t n_visible = 0;
394 for (size_t i = 0; i < n_footnotes; i++)
396 const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
400 shipout (&tex->token_list, "$^{");
402 char *marker = pivot_footnote_marker_string (f, pt);
403 tex_escape_string (tex, marker, true);
408 shipout (&tex->token_list, "}$");
412 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
413 const struct table_cell *cell)
415 struct string s = DS_EMPTY_INITIALIZER;
416 pivot_value_format_body (cell->value, pt, &s);
417 tex_escape_string (tex, ds_cstr (&s), false);
420 tex_put_footnote_markers (tex, pt,
421 cell->value->footnote_indexes,
422 cell->value->n_footnotes);
426 tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
427 const size_t *layer_indexes)
429 /* Tables are rendered in TeX with the \halign command.
430 This is described in the TeXbook Ch. 22 */
431 struct table *title, *layers, *body, *caption;
432 struct pivot_footnote **footnotes;
434 pivot_output (pt, layer_indexes, true, &title, &layers, &body,
435 &caption, NULL, &footnotes, &n_footnotes);
437 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
441 shipout (&tex->token_list, "{\\sl ");
442 struct table_cell cell;
443 table_get_cell (caption, 0, 0, &cell);
444 tex_put_table_cell (tex, pt, &cell);
445 shipout (&tex->token_list, "}\n\n");
452 shipout (&tex->token_list, "{\\bf ");
453 struct table_cell cell;
454 table_get_cell (title, 0, 0, &cell);
455 tex_put_table_cell (tex, pt, &cell);
456 shipout (&tex->token_list, "}\\par\n");
461 for (size_t y = 0; y < layers->n[V]; y++)
463 shipout (&tex->token_list, "{");
464 struct table_cell cell;
465 table_get_cell (layers, 0, y, &cell);
466 tex_put_table_cell (tex, pt, &cell);
467 shipout (&tex->token_list, "}\\par\n");
472 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
474 /* Generate the preamble */
475 for (int x = 0; x < body->n[H]; ++x)
477 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
479 if (x < body->n[H] - 1)
481 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
482 shipout (&tex->token_list, "&\\vrule\n");
485 shipout (&tex->token_list, "\\cr\n");
488 /* Emit the row data */
489 for (int y = 0; y < body->n[V]; y++)
491 enum { H = TABLE_HORZ, V = TABLE_VERT };
492 bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
495 for (int x = 0; x < body->n[H];)
497 struct table_cell cell;
499 table_get_cell (body, x, y, &cell);
501 int colspan = table_cell_colspan (&cell);
503 shipout (&tex->token_list, "&");
505 for (int i = 0; i < skipped - colspan; ++i)
506 shipout (&tex->token_list, "&");
509 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
512 /* bool is_header = (y < body->h[V][0] */
513 /* || y >= body->n[V] - body->h[V][1] */
514 /* || x < body->h[H][0] */
515 /* || x >= body->n[H] - body->h[H][1]); */
517 struct string s = DS_EMPTY_INITIALIZER;
518 bool numeric = pivot_value_format_body (cell.value, pt, &s);
520 enum table_halign halign = table_halign_interpret (
521 cell.cell_style->halign, numeric);
523 /* int rowspan = table_cell_rowspan (&cell); */
525 /* if (rowspan > 1) */
526 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
530 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
531 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
532 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
536 if (halign == TABLE_HALIGN_CENTER)
537 shipout (&tex->token_list, "\\centre{");
539 if (halign == TABLE_HALIGN_RIGHT)
540 shipout (&tex->token_list, "\\right{");
542 /* Output cell contents. */
543 tex_escape_string (tex, ds_cstr (&s), true);
546 tex_put_footnote_markers (tex, pt, cell.value->footnote_indexes,
547 cell.value->n_footnotes);
548 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
550 shipout (&tex->token_list, "}");
554 skipped = x - prev_x;
556 x = cell.d[TABLE_HORZ][1];
558 shipout (&tex->token_list, "\\cr\n");
559 if (is_column_header)
560 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
563 shipout (&tex->token_list, "}%% End of \\halign\n");
565 /* Shipout any footnotes. */
567 shipout (&tex->token_list, "\\vskip 0.5ex\n");
569 for (int i = 0; i < n_footnotes; ++i)
571 char *marker = pivot_footnote_marker_string (footnotes[i], pt);
572 char *content = pivot_value_to_string (footnotes[i]->content, pt);
574 shipout (&tex->token_list, "$^{");
575 tex_escape_string (tex, marker, false);
576 shipout (&tex->token_list, "}$");
577 tex_escape_string (tex, content, false);
583 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
586 table_unref (layers);
588 table_unref (caption);
593 tex_output_table (struct tex_driver *tex, const struct table_item *item)
595 size_t *layer_indexes;
596 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
597 tex_output_table_layer (tex, item->pt, layer_indexes);
600 struct output_driver_factory tex_driver_factory =
601 { "tex", "pspp.tex", tex_create };
603 static const struct output_driver_class tex_driver_class =