1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 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/>. */
30 #include "data/file-name.h"
31 #include "data/file-handle-def.h"
32 #include "data/settings.h"
33 #include "libpspp/assertion.h"
34 #include "libpspp/cast.h"
35 #include "libpspp/compiler.h"
36 #include "libpspp/message.h"
37 #include "libpspp/start-date.h"
38 #include "libpspp/string-map.h"
39 #include "libpspp/u8-line.h"
40 #include "libpspp/version.h"
41 #include "output/ascii.h"
42 #include "output/cairo.h"
43 #include "output/chart-item-provider.h"
44 #include "output/driver-provider.h"
45 #include "output/message-item.h"
46 #include "output/options.h"
47 #include "output/pivot-table.h"
48 #include "output/render.h"
49 #include "output/table-item.h"
50 #include "output/text-item.h"
52 #include "gl/minmax.h"
53 #include "gl/xalloc.h"
57 #define _(msgid) gettext (msgid)
59 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
71 #define N_BOX (ASCII_N_LINES * ASCII_N_LINES \
72 * ASCII_N_LINES * ASCII_N_LINES)
74 static const ucs4_t ascii_box_chars[N_BOX] =
105 static const ucs4_t unicode_box_chars[N_BOX] =
107 0x0020, 0x2575, 0x2551,
108 0x2574, 0x256f, 0x255c,
109 0x2550, 0x255b, 0x255d,
110 0x2577, 0x2502, 0x2551,
111 0x256e, 0x2524, 0x2562,
112 0x2555, 0x2561, 0x2563,
113 0x2551, 0x2551, 0x2551,
114 0x2556, 0x2562, 0x2562,
115 0x2557, 0x2563, 0x2563,
116 0x2576, 0x2570, 0x2559,
117 0x2500, 0x2534, 0x2568,
118 0x2550, 0x2567, 0x2569,
119 0x256d, 0x251c, 0x255f,
120 0x252c, 0x253c, 0x256a,
121 0x2564, 0x256a, 0x256c,
122 0x2553, 0x255f, 0x255f,
123 0x2565, 0x256b, 0x256b,
124 0x2566, 0x256c, 0x256c,
125 0x2550, 0x2558, 0x255a,
126 0x2550, 0x2567, 0x2569,
127 0x2550, 0x2567, 0x2569,
128 0x2552, 0x255e, 0x2560,
129 0x2564, 0x256a, 0x256c,
130 0x2564, 0x256a, 0x256c,
131 0x2554, 0x2560, 0x2560,
132 0x2560, 0x256c, 0x256c,
133 0x2566, 0x256c, 0x256c,
137 ascii_line_from_render_line (int render_line)
141 case RENDER_LINE_NONE:
142 return ASCII_LINE_NONE;
144 case RENDER_LINE_SINGLE:
145 case RENDER_LINE_DASHED:
146 case RENDER_LINE_THICK:
147 case RENDER_LINE_THIN:
148 return ASCII_LINE_SINGLE;
150 case RENDER_LINE_DOUBLE:
151 return ASCII_LINE_DOUBLE;
154 return ASCII_LINE_NONE;
160 make_box_index (int left_, int right_, int top_, int bottom_)
162 bool rtl = render_direction_rtl ();
163 int left = ascii_line_from_render_line (rtl ? right_ : left_);
164 int right = ascii_line_from_render_line (rtl ? left_ : right_);
165 int top = ascii_line_from_render_line (top_);
166 int bottom = ascii_line_from_render_line (bottom_);
169 idx = idx * ASCII_N_LINES + bottom;
170 idx = idx * ASCII_N_LINES + left;
171 idx = idx * ASCII_N_LINES + top;
175 /* ASCII output driver. */
178 struct output_driver driver;
180 /* User parameters. */
181 bool append; /* Append if output file already exists? */
182 bool emphasis; /* Enable bold and underline in output? */
183 char *chart_file_name; /* Name of files used for charts. */
186 /* Colours for charts */
191 int width; /* Page width. */
192 bool auto_width; /* Use viewwidth as page width? */
194 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
196 const ucs4_t *box; /* Line & box drawing characters. */
198 /* Internal state. */
199 struct file_handle *handle;
200 FILE *file; /* Output file. */
201 bool error; /* Output error? */
202 struct u8_line *lines; /* Page content. */
203 int allocated_lines; /* Number of lines allocated. */
204 int chart_cnt; /* Number of charts so far. */
205 struct render_params params;
208 static const struct output_driver_class ascii_driver_class;
210 static void ascii_submit (struct output_driver *, const struct output_item *);
212 static bool update_page_size (struct ascii_driver *, bool issue_error);
213 static int parse_page_size (struct driver_option *);
215 static bool ascii_open_page (struct ascii_driver *);
217 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
218 enum render_line_style styles[TABLE_N_AXES][2],
219 struct cell_color colors[TABLE_N_AXES][2]);
220 static void ascii_measure_cell_width (void *, const struct table_cell *,
222 static int ascii_measure_cell_height (void *, const struct table_cell *,
224 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
225 int bb[TABLE_N_AXES][2],
226 int spill[TABLE_N_AXES][2],
227 int clip[TABLE_N_AXES][2]);
229 static struct ascii_driver *
230 ascii_driver_cast (struct output_driver *driver)
232 assert (driver->class == &ascii_driver_class);
233 return UP_CAST (driver, struct ascii_driver, driver);
236 static struct driver_option *
237 opt (struct output_driver *d, struct string_map *options, const char *key,
238 const char *default_value)
240 return driver_option_get (d, options, key, default_value);
243 /* Return true iff the terminal appears to be an xterm with
244 UTF-8 capabilities */
246 term_is_utf8_xterm (void)
248 const char *term = getenv ("TERM");
249 const char *xterm_locale = getenv ("XTERM_LOCAL");
250 return (term && xterm_locale
251 && !strcmp (term, "xterm")
252 && (strcasestr (xterm_locale, "utf8")
253 || strcasestr (xterm_locale, "utf-8")));
256 static struct output_driver *
257 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
258 struct string_map *o)
260 enum { BOX_ASCII, BOX_UNICODE } box;
261 int min_break[TABLE_N_AXES];
262 struct output_driver *d;
263 struct ascii_driver *a;
265 a = xzalloc (sizeof *a);
267 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
268 a->append = parse_boolean (opt (d, o, "append", "false"));
269 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
271 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
274 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
276 a->width = parse_page_size (opt (d, o, "width", "79"));
277 a->auto_width = a->width < 0;
278 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
280 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
281 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
284 const char *default_box = (!strcmp (fh_get_file_name (fh), "-")
285 && isatty (STDOUT_FILENO)
286 && (!strcmp (locale_charset (), "UTF-8")
287 || term_is_utf8_xterm ())
288 ? "unicode" : "ascii");
289 box = parse_enum (opt (d, o, "box", default_box),
291 "unicode", BOX_UNICODE,
293 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
298 a->allocated_lines = 0;
301 a->params.draw_line = ascii_draw_line;
302 a->params.measure_cell_width = ascii_measure_cell_width;
303 a->params.measure_cell_height = ascii_measure_cell_height;
304 a->params.adjust_break = NULL;
305 a->params.draw_cell = ascii_draw_cell;
307 a->params.size[H] = a->width;
308 a->params.size[V] = INT_MAX;
309 a->params.font_size[H] = 1;
310 a->params.font_size[V] = 1;
311 for (int i = 0; i < RENDER_N_LINES; i++)
313 int width = i == RENDER_LINE_NONE ? 0 : 1;
314 a->params.line_widths[H][i] = width;
315 a->params.line_widths[V][i] = width;
317 for (int i = 0; i < TABLE_N_AXES; i++)
318 a->params.min_break[i] = a->min_break[i];
319 a->params.supports_margins = false;
320 a->params.rtl = render_direction_rtl ();
322 if (!update_page_size (a, true))
328 output_driver_destroy (d);
333 parse_page_size (struct driver_option *option)
335 int dim = atol (option->default_value);
337 if (option->value != NULL)
339 if (!strcmp (option->value, "auto"))
347 value = strtol (option->value, &tail, 0);
348 if (dim >= 1 && errno != ERANGE && *tail == '\0')
351 msg (MW, _("%s: %s must be positive integer or `auto'"),
352 option->driver_name, option->name);
356 driver_option_destroy (option);
361 /* Re-calculates the page width based on settings, margins, and, if "auto" is
362 set, the size of the user's terminal window or GUI output window. */
364 update_page_size (struct ascii_driver *a, bool issue_error)
366 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
370 a->params.size[H] = a->width = settings_get_viewwidth ();
371 a->params.min_break[H] = a->min_break[H] = a->width / 2;
374 if (a->width < MIN_WIDTH)
378 _("ascii: page must be at least %d characters wide, but "
379 "as configured is only %d characters"),
382 if (a->width < MIN_WIDTH)
383 a->params.size[H] = a->width = MIN_WIDTH;
391 ascii_destroy (struct output_driver *driver)
393 struct ascii_driver *a = ascii_driver_cast (driver);
397 fn_close (a->handle, a->file);
398 fh_unref (a->handle);
399 free (a->chart_file_name);
400 for (i = 0; i < a->allocated_lines; i++)
401 u8_line_destroy (&a->lines[i]);
407 ascii_flush (struct output_driver *driver)
409 struct ascii_driver *a = ascii_driver_cast (driver);
415 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
417 for (size_t y = 0; y < n_lines; y++)
419 struct u8_line *line = &a->lines[y];
421 while (ds_chomp_byte (&line->s, ' '))
423 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
424 putc ('\n', a->file);
426 u8_line_clear (&a->lines[y]);
431 ascii_output_table_item (struct ascii_driver *a,
432 const struct table_item *table_item)
434 struct render_pager *p;
436 update_page_size (a, false);
439 putc ('\n', a->file);
440 else if (!ascii_open_page (a))
443 p = render_pager_create (&a->params, table_item);
444 for (int i = 0; render_pager_has_next (p); i++)
447 putc ('\n', a->file);
448 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
450 render_pager_destroy (p);
454 ascii_output_table_item_unref (struct ascii_driver *a,
455 struct table_item *table_item)
457 ascii_output_table_item (a, table_item);
458 table_item_unref (table_item);
462 ascii_output_text (struct ascii_driver *a, const char *text)
464 struct pivot_table *pt = pivot_table_create_for_text (
465 NULL, pivot_value_new_user_text (text, -1));
466 ascii_output_table_item_unref (a, table_item_create (pt));
470 ascii_submit (struct output_driver *driver,
471 const struct output_item *output_item)
473 struct ascii_driver *a = ascii_driver_cast (driver);
478 if (is_table_item (output_item))
479 ascii_output_table_item (a, to_table_item (output_item));
481 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
483 struct chart_item *chart_item = to_chart_item (output_item);
486 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
490 if (file_name != NULL)
492 struct text_item *text_item;
494 text_item = text_item_create_format (
495 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
497 ascii_submit (driver, &text_item->output_item);
498 text_item_unref (text_item);
502 #endif /* HAVE_CAIRO */
503 else if (is_text_item (output_item))
505 const struct text_item *text_item = to_text_item (output_item);
506 enum text_item_type type = text_item_get_type (text_item);
510 case TEXT_ITEM_PAGE_TITLE:
511 case TEXT_ITEM_BLANK_LINE:
514 case TEXT_ITEM_EJECT_PAGE:
518 ascii_output_table_item_unref (
519 a, text_item_to_table_item (text_item_ref (text_item)));
523 else if (is_message_item (output_item))
525 const struct message_item *message_item = to_message_item (output_item);
526 char *s = msg_to_string (message_item_get_msg (message_item));
527 ascii_output_text (a, s);
532 const struct output_driver_factory txt_driver_factory =
533 { "txt", "-", ascii_create };
534 const struct output_driver_factory list_driver_factory =
535 { "list", "-", ascii_create };
537 static const struct output_driver_class ascii_driver_class =
545 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
547 static void ascii_layout_cell (struct ascii_driver *,
548 const struct table_cell *,
549 int bb[TABLE_N_AXES][2],
550 int clip[TABLE_N_AXES][2],
551 int *width, int *height);
554 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
555 enum render_line_style styles[TABLE_N_AXES][2],
556 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
558 struct ascii_driver *a = a_;
565 /* Clip to the page. */
566 x0 = MAX (bb[H][0], 0);
567 y0 = MAX (bb[V][0], 0);
568 x1 = MIN (bb[H][1], a->width);
570 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
574 uc = a->box[make_box_index (styles[V][0], styles[V][1],
575 styles[H][0], styles[H][1])];
576 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
577 for (y = y0; y < y1; y++)
579 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
580 for (x = x0; x < x1; x++)
582 memcpy (p, mbchar, mblen);
589 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
590 int *min_width, int *max_width)
592 struct ascii_driver *a = a_;
593 int bb[TABLE_N_AXES][2];
594 int clip[TABLE_N_AXES][2];
601 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
602 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
604 if (cell->n_footnotes || strchr (cell->text, ' '))
607 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
610 *min_width = *max_width;
614 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
616 struct ascii_driver *a = a_;
617 int bb[TABLE_N_AXES][2];
618 int clip[TABLE_N_AXES][2];
625 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
626 ascii_layout_cell (a, cell, bb, clip, &w, &h);
631 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
632 int bb[TABLE_N_AXES][2],
633 int spill[TABLE_N_AXES][2] UNUSED,
634 int clip[TABLE_N_AXES][2])
636 struct ascii_driver *a = a_;
639 ascii_layout_cell (a, cell, bb, clip, &w, &h);
643 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
645 if (y >= a->allocated_lines)
647 size_t new_alloc = MAX (25, a->allocated_lines);
648 while (new_alloc <= y)
649 new_alloc = xtimes (new_alloc, 2);
650 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
651 for (size_t i = a->allocated_lines; i < new_alloc; i++)
652 u8_line_init (&a->lines[i]);
653 a->allocated_lines = new_alloc;
655 return u8_line_reserve (&a->lines[y], x0, x1, n);
659 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
660 bool bold, bool underline,
661 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
662 int y, const uint8_t *string, int n, size_t width)
664 int x0 = MAX (0, clip[H][0]);
665 int y0 = MAX (0, clip[V][0]);
666 int x1 = MIN (a->width, clip[H][1]);
670 if (y < y0 || y >= y1)
673 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
675 case TABLE_HALIGN_LEFT:
678 case TABLE_HALIGN_CENTER:
679 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
681 case TABLE_HALIGN_RIGHT:
682 case TABLE_HALIGN_DECIMAL:
683 x = bb[H][1] - width;
699 mblen = u8_mbtouc (&uc, string, n);
704 w = uc_width (uc, "UTF-8");
719 for (ofs = 0; ofs < n; )
725 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
727 w = uc_width (uc, "UTF-8");
730 if (width + w > x1 - x)
741 if (!a->emphasis || (!bold && !underline))
742 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
750 /* First figure out how many bytes need to be inserted. */
752 for (ofs = 0; ofs < n; ofs += mblen)
757 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
758 w = uc_width (uc, "UTF-8");
769 /* Then insert them. */
770 out = ascii_reserve (a, y, x, x + width, n_out);
771 for (ofs = 0; ofs < n; ofs += mblen)
776 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
777 w = uc_width (uc, "UTF-8");
783 out = mempcpy (out, string + ofs, mblen);
792 out = mempcpy (out, string + ofs, mblen);
798 add_footnote_markers (const char *text, const struct table_cell *cell)
800 struct string s = DS_EMPTY_INITIALIZER;
801 ds_put_cstr (&s, text);
802 for (size_t i = 0; i < cell->n_footnotes; i++)
803 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
804 return ds_steal_cstr (&s);
808 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
809 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
810 int *widthp, int *heightp)
815 /* Get the basic textual contents. */
816 const char *plain_text = (cell->options & TAB_MARKUP
817 ? output_get_text_from_markup (cell->text)
820 /* Append footnote markers if any. */
822 if (cell->n_footnotes)
824 text = add_footnote_markers (plain_text, cell);
825 if (plain_text != cell->text)
826 free (CONST_CAST (char *, plain_text));
831 /* Calculate length; if it's zero, then there's nothing to do. */
832 size_t length = strlen (text);
835 if (text != cell->text)
836 free (CONST_CAST (char *, text));
840 char *breaks = xmalloc (length + 1);
841 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
843 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
844 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
847 int bb_width = bb[H][1] - bb[H][0];
848 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
850 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
851 const char *b = breaks + pos;
852 size_t n = length - pos;
854 size_t last_break_ofs = 0;
855 int last_break_width = 0;
860 for (ofs = 0; ofs < n; )
866 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
867 if (b[ofs] == UC_BREAK_MANDATORY)
869 else if (b[ofs] == UC_BREAK_POSSIBLE)
871 last_break_ofs = ofs;
872 last_break_width = width;
875 w = uc_width (uc, "UTF-8");
878 if (width + w > bb_width)
880 if (isspace (line[ofs]))
882 else if (last_break_ofs != 0)
884 ofs = last_break_ofs;
885 width = last_break_width;
894 /* Trim any trailing spaces off the end of the text to be drawn. */
895 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
896 if (!isspace (line[graph_ofs - 1]))
898 width -= ofs - graph_ofs;
901 text_draw (a, cell->style->cell_style.halign, cell->options,
902 cell->style->font_style.bold,
903 cell->style->font_style.underline,
904 bb, clip, y, line, graph_ofs, width);
906 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
907 past any spaces past the end of the line (but not past a new-line). */
908 if (b[ofs] == UC_BREAK_MANDATORY)
911 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
921 if (text != cell->text)
922 free (CONST_CAST (char *, text));
926 ascii_test_write (struct output_driver *driver,
927 const char *s, int x, int y, bool bold, bool underline)
929 struct ascii_driver *a = ascii_driver_cast (driver);
930 int bb[TABLE_N_AXES][2];
933 if (a->file == NULL && !ascii_open_page (a))
936 struct area_style style = {
937 .cell_style.halign = TABLE_HALIGN_LEFT,
938 .font_style.bold = bold,
939 .font_style.underline = underline,
941 struct table_cell cell = {
942 .text = CONST_CAST (char *, s),
946 bb[TABLE_HORZ][0] = x;
947 bb[TABLE_HORZ][1] = a->width;
948 bb[TABLE_VERT][0] = y;
949 bb[TABLE_VERT][1] = INT_MAX;
951 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
955 ascii_test_set_length (struct output_driver *driver, int y, int length)
957 struct ascii_driver *a = ascii_driver_cast (driver);
959 if (a->file == NULL && !ascii_open_page (a))
964 u8_line_set_length (&a->lines[y], length);
968 ascii_test_flush (struct output_driver *driver)
970 struct ascii_driver *a = ascii_driver_cast (driver);
972 for (size_t i = a->allocated_lines; i-- > 0; )
973 if (a->lines[i].width)
975 ascii_output_lines (a, i + 1);
980 /* ascii_close_page () and support routines. */
982 #if HAVE_DECL_SIGWINCH
983 static struct ascii_driver *the_driver;
986 winch_handler (int signum UNUSED)
988 update_page_size (the_driver, false);
993 ascii_open_page (struct ascii_driver *a)
1000 a->file = fn_open (a->handle, a->append ? "a" : "w");
1001 if (a->file != NULL)
1003 if ( isatty (fileno (a->file)))
1005 #if HAVE_DECL_SIGWINCH
1006 struct sigaction action;
1007 sigemptyset (&action.sa_mask);
1008 action.sa_flags = 0;
1009 action.sa_handler = winch_handler;
1011 sigaction (SIGWINCH, &action, NULL);
1013 a->auto_width = true;
1018 msg_error (errno, _("ascii: opening output file `%s'"),
1019 fh_get_file_name (a->handle));