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 const 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_item_text (struct tex_driver *tex,
398 const struct table_item_text *text)
400 tex_escape_string (tex, text->content, false);
401 tex_put_footnote_markers (tex, text->footnotes, text->n_footnotes);
405 tex_output_table (struct tex_driver *tex, const struct table_item *item)
407 /* Tables are rendered in TeX with the \halign command.
408 This is described in the TeXbook Ch. 22 */
410 const struct table *t = table_item_get_table (item);
412 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
414 const struct table_item_text *caption = table_item_get_caption (item);
417 shipout (&tex->token_list, "{\\sl ");
418 tex_escape_string (tex, caption->content, false);
419 shipout (&tex->token_list, "}\n\n");
421 const struct footnote **f;
422 size_t n_footnotes = table_collect_footnotes (item, &f);
424 const struct table_item_text *title = table_item_get_title (item);
425 const struct table_item_layers *layers = table_item_get_layers (item);
430 shipout (&tex->token_list, "{\\bf ");
431 tex_put_table_item_text (tex, title);
432 shipout (&tex->token_list, "}");
436 shipout (&tex->token_list, "\\par\n");
439 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
441 /* Generate the preamble */
442 for (int x = 0; x < t->n[H]; ++x)
444 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", t->n[H]);
448 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
449 shipout (&tex->token_list, "&\\vrule\n");
452 shipout (&tex->token_list, "\\cr\n");
455 /* Emit the row data */
456 for (int y = 0; y < t->n[V]; y++)
458 enum { H = TABLE_HORZ, V = TABLE_VERT };
459 bool is_column_header = y < t->h[V][0] || y >= t->n[V] - t->h[V][1];
462 for (int x = 0; x < t->n[H];)
464 struct table_cell cell;
466 table_get_cell (t, x, y, &cell);
468 int colspan = table_cell_colspan (&cell);
470 shipout (&tex->token_list, "&");
472 for (int i = 0; i < skipped - colspan; ++i)
473 shipout (&tex->token_list, "&");
476 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
479 /* bool is_header = (y < t->h[V][0] */
480 /* || y >= t->n[V] - t->h[V][1] */
481 /* || x < t->h[H][0] */
482 /* || x >= t->n[H] - t->h[H][1]); */
484 enum table_halign halign =
485 table_halign_interpret (cell.style->cell_style.halign,
486 cell.options & TAB_NUMERIC);
488 /* int rowspan = table_cell_rowspan (&cell); */
490 /* if (rowspan > 1) */
491 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
495 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
496 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
497 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
501 if (halign == TABLE_HALIGN_CENTER)
502 shipout (&tex->token_list, "\\centre{");
504 if (halign == TABLE_HALIGN_RIGHT)
505 shipout (&tex->token_list, "\\right{");
507 /* Output cell contents. */
508 tex_escape_string (tex, cell.text, true);
509 tex_put_footnote_markers (tex, cell.footnotes, cell.n_footnotes);
510 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
512 shipout (&tex->token_list, "}");
516 skipped = x - prev_x;
518 x = cell.d[TABLE_HORZ][1];
520 shipout (&tex->token_list, "\\cr\n");
521 if (is_column_header)
522 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
525 shipout (&tex->token_list, "}%% End of \\halign\n");
527 /* Shipout any footnotes. */
529 shipout (&tex->token_list, "\\vskip 0.5ex\n");
531 for (int i = 0; i < n_footnotes; ++i)
533 shipout (&tex->token_list, "$^{");
534 tex_escape_string (tex, f[i]->marker, false);
535 shipout (&tex->token_list, "}$");
536 tex_escape_string (tex, f[i]->content, false);
540 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
543 struct output_driver_factory tex_driver_factory =
544 { "tex", "pspp.tex", tex_create };
546 static const struct output_driver_class tex_driver_class =