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. */
69 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 table_item *);
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)));
136 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &tex->bg);
137 parse_color (d, o, "foreground-color", "#000000000000", &tex->fg);
140 tex->file = fn_open (tex->handle, "w");
141 if (tex->file == NULL)
143 msg_error (errno, _("error opening output file `%s'"),
144 fh_get_file_name (tex->handle));
151 output_driver_destroy (d);
156 /* Emit all the tokens in LIST to FILE.
157 Then destroy LIST and its contents. */
159 post_process_tokens (FILE *file, struct ll_list *list)
162 struct tex_token *tt;
163 struct tex_token *ttnext;
164 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
166 if (tt->cat == CAT_SPACE)
168 /* Count the number of characters up to the next space,
169 and if it'll not fit on to the line, then make a line
172 struct tex_token *prev_x = NULL;
173 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
176 struct tex_token *nt = ll_data (x, struct tex_token, ll);
177 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
179 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
181 word_len += ds_length (&nt->str);
185 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
193 line_len += ds_length (&tt->str);
194 if (tt->cat == CAT_EOL)
196 if (line_len >= TEX_LINE_MAX)
199 line_len = ds_length (&tt->str);
201 if (tt->cat == CAT_COMMENT)
203 fputs (ds_cstr (&tt->str), file);
204 ds_destroy (&tt->str);
211 tex_destroy (struct output_driver *driver)
213 struct tex_driver *tex = tex_driver_cast (driver);
215 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
216 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
217 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
219 char *ln = get_language ();
221 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
223 shipout (&tex->preamble_list, "\n");
225 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
226 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
227 shipout (&tex->preamble_list,
228 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
229 "\\advance\\hsize by \\psppcolumnspace"
230 "\\divide\\hsize by #1"
231 "\\noindent\\raggedright\\hskip0pt}\n\n");
234 shipout (&tex->preamble_list,
235 "%%%% Render the text centre justified\n"
236 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
237 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
238 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
239 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
243 shipout (&tex->preamble_list,
244 "%%%% Render the text right justified\n"
245 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
246 "\\parfillskip=0pt}\n");
247 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
248 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
251 /* Emit all the macro defintions. */
253 struct tex_macro *next;
254 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
256 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
257 shipout (&tex->preamble_list, "\n\n");
260 hmap_destroy (&tex->macros);
262 if (tex->require_graphics)
263 shipout (&tex->preamble_list, "\\input graphicx\n\n");
265 post_process_tokens (tex->file, &tex->preamble_list);
267 shipout (&tex->token_list, "\n\\bye\n");
269 post_process_tokens (tex->file, &tex->token_list);
271 fn_close (tex->handle, tex->file);
273 free (tex->chart_file_name);
274 fh_unref (tex->handle);
278 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
279 if TABULAR is true, then this text is within a table. */
281 tex_escape_string (struct tex_driver *tex, const char *text,
284 size_t n = strlen (text);
287 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
288 shipout (&tex->token_list, "%s", frag);
289 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
291 /* Peek ahead to the next code sequence */
293 const char *t = text;
294 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
295 /* If a period followed by whitespace is encountered within tabular
296 material, then it is reasonable to assume, that it is an
297 abbreviation (like "Sig." or "Std. Deviation") rather than the
298 end of a sentance. */
299 if (next && 0 == strcmp (" ", next))
301 shipout (&tex->token_list, "\\ ");
308 tex_submit (struct output_driver *driver,
309 const struct output_item *output_item)
311 struct tex_driver *tex = tex_driver_cast (driver);
313 if (is_table_item (output_item))
315 struct table_item *table_item = to_table_item (output_item);
316 tex_output_table (tex, table_item);
319 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
321 struct chart_item *chart_item = to_chart_item (output_item);
322 char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
326 if (file_name != NULL)
328 //const char *title = chart_item_get_title (chart_item);
329 // printf ("The chart title is %s\n", title);
331 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
332 tex->require_graphics = true;
336 #endif /* HAVE_CAIRO */
337 else if (is_text_item (output_item))
339 struct text_item *text_item = to_text_item (output_item);
340 const char *s = text_item_get_text (text_item);
342 switch (text_item_get_type (text_item))
344 case TEXT_ITEM_PAGE_TITLE:
345 shipout (&tex->token_list, "\\headline={\\bf ");
346 tex_escape_string (tex, s, false);
347 shipout (&tex->token_list, "\\hfil}\n");
351 shipout (&tex->token_list, "{\\tt ");
352 tex_escape_string (tex, s, false);
353 shipout (&tex->token_list, "}\\par\n\n");
356 case TEXT_ITEM_EJECT_PAGE:
360 case TEXT_ITEM_SYNTAX:
361 /* So far as I'm aware, this can never happen. */
363 printf ("Unhandled type %d\n", text_item_get_type (text_item));
367 else if (is_message_item (output_item))
369 const struct message_item *message_item = to_message_item (output_item);
370 char *s = msg_to_string (message_item_get_msg (message_item));
371 tex_escape_string (tex, s, false);
372 shipout (&tex->token_list, "\\par\n");
378 tex_put_footnote_markers (struct tex_driver *tex,
379 const struct footnote **footnotes,
383 shipout (&tex->token_list, "$^{");
384 for (size_t i = 0; i < n_footnotes; i++)
386 const struct footnote *f = footnotes[i];
388 tex_escape_string (tex, f->marker, true);
391 shipout (&tex->token_list, "}$");
395 tex_put_table_item_text (struct tex_driver *tex,
396 const struct table_item_text *text)
398 tex_escape_string (tex, text->content, false);
399 tex_put_footnote_markers (tex, text->footnotes, text->n_footnotes);
403 tex_output_table (struct tex_driver *tex, const struct table_item *item)
405 /* Tables are rendered in TeX with the \halign command.
406 This is described in the TeXbook Ch. 22 */
408 const struct table *t = table_item_get_table (item);
410 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
412 const struct table_item_text *caption = table_item_get_caption (item);
415 shipout (&tex->token_list, "{\\sl ");
416 tex_escape_string (tex, caption->content, false);
417 shipout (&tex->token_list, "}\n\n");
419 const struct footnote **f;
420 size_t n_footnotes = table_collect_footnotes (item, &f);
422 const struct table_item_text *title = table_item_get_title (item);
423 const struct table_item_layers *layers = table_item_get_layers (item);
428 shipout (&tex->token_list, "{\\bf ");
429 tex_put_table_item_text (tex, title);
430 shipout (&tex->token_list, "}");
434 shipout (&tex->token_list, "\\par\n");
437 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
439 /* Generate the preamble */
440 for (int x = 0; x < table_nc (t); ++x)
442 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", table_nc (t));
444 if (x < table_nc (t) - 1)
446 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
447 shipout (&tex->token_list, "&\\vrule\n");
450 shipout (&tex->token_list, "\\cr\n");
453 /* Emit the row data */
454 for (int y = 0; y < table_nr (t); y++)
456 bool is_column_header = (y < table_ht (t)
457 || y >= table_nr (t) - table_hb (t));
460 for (int x = 0; x < table_nc (t);)
462 struct table_cell cell;
464 table_get_cell (t, x, y, &cell);
466 int colspan = table_cell_colspan (&cell);
468 shipout (&tex->token_list, "&");
470 for (int i = 0; i < skipped - colspan; ++i)
471 shipout (&tex->token_list, "&");
474 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
477 /* bool is_header = (y < table_ht (t) */
478 /* || y >= table_nr (t) - table_hb (t) */
479 /* || x < table_hl (t) */
480 /* || x >= table_nc (t) - table_hr (t)); */
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 =