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/table-provider.h"
48 #include "output/table-item.h"
49 #include "output/text-item.h"
50 #include "output/tex-rendering.h"
51 #include "output/tex-parsing.h"
54 #include "tex-glyphs.h"
56 #include "gl/minmax.h"
57 #include "gl/xalloc.h"
58 #include "gl/c-vasnprintf.h"
61 #define _(msgid) gettext (msgid)
63 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
67 /* The desired maximum line length in the TeX file. */
68 #define TEX_LINE_MAX 80
72 struct output_driver driver;
73 /* A hash table containing any Tex macros which need to be emitted. */
75 bool require_graphics;
80 struct file_handle *handle;
81 char *chart_file_name;
86 struct ll_list preamble_list;
87 struct ll_list token_list;
90 /* Ships the string STR to the driver. */
92 shipout (struct ll_list *list, const char *str, ...)
98 char *s = c_vasnprintf (NULL, &length, str, args);
106 static const struct output_driver_class tex_driver_class;
108 static void tex_output_table (struct tex_driver *, const struct table_item *);
110 static struct tex_driver *
111 tex_driver_cast (struct output_driver *driver)
113 assert (driver->class == &tex_driver_class);
114 return UP_CAST (driver, struct tex_driver, driver);
117 static struct driver_option *
118 opt (struct output_driver *d, struct string_map *options, const char *key,
119 const char *default_value)
121 return driver_option_get (d, options, key, default_value);
124 static struct output_driver *
125 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
126 struct string_map *o)
128 struct output_driver *d;
129 struct tex_driver *tex = XZALLOC (struct tex_driver);
130 hmap_init (&tex->macros);
131 ll_init (&tex->preamble_list);
132 ll_init (&tex->token_list);
135 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
138 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
139 fh_get_file_name (fh)));
142 tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
143 tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
146 tex->file = fn_open (tex->handle, "w");
147 if (tex->file == NULL)
149 msg_error (errno, _("error opening output file `%s'"),
150 fh_get_file_name (tex->handle));
157 output_driver_destroy (d);
162 /* Emit all the tokens in LIST to FILE.
163 Then destroy LIST and its contents. */
165 post_process_tokens (FILE *file, struct ll_list *list)
168 struct tex_token *tt;
169 struct tex_token *ttnext;
170 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
172 if (tt->cat == CAT_SPACE)
174 /* Count the number of characters up to the next space,
175 and if it'll not fit on to the line, then make a line
178 struct tex_token *prev_x = NULL;
179 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
182 struct tex_token *nt = ll_data (x, struct tex_token, ll);
183 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
185 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
187 word_len += ds_length (&nt->str);
191 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
199 line_len += ds_length (&tt->str);
200 if (tt->cat == CAT_EOL)
202 if (line_len >= TEX_LINE_MAX)
205 line_len = ds_length (&tt->str);
207 if (tt->cat == CAT_COMMENT)
209 fputs (ds_cstr (&tt->str), file);
210 ds_destroy (&tt->str);
217 tex_destroy (struct output_driver *driver)
219 struct tex_driver *tex = tex_driver_cast (driver);
221 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
222 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
223 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
225 char *ln = get_language ();
227 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
229 shipout (&tex->preamble_list, "\n");
231 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
232 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
233 shipout (&tex->preamble_list,
234 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
235 "\\advance\\hsize by \\psppcolumnspace"
236 "\\divide\\hsize by #1"
237 "\\noindent\\raggedright\\hskip0pt}\n\n");
240 shipout (&tex->preamble_list,
241 "%%%% Render the text centre justified\n"
242 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
243 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
244 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
245 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
249 shipout (&tex->preamble_list,
250 "%%%% Render the text right justified\n"
251 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
252 "\\parfillskip=0pt}\n");
253 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
254 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
257 /* Emit all the macro defintions. */
259 struct tex_macro *next;
260 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
262 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
263 shipout (&tex->preamble_list, "\n\n");
266 hmap_destroy (&tex->macros);
268 if (tex->require_graphics)
269 shipout (&tex->preamble_list, "\\input graphicx\n\n");
271 post_process_tokens (tex->file, &tex->preamble_list);
273 shipout (&tex->token_list, "\n\\bye\n");
275 post_process_tokens (tex->file, &tex->token_list);
277 fn_close (tex->handle, tex->file);
279 free (tex->chart_file_name);
280 fh_unref (tex->handle);
284 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
285 if TABULAR is true, then this text is within a table. */
287 tex_escape_string (struct tex_driver *tex, const char *text,
290 size_t n = strlen (text);
293 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
294 shipout (&tex->token_list, "%s", frag);
295 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
297 /* Peek ahead to the next code sequence */
299 const char *t = text;
300 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
301 /* If a period followed by whitespace is encountered within tabular
302 material, then it is reasonable to assume, that it is an
303 abbreviation (like "Sig." or "Std. Deviation") rather than the
304 end of a sentance. */
305 if (next && 0 == strcmp (" ", next))
307 shipout (&tex->token_list, "\\ ");
314 tex_submit (struct output_driver *driver,
315 const struct output_item *output_item)
317 struct tex_driver *tex = tex_driver_cast (driver);
319 if (is_table_item (output_item))
321 struct table_item *table_item = to_table_item (output_item);
322 tex_output_table (tex, table_item);
325 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
327 struct chart_item *chart_item = to_chart_item (output_item);
328 char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
332 if (file_name != NULL)
334 //const char *title = chart_item_get_title (chart_item);
335 // printf ("The chart title is %s\n", title);
337 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
338 tex->require_graphics = true;
342 #endif /* HAVE_CAIRO */
343 else if (is_text_item (output_item))
345 struct text_item *text_item = to_text_item (output_item);
346 const char *s = text_item_get_text (text_item);
348 switch (text_item_get_type (text_item))
350 case TEXT_ITEM_PAGE_TITLE:
351 shipout (&tex->token_list, "\\headline={\\bf ");
352 tex_escape_string (tex, s, false);
353 shipout (&tex->token_list, "\\hfil}\n");
357 shipout (&tex->token_list, "{\\tt ");
358 tex_escape_string (tex, s, false);
359 shipout (&tex->token_list, "}\\par\n\n");
362 case TEXT_ITEM_SYNTAX:
363 /* So far as I'm aware, this can never happen. */
365 printf ("Unhandled type %d\n", text_item_get_type (text_item));
369 else if (is_message_item (output_item))
371 const struct message_item *message_item = to_message_item (output_item);
372 char *s = msg_to_string (message_item_get_msg (message_item));
373 tex_escape_string (tex, s, false);
374 shipout (&tex->token_list, "\\par\n");
380 tex_put_footnote_markers (struct tex_driver *tex,
381 struct footnote **footnotes,
385 shipout (&tex->token_list, "$^{");
386 for (size_t i = 0; i < n_footnotes; i++)
388 const struct footnote *f = footnotes[i];
390 tex_escape_string (tex, f->marker, true);
393 shipout (&tex->token_list, "}$");
397 tex_put_table_cell (struct tex_driver *tex, const struct table_cell *cell)
399 tex_escape_string (tex, cell->text, false);
400 tex_put_footnote_markers (tex, cell->footnotes, cell->n_footnotes);
404 tex_output_table (struct tex_driver *tex, const struct table_item *item)
406 /* Tables are rendered in TeX with the \halign command.
407 This is described in the TeXbook Ch. 22 */
409 const struct table *t = table_item_get_table (item);
411 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
413 const struct table_cell *caption = table_item_get_caption (item);
416 shipout (&tex->token_list, "{\\sl ");
417 tex_escape_string (tex, caption->text, false);
418 shipout (&tex->token_list, "}\n\n");
421 size_t n_footnotes = table_collect_footnotes (item, &f);
423 const struct table_cell *title = table_item_get_title (item);
424 const struct table_item_layers *layers = table_item_get_layers (item);
429 shipout (&tex->token_list, "{\\bf ");
430 tex_put_table_cell (tex, title);
431 shipout (&tex->token_list, "}");
435 shipout (&tex->token_list, "\\par\n");
438 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
440 /* Generate the preamble */
441 for (int x = 0; x < t->n[H]; ++x)
443 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", t->n[H]);
447 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
448 shipout (&tex->token_list, "&\\vrule\n");
451 shipout (&tex->token_list, "\\cr\n");
454 /* Emit the row data */
455 for (int y = 0; y < t->n[V]; y++)
457 enum { H = TABLE_HORZ, V = TABLE_VERT };
458 bool is_column_header = y < t->h[V][0] || y >= t->n[V] - t->h[V][1];
461 for (int x = 0; x < t->n[H];)
463 struct table_cell cell;
465 table_get_cell (t, x, y, &cell);
467 int colspan = table_cell_colspan (&cell);
469 shipout (&tex->token_list, "&");
471 for (int i = 0; i < skipped - colspan; ++i)
472 shipout (&tex->token_list, "&");
475 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
478 /* bool is_header = (y < t->h[V][0] */
479 /* || y >= t->n[V] - t->h[V][1] */
480 /* || x < t->h[H][0] */
481 /* || x >= t->n[H] - t->h[H][1]); */
483 enum table_halign halign =
484 table_halign_interpret (cell.style->cell_style.halign,
485 cell.options & TAB_NUMERIC);
487 /* int rowspan = table_cell_rowspan (&cell); */
489 /* if (rowspan > 1) */
490 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
494 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
495 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
496 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
500 if (halign == TABLE_HALIGN_CENTER)
501 shipout (&tex->token_list, "\\centre{");
503 if (halign == TABLE_HALIGN_RIGHT)
504 shipout (&tex->token_list, "\\right{");
506 /* Output cell contents. */
507 tex_escape_string (tex, cell.text, true);
508 tex_put_footnote_markers (tex, cell.footnotes, cell.n_footnotes);
509 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
511 shipout (&tex->token_list, "}");
515 skipped = x - prev_x;
517 x = cell.d[TABLE_HORZ][1];
519 shipout (&tex->token_list, "\\cr\n");
520 if (is_column_header)
521 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
524 shipout (&tex->token_list, "}%% End of \\halign\n");
526 /* Shipout any footnotes. */
528 shipout (&tex->token_list, "\\vskip 0.5ex\n");
530 for (int i = 0; i < n_footnotes; ++i)
532 shipout (&tex->token_list, "$^{");
533 tex_escape_string (tex, f[i]->marker, false);
534 shipout (&tex->token_list, "}$");
535 tex_escape_string (tex, f[i]->content, false);
539 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
542 struct output_driver_factory tex_driver_factory =
543 { "tex", "pspp.tex", tex_create };
545 static const struct output_driver_class tex_driver_class =