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/image-item.h"
45 #include "output/message-item.h"
46 #include "output/options.h"
47 #include "output/output-item-provider.h"
48 #include "output/pivot-output.h"
49 #include "output/pivot-table.h"
50 #include "output/table-provider.h"
51 #include "output/table-item.h"
52 #include "output/text-item.h"
53 #include "output/tex-rendering.h"
54 #include "output/tex-parsing.h"
57 #include "tex-glyphs.h"
59 #include "gl/minmax.h"
60 #include "gl/xalloc.h"
61 #include "gl/c-vasnprintf.h"
64 #define _(msgid) gettext (msgid)
66 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
70 /* The desired maximum line length in the TeX file. */
71 #define TEX_LINE_MAX 80
75 struct output_driver driver;
76 /* A hash table containing any Tex macros which need to be emitted. */
78 bool require_graphics;
83 struct file_handle *handle;
84 char *chart_file_name;
89 struct ll_list preamble_list;
90 struct ll_list token_list;
93 /* Ships the string STR to the driver. */
95 shipout (struct ll_list *list, const char *str, ...)
101 char *s = c_vasnprintf (NULL, &length, str, args);
109 static const struct output_driver_class tex_driver_class;
111 static void tex_output_table (struct tex_driver *, const struct table_item *);
113 static struct tex_driver *
114 tex_driver_cast (struct output_driver *driver)
116 assert (driver->class == &tex_driver_class);
117 return UP_CAST (driver, struct tex_driver, driver);
120 static struct driver_option *
121 opt (struct output_driver *d, struct string_map *options, const char *key,
122 const char *default_value)
124 return driver_option_get (d, options, key, default_value);
127 static struct output_driver *
128 tex_create (struct file_handle *fh, enum settings_output_devices device_type,
129 struct string_map *o)
131 struct output_driver *d;
132 struct tex_driver *tex = XZALLOC (struct tex_driver);
133 hmap_init (&tex->macros);
134 ll_init (&tex->preamble_list);
135 ll_init (&tex->token_list);
138 output_driver_init (&tex->driver, &tex_driver_class, fh_get_file_name (fh),
141 tex->chart_file_name = parse_chart_file_name (opt (d, o, "charts",
142 fh_get_file_name (fh)));
145 tex->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
146 tex->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
149 tex->file = fn_open (tex->handle, "w");
150 if (tex->file == NULL)
152 msg_error (errno, _("error opening output file `%s'"),
153 fh_get_file_name (tex->handle));
160 output_driver_destroy (d);
165 /* Emit all the tokens in LIST to FILE.
166 Then destroy LIST and its contents. */
168 post_process_tokens (FILE *file, struct ll_list *list)
171 struct tex_token *tt;
172 struct tex_token *ttnext;
173 ll_for_each_safe (tt, ttnext, struct tex_token, ll, list)
175 if (tt->cat == CAT_SPACE)
177 /* Count the number of characters up to the next space,
178 and if it'll not fit on to the line, then make a line
181 struct tex_token *prev_x = NULL;
182 for (struct ll *x = ll_next (&tt->ll); x != ll_null (list);
185 struct tex_token *nt = ll_data (x, struct tex_token, ll);
186 if (nt->cat == CAT_SPACE || nt->cat == CAT_EOL)
188 if (prev_x && (prev_x->cat == CAT_COMMENT) && (nt->cat != CAT_COMMENT))
190 word_len += ds_length (&nt->str);
194 if ((word_len < TEX_LINE_MAX) && (line_len + word_len >= TEX_LINE_MAX - 1))
202 line_len += ds_length (&tt->str);
203 if (tt->cat == CAT_EOL)
205 if (line_len >= TEX_LINE_MAX)
208 line_len = ds_length (&tt->str);
210 if (tt->cat == CAT_COMMENT)
212 fputs (ds_cstr (&tt->str), file);
213 ds_destroy (&tt->str);
220 tex_destroy (struct output_driver *driver)
222 struct tex_driver *tex = tex_driver_cast (driver);
224 shipout (&tex->preamble_list, "%%%% TeX output of pspp\n\n");
225 shipout (&tex->preamble_list, "%%%% Define the horizontal space between table columns\n");
226 shipout (&tex->preamble_list, "\\def\\psppcolumnspace{1mm}\n\n");
228 char *ln = get_language ();
230 shipout (&tex->preamble_list, "%%%% Language is \"%s\"\n", ln);
232 shipout (&tex->preamble_list, "\n");
234 shipout (&tex->preamble_list, "%%%% Sets the environment for rendering material in table cell\n");
235 shipout (&tex->preamble_list, "%%%% The parameter is the number of columns in the table\n");
236 shipout (&tex->preamble_list,
237 "\\def\\cell#1{\\normalbaselines\\advance\\hsize by -#1.0\\psppcolumnspace"
238 "\\advance\\hsize by \\psppcolumnspace"
239 "\\divide\\hsize by #1"
240 "\\noindent\\raggedright\\hskip0pt}\n\n");
243 shipout (&tex->preamble_list,
244 "%%%% Render the text centre justified\n"
245 "\\def\\startcentre{\\begingroup\\leftskip=0pt plus 1fil\n"
246 "\\rightskip=\\leftskip\\parfillskip=0pt}\n");
247 shipout (&tex->preamble_list, "\\def\\stopcentre{\\par\\endgroup}\n");
248 shipout (&tex->preamble_list, "\\long\\def\\centre#1{\\startcentre#1\\stopcentre}\n\n");
252 shipout (&tex->preamble_list,
253 "%%%% Render the text right justified\n"
254 "\\def\\startright{\\begingroup\\leftskip=0pt plus 1fil\n"
255 "\\parfillskip=0pt}\n");
256 shipout (&tex->preamble_list, "\\def\\stopright{\\par\\endgroup}\n");
257 shipout (&tex->preamble_list, "\\long\\def\\right#1{\\startright#1\\stopright}\n\n");
260 /* Emit all the macro defintions. */
262 struct tex_macro *next;
263 HMAP_FOR_EACH_SAFE (m, next, struct tex_macro, node, &tex->macros)
265 shipout (&tex->preamble_list, "%s", tex_macro[m->index]);
266 shipout (&tex->preamble_list, "\n\n");
269 hmap_destroy (&tex->macros);
271 if (tex->require_graphics)
272 shipout (&tex->preamble_list, "\\input graphicx\n\n");
274 post_process_tokens (tex->file, &tex->preamble_list);
276 shipout (&tex->token_list, "\n\\bye\n");
278 post_process_tokens (tex->file, &tex->token_list);
280 fn_close (tex->handle, tex->file);
282 free (tex->chart_file_name);
283 fh_unref (tex->handle);
287 /* Ship out TEXT (which must be a UTF-8 encoded string to the driver's output.
288 if TABULAR is true, then this text is within a table. */
290 tex_escape_string (struct tex_driver *tex, const char *text,
293 size_t n = strlen (text);
296 const char *frag = u8_to_tex_fragments (&text, &n, &tex->macros);
297 shipout (&tex->token_list, "%s", frag);
298 if (text[0] != '\0' && tabular && 0 == strcmp (frag, "."))
300 /* Peek ahead to the next code sequence */
302 const char *t = text;
303 const char *next = u8_to_tex_fragments (&t, &nn, &tex->macros);
304 /* If a period followed by whitespace is encountered within tabular
305 material, then it is reasonable to assume, that it is an
306 abbreviation (like "Sig." or "Std. Deviation") rather than the
307 end of a sentance. */
308 if (next && 0 == strcmp (" ", next))
310 shipout (&tex->token_list, "\\ ");
317 tex_submit (struct output_driver *driver,
318 const struct output_item *output_item)
320 struct tex_driver *tex = tex_driver_cast (driver);
322 if (is_table_item (output_item))
324 struct table_item *table_item = to_table_item (output_item);
325 tex_output_table (tex, table_item);
328 else if (is_image_item (output_item) && tex->chart_file_name != NULL)
330 struct image_item *image_item = to_image_item (output_item);
331 char *file_name = xr_write_png_image (
332 image_item->image, tex->chart_file_name, tex->chart_cnt++);
333 if (file_name != NULL)
335 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
336 tex->require_graphics = true;
340 else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
342 struct chart_item *chart_item = to_chart_item (output_item);
343 char *file_name = xr_draw_png_chart (chart_item, tex->chart_file_name,
347 if (file_name != NULL)
349 //const char *title = chart_item_get_title (chart_item);
350 // printf ("The chart title is %s\n", title);
352 shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
353 tex->require_graphics = true;
357 #endif /* HAVE_CAIRO */
358 else if (is_text_item (output_item))
360 struct text_item *text_item = to_text_item (output_item);
361 const char *s = text_item_get_text (text_item);
363 switch (text_item_get_type (text_item))
365 case TEXT_ITEM_PAGE_TITLE:
366 shipout (&tex->token_list, "\\headline={\\bf ");
367 tex_escape_string (tex, s, false);
368 shipout (&tex->token_list, "\\hfil}\n");
372 shipout (&tex->token_list, "{\\tt ");
373 tex_escape_string (tex, s, false);
374 shipout (&tex->token_list, "}\\par\n\n");
377 case TEXT_ITEM_SYNTAX:
378 /* So far as I'm aware, this can never happen. */
380 printf ("Unhandled type %d\n", text_item_get_type (text_item));
384 else if (is_message_item (output_item))
386 const struct message_item *message_item = to_message_item (output_item);
387 char *s = msg_to_string (message_item_get_msg (message_item));
388 tex_escape_string (tex, s, false);
389 shipout (&tex->token_list, "\\par\n");
395 tex_put_footnote_markers (struct tex_driver *tex,
396 const struct pivot_table *pt,
397 const size_t *footnote_indexes,
401 shipout (&tex->token_list, "$^{");
402 for (size_t i = 0; i < n_footnotes; i++)
404 const struct pivot_footnote *f = pt->footnotes[footnote_indexes[i]];
405 char *marker = pivot_value_to_string (f->marker, pt);
406 tex_escape_string (tex, marker, true);
410 shipout (&tex->token_list, "}$");
414 tex_put_table_cell (struct tex_driver *tex, const struct pivot_table *pt,
415 const struct table_cell *cell)
417 struct string s = DS_EMPTY_INITIALIZER;
418 pivot_value_format_body (cell->value, pt, &s);
419 tex_escape_string (tex, ds_cstr (&s), false);
422 tex_put_footnote_markers (tex, pt,
423 cell->value->footnote_indexes,
424 cell->value->n_footnotes);
428 tex_output_table_layer (struct tex_driver *tex, const struct pivot_table *pt,
429 const size_t *layer_indexes)
431 /* Tables are rendered in TeX with the \halign command.
432 This is described in the TeXbook Ch. 22 */
433 struct table *title, *layers, *body, *caption;
434 struct pivot_footnote **footnotes;
436 pivot_output (pt, layer_indexes, true, &title, &layers, &body,
437 &caption, NULL, &footnotes, &n_footnotes);
439 shipout (&tex->token_list, "\n{\\parindent=0pt\n");
443 shipout (&tex->token_list, "{\\sl ");
444 struct table_cell cell;
445 table_get_cell (caption, 0, 0, &cell);
446 tex_put_table_cell (tex, pt, &cell);
447 shipout (&tex->token_list, "}\n\n");
454 shipout (&tex->token_list, "{\\bf ");
455 struct table_cell cell;
456 table_get_cell (title, 0, 0, &cell);
457 tex_put_table_cell (tex, pt, &cell);
458 shipout (&tex->token_list, "}\\par\n");
463 for (size_t y = 0; y < layers->n[V]; y++)
465 shipout (&tex->token_list, "{");
466 struct table_cell cell;
467 table_get_cell (layers, 0, y, &cell);
468 tex_put_table_cell (tex, pt, &cell);
469 shipout (&tex->token_list, "}\\par\n");
474 shipout (&tex->token_list, "\\offinterlineskip\\halign{\\strut%%\n");
476 /* Generate the preamble */
477 for (int x = 0; x < body->n[H]; ++x)
479 shipout (&tex->token_list, "{\\vbox{\\cell{%d}#}}", body->n[H]);
481 if (x < body->n[H] - 1)
483 shipout (&tex->token_list, "\\hskip\\psppcolumnspace\\hfil");
484 shipout (&tex->token_list, "&\\vrule\n");
487 shipout (&tex->token_list, "\\cr\n");
490 /* Emit the row data */
491 for (int y = 0; y < body->n[V]; y++)
493 enum { H = TABLE_HORZ, V = TABLE_VERT };
494 bool is_column_header = y < body->h[V][0] || y >= body->n[V] - body->h[V][1];
497 for (int x = 0; x < body->n[H];)
499 struct table_cell cell;
501 table_get_cell (body, x, y, &cell);
503 int colspan = table_cell_colspan (&cell);
505 shipout (&tex->token_list, "&");
507 for (int i = 0; i < skipped - colspan; ++i)
508 shipout (&tex->token_list, "&");
511 if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
514 /* bool is_header = (y < body->h[V][0] */
515 /* || y >= body->n[V] - body->h[V][1] */
516 /* || x < body->h[H][0] */
517 /* || x >= body->n[H] - body->h[H][1]); */
519 struct string s = DS_EMPTY_INITIALIZER;
520 bool numeric = pivot_value_format_body (cell.value, pt, &s);
522 enum table_halign halign = table_halign_interpret (
523 cell.cell_style->halign, numeric);
525 /* int rowspan = table_cell_rowspan (&cell); */
527 /* if (rowspan > 1) */
528 /* fprintf (tex->file, " rowspan=\"%d\"", rowspan); */
532 shipout (&tex->token_list, "\\multispan{%d}\\span", colspan - 1);
533 shipout (&tex->token_list, "\\hsize=%d.0\\hsize", colspan);
534 shipout (&tex->token_list, "\\advance\\hsize%d.0\\psppcolumnspace ",
538 if (halign == TABLE_HALIGN_CENTER)
539 shipout (&tex->token_list, "\\centre{");
541 if (halign == TABLE_HALIGN_RIGHT)
542 shipout (&tex->token_list, "\\right{");
544 /* Output cell contents. */
545 tex_escape_string (tex, ds_cstr (&s), true);
548 tex_put_footnote_markers (tex, pt, cell.value->footnote_indexes,
549 cell.value->n_footnotes);
550 if (halign == TABLE_HALIGN_CENTER || halign == TABLE_HALIGN_RIGHT)
552 shipout (&tex->token_list, "}");
556 skipped = x - prev_x;
558 x = cell.d[TABLE_HORZ][1];
560 shipout (&tex->token_list, "\\cr\n");
561 if (is_column_header)
562 shipout (&tex->token_list, "\\noalign{\\hrule\\vskip -\\normalbaselineskip}\\cr\n");
565 shipout (&tex->token_list, "}%% End of \\halign\n");
567 /* Shipout any footnotes. */
569 shipout (&tex->token_list, "\\vskip 0.5ex\n");
571 for (int i = 0; i < n_footnotes; ++i)
573 char *marker = pivot_value_to_string (footnotes[i]->marker, pt);
574 char *content = pivot_value_to_string (footnotes[i]->content, pt);
576 shipout (&tex->token_list, "$^{");
577 tex_escape_string (tex, marker, false);
578 shipout (&tex->token_list, "}$");
579 tex_escape_string (tex, content, false);
585 shipout (&tex->token_list, "}\n\\vskip 3ex\n\n");
588 table_unref (layers);
590 table_unref (caption);
595 tex_output_table (struct tex_driver *tex, const struct table_item *item)
597 size_t *layer_indexes;
598 PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->pt, true)
599 tex_output_table_layer (tex, item->pt, layer_indexes);
602 struct output_driver_factory tex_driver_factory =
603 { "tex", "pspp.tex", tex_create };
605 static const struct output_driver_class tex_driver_class =