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.h"
40 #include "output/chart-item.h"
41 #include "output/driver-provider.h"
42 #include "output/message-item.h"
43 #include "output/options.h"
44 #include "output/output-item-provider.h"
45 #include "output/table-provider.h"
46 #include "output/table-item.h"
47 #include "output/text-item.h"
48 #include "output/tex-rendering.h"
49 #include "output/tex-parsing.h"
52 #include "tex-glyphs.h"
54 #include "gl/minmax.h"
55 #include "gl/xalloc.h"
56 #include "gl/c-vasnprintf.h"
59 #define _(msgid) gettext (msgid)
61 /* The desired maximum line length in the TeX file. */
62 #define TEX_LINE_MAX 80
66 struct output_driver driver;
67 /* A hash table containing any Tex macros which need to be emitted. */
73 struct file_handle *handle;
74 char *chart_file_name;
79 struct ll_list preamble_list;
80 struct ll_list token_list;
83 /* Ships the string STR to the driver. */
85 shipout (struct ll_list *list, const char *str, ...)
91 char *s = c_vasnprintf (NULL, &length, str, args);
99 static const struct output_driver_class tex_driver_class;
101 static void tex_output_table (struct tex_driver *, const struct table_item *);
103 static struct tex_driver *
104 tex_driver_cast (struct output_driver *driver)
106 assert (driver->class == &tex_driver_class);
107 return UP_CAST (driver, struct tex_driver, driver);
110 static struct driver_option *
111 opt (struct output_driver *d, struct string_map *options, const char *key,
112 const char *default_value)
114 return driver_option_get (d, options, key, default_value);
117 static struct output_driver *
118 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
119 struct string_map *o)
121 struct output_driver *d;
122 struct tex_driver *tex = XZALLOC (struct tex_driver);
123 hmap_init (&tex->macros);
124 ll_init (&tex->preamble_list);
125 ll_init (&tex->token_list);
128 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
131 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
132 fh_get_file_name (fh)));
135 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &tex->bg);
136 parse_color (d, o, "foreground-color", "#000000000000", &tex->fg);
139 tex->file = fn_open (tex->handle, "w");
140 if (tex->file == NULL)
142 msg_error (errno, _("error opening output file `%s'"),
143 fh_get_file_name (tex->handle));
150 output_driver_destroy (d);
155 /* Emit all the tokens in LIST to FILE.
156 Then destroy LIST and its contents. */
158 post_process_tokens (FILE *file, struct ll_list *list)
161 struct tex_token *tt;
162 struct tex_token *ttnext;
163 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
165 if (tt->cat == CAT_SPACE)
167 /* Count the number of characters up to the next space,
168 and if it'll not fit on to the line, then make a line
171 struct tex_token *prev_x = NULL;
172 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
175 struct tex_token *nt = ll_data (x, struct tex_token, ll);
176 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
178 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
180 word_len += ds_length (&nt->str);
184 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
192 line_len += ds_length (&tt->str);
193 if (tt->cat == CAT_EOL)
195 if (line_len >= TEX_LINE_MAX)
198 line_len = ds_length (&tt->str);
200 if (tt->cat == CAT_COMMENT)
202 fputs (ds_cstr (&tt->str), file);
203 ds_destroy (&tt->str);
210 tex_destroy (struct output_driver *driver)
212 struct tex_driver *tex = tex_driver_cast (driver);
214 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
215 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
216 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
218 shipout (&tex->preamble_list, "\\input graphicx\n\n");
220 char *ln = get_language ();
222 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
224 shipout (&tex->preamble_list, "\n");
226 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
227 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
228 shipout (&tex->preamble_list,
229 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
230 "\\advance\\hsize by \\psppcolumnspace"
231 "\\divide\\hsize by #1"
232 "\\noindent\\raggedright\\hskip0pt}\n\n");
235 shipout (&tex->preamble_list,
236 "%%%% Render the text centre justified\n"
237 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
238 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
239 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
240 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
244 shipout (&tex->preamble_list,
245 "%%%% Render the text right justified\n"
246 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
247 "\\parfillskip=0pt}\n");
248 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
249 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
252 /* Emit all the macro defintions. */
254 struct tex_macro *next;
255 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
257 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
258 shipout (&tex->preamble_list, "\n\n");
261 hmap_destroy (&tex->macros);
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,
307 const struct output_item *output_item)
309 struct tex_driver *tex = tex_driver_cast (driver);
311 if (is_table_item (output_item))
313 struct table_item *table_item = to_table_item (output_item);
314 tex_output_table (tex, table_item);
317 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
319 struct chart_item *chart_item = to_chart_item (output_item);
320 char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
324 if (file_name != NULL)
326 const char *title = chart_item_get_title (chart_item);
327 // printf ("The chart title is %s\n", title);
329 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
333 #endif /* HAVE_CAIRO */
334 else if (is_text_item (output_item))
336 struct text_item *text_item = to_text_item (output_item);
337 const char *s = text_item_get_text (text_item);
339 switch (text_item_get_type (text_item))
341 case TEXT_ITEM_PAGE_TITLE:
342 shipout (&tex->token_list, "\\headline={\\bf ");
343 tex_escape_string (tex, s, false);
344 shipout (&tex->token_list, "\\hfil}\n");
348 shipout (&tex->token_list, "{\\tt ");
349 tex_escape_string (tex, s, false);
350 shipout (&tex->token_list, "}\\par\n\n");
353 case TEXT_ITEM_EJECT_PAGE:
357 case TEXT_ITEM_SYNTAX:
358 /* So far as I'm aware, this can never happen. */
360 printf ("Unhandled type %d\n", text_item_get_type (text_item));
364 else if (is_message_item (output_item))
366 const struct message_item *message_item = to_message_item (output_item);
367 char *s = msg_to_string (message_item_get_msg (message_item));
368 tex_escape_string (tex, s, false);
369 shipout (&tex->token_list, "\\par\n");
375 tex_put_footnote_markers (struct tex_driver *tex,
376 const struct footnote **footnotes,
380 shipout (&tex->token_list, "$^{");
381 for (size_t i = 0; i < n_footnotes; i++)
383 const struct footnote *f = footnotes[i];
385 tex_escape_string (tex, f->marker, true);
388 shipout (&tex->token_list, "}$");
392 tex_put_table_item_text (struct tex_driver *tex,
393 const struct table_item_text *text)
395 tex_escape_string (tex, text->content, false);
396 tex_put_footnote_markers (tex, text->footnotes, text->n_footnotes);
400 tex_output_table (struct tex_driver *tex, const struct table_item *item)
402 /* Tables are rendered in TeX with the \halign command.
403 This is described in the TeXbook Ch. 22 */
405 const struct table *t = table_item_get_table (item);
407 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
409 const struct table_item_text *caption = table_item_get_caption (item);
412 shipout (&tex->token_list, "{\\sl ");
413 tex_escape_string (tex, caption->content, false);
414 shipout (&tex->token_list, "}\n\n");
416 const struct footnote **f;
417 size_t n_footnotes = table_collect_footnotes (item, &f);
419 const struct table_item_text *title = table_item_get_title (item);
420 const struct table_item_layers *layers = table_item_get_layers (item);
425 shipout (&tex->token_list, "{\\bf ");
426 tex_put_table_item_text (tex, title);
427 shipout (&tex->token_list, "}");
431 shipout (&tex->token_list, "\\par\n");
434 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
436 /* Generate the preamble */
437 for (int x = 0; x < table_nc (t); ++x)
439 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", table_nc (t));
441 if (x < table_nc (t) - 1)
443 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
444 shipout (&tex->token_list, "&\\vrule\n");
447 shipout (&tex->token_list, "\\cr\n");
450 /* Emit the row data */
451 for (int y = 0; y < table_nr (t); y++)
453 bool is_column_header = (y < table_ht (t)
454 || y >= table_nr (t) - table_hb (t));
457 for (int x = 0; x < table_nc (t);)
459 struct table_cell cell;
461 table_get_cell (t, x, y, &cell);
463 int colspan = table_cell_colspan (&cell);
465 shipout (&tex->token_list, "&");
467 for (int i = 0; i < skipped - colspan; ++i)
468 shipout (&tex->token_list, "&");
471 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
474 /* bool is_header = (y < table_ht (t) */
475 /* || y >= table_nr (t) - table_hb (t) */
476 /* || x < table_hl (t) */
477 /* || x >= table_nc (t) - table_hr (t)); */
480 enum table_halign halign =
481 table_halign_interpret (cell.style->cell_style.halign,
482 cell.options & TAB_NUMERIC);
484 /* int rowspan = table_cell_rowspan (&cell); */
486 /* if (rowspan > 1) */
487 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
491 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
492 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
493 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
497 if (halign == TABLE_HALIGN_CENTER)
498 shipout (&tex->token_list, "\\centre{");
500 if (halign == TABLE_HALIGN_RIGHT)
501 shipout (&tex->token_list, "\\right{");
503 /* Output cell contents. */
504 tex_escape_string (tex, cell.text, true);
505 tex_put_footnote_markers (tex, cell.footnotes, cell.n_footnotes);
506 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
508 shipout (&tex->token_list, "}");
512 skipped = x - prev_x;
514 x = cell.d[TABLE_HORZ][1];
516 shipout (&tex->token_list, "\\cr\n");
517 if (is_column_header)
518 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
521 shipout (&tex->token_list, "}%% End of \\halign\n");
523 /* Shipout any footnotes. */
525 shipout (&tex->token_list, "\\vskip 0.5ex\n");
527 for (int i = 0; i < n_footnotes; ++i)
529 shipout (&tex->token_list, "$^{");
530 tex_escape_string (tex, f[i]->marker, false);
531 shipout (&tex->token_list, "}$");
532 tex_escape_string (tex, f[i]->content, false);
536 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
539 struct output_driver_factory tex_driver_factory =
540 { "tex", "pspp.tex", tex_create };
542 static const struct output_driver_class tex_driver_class =