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 word_len += ds_length (&nt->str);
183 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
191 line_len += ds_length (&tt->str);
192 if (tt->cat == CAT_EOL)
194 if (line_len >= TEX_LINE_MAX)
197 line_len = ds_length (&tt->str);
199 if (tt->cat == CAT_COMMENT)
201 fputs (ds_cstr (&tt->str), file);
202 ds_destroy (&tt->str);
209 tex_destroy (struct output_driver *driver)
211 struct tex_driver *tex = tex_driver_cast (driver);
213 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
214 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
215 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
217 char *ln = get_language ();
219 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
221 shipout (&tex->preamble_list, "\n");
223 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
224 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
225 shipout (&tex->preamble_list,
226 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
227 "\\advance\\hsize by \\psppcolumnspace"
228 "\\divide\\hsize by #1"
229 "\\noindent\\raggedright\\hskip0pt}\n\n");
232 shipout (&tex->preamble_list,
233 "%%%% Render the text centre justified\n"
234 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
235 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
236 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
237 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
241 shipout (&tex->preamble_list,
242 "%%%% Render the text right justified\n"
243 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
244 "\\parfillskip=0pt}\n");
245 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
246 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
249 /* Emit all the macro defintions. */
251 struct tex_macro *next;
252 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
254 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
255 shipout (&tex->preamble_list, "\n\n");
258 hmap_destroy (&tex->macros);
260 if (tex->require_graphics)
261 shipout (&tex->preamble_list, "\\input graphicx\n\n");
263 post_process_tokens (tex->file, &tex->preamble_list);
265 shipout (&tex->token_list, "\n\\bye\n");
267 post_process_tokens (tex->file, &tex->token_list);
269 fn_close (tex->handle, tex->file);
271 free (tex->chart_file_name);
272 fh_unref (tex->handle);
276 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
277 if TABULAR is true, then this text is within a table. */
279 tex_escape_string (struct tex_driver *tex, const char *text,
282 size_t n = strlen (text);
285 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
286 shipout (&tex->token_list, "%s", frag);
287 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
289 /* Peek ahead to the next code sequence */
291 const char *t = text;
292 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
293 /* If a period followed by whitespace is encountered within tabular
294 material, then it is reasonable to assume, that it is an
295 abbreviation (like "Sig." or "Std. Deviation") rather than the
296 end of a sentance. */
297 if (next && 0 == strcmp (" ", next))
299 shipout (&tex->token_list, "\\ ");
306 tex_submit (struct output_driver *driver, const struct output_item *item)
308 struct tex_driver *tex = tex_driver_cast (driver);
312 case OUTPUT_ITEM_CHART:
313 if (tex->chart_file_name != NULL)
315 char *file_name = xr_draw_png_chart (item->chart,
316 tex->chart_file_name,
319 if (file_name != NULL)
321 //const char *title = chart_item_get_title (chart_item);
322 // printf ("The chart title is %s\n", title);
324 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
325 tex->require_graphics = true;
331 case OUTPUT_ITEM_GROUP:
334 case OUTPUT_ITEM_IMAGE:
336 char *file_name = xr_write_png_image (
337 item->image, tex->chart_file_name, tex->chart_cnt++);
338 if (file_name != NULL)
340 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
341 tex->require_graphics = true;
347 case OUTPUT_ITEM_MESSAGE:
349 char *s = msg_to_string (item->message);
350 tex_escape_string (tex, s, false);
351 shipout (&tex->token_list, "\\par\n");
356 case OUTPUT_ITEM_PAGE_BREAK:
359 case OUTPUT_ITEM_PAGE_SETUP:
362 case OUTPUT_ITEM_TABLE:
363 tex_output_table (tex, item->table);
366 case OUTPUT_ITEM_TEXT:
368 char *s = text_item_get_plain_text (item);
370 switch (item->text.subtype)
372 case TEXT_ITEM_PAGE_TITLE:
373 shipout (&tex->token_list, "\\headline={\\bf ");
374 tex_escape_string (tex, s, false);
375 shipout (&tex->token_list, "\\hfil}\n");
379 shipout (&tex->token_list, "{\\tt ");
380 tex_escape_string (tex, s, false);
381 shipout (&tex->token_list, "}\\par\n\n");
384 case TEXT_ITEM_SYNTAX:
385 /* So far as I'm aware, this can never happen. */
387 printf ("Unhandled type %d\n", item->text.subtype);
397 tex_put_footnote_markers (struct tex_driver *tex,
398 const struct pivot_table *pt,
399 const size_t *footnote_indexes,
402 size_t n_visible = 0;
403 for (size_t i = 0; i < n_footnotes; i++)
405 const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
409 shipout (&tex->token_list, "$^{");
411 char *marker = pivot_footnote_marker_string (f, pt);
412 tex_escape_string (tex, marker, true);
417 shipout (&tex->token_list, "}$");
421 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
422 const struct table_cell *cell)
424 struct string s = DS_EMPTY_INITIALIZER;
425 pivot_value_format_body (cell->value, pt, &s);
426 tex_escape_string (tex, ds_cstr (&s), false);
429 tex_put_footnote_markers (tex, pt,
430 cell->value->footnote_indexes,
431 cell->value->n_footnotes);
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, cell.value->footnote_indexes,
556 cell.value->n_footnotes);
557 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
559 shipout (&tex->token_list, "}");
563 skipped = x - prev_x;
565 x = cell.d[TABLE_HORZ][1];
567 shipout (&tex->token_list, "\\cr\n");
568 if (is_column_header)
569 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
572 shipout (&tex->token_list, "}%% End of \\halign\n");
574 /* Shipout any footnotes. */
576 shipout (&tex->token_list, "\\vskip 0.5ex\n");
578 for (int i = 0; i < n_footnotes; ++i)
580 char *marker = pivot_footnote_marker_string (footnotes[i], pt);
581 char *content = pivot_value_to_string (footnotes[i]->content, pt);
583 shipout (&tex->token_list, "$^{");
584 tex_escape_string (tex, marker, false);
585 shipout (&tex->token_list, "}$");
586 tex_escape_string (tex, content, false);
592 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
595 table_unref (layers);
597 table_unref (caption);
602 tex_output_table (struct tex_driver *tex, const struct pivot_table *pt)
604 size_t *layer_indexes;
605 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, pt, true)
606 tex_output_table_layer (tex, pt, layer_indexes);
609 struct output_driver_factory tex_driver_factory =
610 { "tex", "pspp.tex", tex_create };
612 static const struct output_driver_class tex_driver_class =
615 .destroy = tex_destroy,
616 .submit = tex_submit,