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 /* 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;
76 struct file_handle *handle;
77 char *chart_file_name;
82 struct ll_list preamble_list;
83 struct ll_list token_list;
86 /* Ships the string STR to the driver. */
88 shipout (struct ll_list *list, const char *str, ...)
94 char *s = c_vasnprintf (NULL, &length, str, args);
102 static const struct output_driver_class tex_driver_class;
104 static void tex_output_table (struct tex_driver *, const struct table_item *);
106 static struct tex_driver *
107 tex_driver_cast (struct output_driver *driver)
109 assert (driver->class == &tex_driver_class);
110 return UP_CAST (driver, struct tex_driver, driver);
113 static struct driver_option *
114 opt (struct output_driver *d, struct string_map *options, const char *key,
115 const char *default_value)
117 return driver_option_get (d, options, key, default_value);
120 static struct output_driver *
121 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
122 struct string_map *o)
124 struct output_driver *d;
125 struct tex_driver *tex = XZALLOC (struct tex_driver);
126 hmap_init (&tex->macros);
127 ll_init (&tex->preamble_list);
128 ll_init (&tex->token_list);
131 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
134 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
135 fh_get_file_name (fh)));
138 tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
139 tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
142 tex->file = fn_open (tex->handle, "w");
143 if (tex->file == NULL)
145 msg_error (errno, _("error opening output file `%s'"),
146 fh_get_file_name (tex->handle));
153 output_driver_destroy (d);
158 /* Emit all the tokens in LIST to FILE.
159 Then destroy LIST and its contents. */
161 post_process_tokens (FILE *file, struct ll_list *list)
164 struct tex_token *tt;
165 struct tex_token *ttnext;
166 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
168 if (tt->cat == CAT_SPACE)
170 /* Count the number of characters up to the next space,
171 and if it'll not fit on to the line, then make a line
174 struct tex_token *prev_x = NULL;
175 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
178 struct tex_token *nt = ll_data (x, struct tex_token, ll);
179 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
181 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
183 word_len += ds_length (&nt->str);
187 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
195 line_len += ds_length (&tt->str);
196 if (tt->cat == CAT_EOL)
198 if (line_len >= TEX_LINE_MAX)
201 line_len = ds_length (&tt->str);
203 if (tt->cat == CAT_COMMENT)
205 fputs (ds_cstr (&tt->str), file);
206 ds_destroy (&tt->str);
213 tex_destroy (struct output_driver *driver)
215 struct tex_driver *tex = tex_driver_cast (driver);
217 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
218 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
219 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
221 char *ln = get_language ();
223 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
225 shipout (&tex->preamble_list, "\n");
227 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
228 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
229 shipout (&tex->preamble_list,
230 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
231 "\\advance\\hsize by \\psppcolumnspace"
232 "\\divide\\hsize by #1"
233 "\\noindent\\raggedright\\hskip0pt}\n\n");
236 shipout (&tex->preamble_list,
237 "%%%% Render the text centre justified\n"
238 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
239 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
240 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
241 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
245 shipout (&tex->preamble_list,
246 "%%%% Render the text right justified\n"
247 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
248 "\\parfillskip=0pt}\n");
249 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
250 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
253 /* Emit all the macro defintions. */
255 struct tex_macro *next;
256 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
258 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
259 shipout (&tex->preamble_list, "\n\n");
262 hmap_destroy (&tex->macros);
264 if (tex->require_graphics)
265 shipout (&tex->preamble_list, "\\input graphicx\n\n");
267 post_process_tokens (tex->file, &tex->preamble_list);
269 shipout (&tex->token_list, "\n\\bye\n");
271 post_process_tokens (tex->file, &tex->token_list);
273 fn_close (tex->handle, tex->file);
275 free (tex->chart_file_name);
276 fh_unref (tex->handle);
280 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
281 if TABULAR is true, then this text is within a table. */
283 tex_escape_string (struct tex_driver *tex, const char *text,
286 size_t n = strlen (text);
289 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
290 shipout (&tex->token_list, "%s", frag);
291 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
293 /* Peek ahead to the next code sequence */
295 const char *t = text;
296 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
297 /* If a period followed by whitespace is encountered within tabular
298 material, then it is reasonable to assume, that it is an
299 abbreviation (like "Sig." or "Std. Deviation") rather than the
300 end of a sentance. */
301 if (next && 0 == strcmp (" ", next))
303 shipout (&tex->token_list, "\\ ");
310 tex_submit (struct output_driver *driver,
311 const struct output_item *output_item)
313 struct tex_driver *tex = tex_driver_cast (driver);
315 if (is_table_item (output_item))
317 struct table_item *table_item = to_table_item (output_item);
318 tex_output_table (tex, table_item);
321 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
323 struct chart_item *chart_item = to_chart_item (output_item);
324 char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
328 if (file_name != NULL)
330 //const char *title = chart_item_get_title (chart_item);
331 // printf ("The chart title is %s\n", title);
333 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
334 tex->require_graphics = true;
338 #endif /* HAVE_CAIRO */
339 else if (is_text_item (output_item))
341 struct text_item *text_item = to_text_item (output_item);
342 const char *s = text_item_get_text (text_item);
344 switch (text_item_get_type (text_item))
346 case TEXT_ITEM_PAGE_TITLE:
347 shipout (&tex->token_list, "\\headline={\\bf ");
348 tex_escape_string (tex, s, false);
349 shipout (&tex->token_list, "\\hfil}\n");
353 shipout (&tex->token_list, "{\\tt ");
354 tex_escape_string (tex, s, false);
355 shipout (&tex->token_list, "}\\par\n\n");
358 case TEXT_ITEM_SYNTAX:
359 /* So far as I'm aware, this can never happen. */
361 printf ("Unhandled type %d\n", text_item_get_type (text_item));
365 else if (is_message_item (output_item))
367 const struct message_item *message_item = to_message_item (output_item);
368 char *s = msg_to_string (message_item_get_msg (message_item));
369 tex_escape_string (tex, s, false);
370 shipout (&tex->token_list, "\\par\n");
376 tex_put_footnote_markers (struct tex_driver *tex,
377 const struct footnote **footnotes,
381 shipout (&tex->token_list, "$^{");
382 for (size_t i = 0; i < n_footnotes; i++)
384 const struct footnote *f = footnotes[i];
386 tex_escape_string (tex, f->marker, true);
389 shipout (&tex->token_list, "}$");
393 tex_put_table_item_text (struct tex_driver *tex,
394 const struct table_item_text *text)
396 tex_escape_string (tex, text->content, false);
397 tex_put_footnote_markers (tex, text->footnotes, text->n_footnotes);
401 tex_output_table (struct tex_driver *tex, const struct table_item *item)
403 /* Tables are rendered in TeX with the \halign command.
404 This is described in the TeXbook Ch. 22 */
406 const struct table *t = table_item_get_table (item);
408 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
410 const struct table_item_text *caption = table_item_get_caption (item);
413 shipout (&tex->token_list, "{\\sl ");
414 tex_escape_string (tex, caption->content, false);
415 shipout (&tex->token_list, "}\n\n");
417 const struct footnote **f;
418 size_t n_footnotes = table_collect_footnotes (item, &f);
420 const struct table_item_text *title = table_item_get_title (item);
421 const struct table_item_layers *layers = table_item_get_layers (item);
426 shipout (&tex->token_list, "{\\bf ");
427 tex_put_table_item_text (tex, title);
428 shipout (&tex->token_list, "}");
432 shipout (&tex->token_list, "\\par\n");
435 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
437 /* Generate the preamble */
438 for (int x = 0; x < table_nc (t); ++x)
440 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", table_nc (t));
442 if (x < table_nc (t) - 1)
444 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
445 shipout (&tex->token_list, "&\\vrule\n");
448 shipout (&tex->token_list, "\\cr\n");
451 /* Emit the row data */
452 for (int y = 0; y < table_nr (t); y++)
454 bool is_column_header = (y < table_ht (t)
455 || y >= table_nr (t) - table_hb (t));
458 for (int x = 0; x < table_nc (t);)
460 struct table_cell cell;
462 table_get_cell (t, x, y, &cell);
464 int colspan = table_cell_colspan (&cell);
466 shipout (&tex->token_list, "&");
468 for (int i = 0; i < skipped - colspan; ++i)
469 shipout (&tex->token_list, "&");
472 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
475 /* bool is_header = (y < table_ht (t) */
476 /* || y >= table_nr (t) - table_hb (t) */
477 /* || x < table_hl (t) */
478 /* || x >= table_nc (t) - table_hr (t)); */
481 enum table_halign halign =
482 table_halign_interpret (cell.style->cell_style.halign,
483 cell.options & TAB_NUMERIC);
485 /* int rowspan = table_cell_rowspan (&cell); */
487 /* if (rowspan > 1) */
488 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
492 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
493 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
494 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
498 if (halign == TABLE_HALIGN_CENTER)
499 shipout (&tex->token_list, "\\centre{");
501 if (halign == TABLE_HALIGN_RIGHT)
502 shipout (&tex->token_list, "\\right{");
504 /* Output cell contents. */
505 tex_escape_string (tex, cell.text, true);
506 tex_put_footnote_markers (tex, cell.footnotes, cell.n_footnotes);
507 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
509 shipout (&tex->token_list, "}");
513 skipped = x - prev_x;
515 x = cell.d[TABLE_HORZ][1];
517 shipout (&tex->token_list, "\\cr\n");
518 if (is_column_header)
519 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
522 shipout (&tex->token_list, "}%% End of \\halign\n");
524 /* Shipout any footnotes. */
526 shipout (&tex->token_list, "\\vskip 0.5ex\n");
528 for (int i = 0; i < n_footnotes; ++i)
530 shipout (&tex->token_list, "$^{");
531 tex_escape_string (tex, f[i]->marker, false);
532 shipout (&tex->token_list, "}$");
533 tex_escape_string (tex, f[i]->content, false);
537 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
540 struct output_driver_factory tex_driver_factory =
541 { "tex", "pspp.tex", tex_create };
543 static const struct output_driver_class tex_driver_class =