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/render.h"
48 #include "output/table-item.h"
49 #include "output/text-item.h"
51 #include "gl/minmax.h"
52 #include "gl/xalloc.h"
56 #define _(msgid) gettext (msgid)
58 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
70 #define N_BOX (ASCII_N_LINES * ASCII_N_LINES \
71 * ASCII_N_LINES * ASCII_N_LINES)
73 static const ucs4_t ascii_box_chars[N_BOX] =
104 static const ucs4_t unicode_box_chars[N_BOX] =
106 0x0020, 0x2575, 0x2551,
107 0x2574, 0x256f, 0x255c,
108 0x2550, 0x255b, 0x255d,
109 0x2577, 0x2502, 0x2551,
110 0x256e, 0x2524, 0x2562,
111 0x2555, 0x2561, 0x2563,
112 0x2551, 0x2551, 0x2551,
113 0x2556, 0x2562, 0x2562,
114 0x2557, 0x2563, 0x2563,
115 0x2576, 0x2570, 0x2559,
116 0x2500, 0x2534, 0x2568,
117 0x2550, 0x2567, 0x2569,
118 0x256d, 0x251c, 0x255f,
119 0x252c, 0x253c, 0x256a,
120 0x2564, 0x256a, 0x256c,
121 0x2553, 0x255f, 0x255f,
122 0x2565, 0x256b, 0x256b,
123 0x2566, 0x256c, 0x256c,
124 0x2550, 0x2558, 0x255a,
125 0x2550, 0x2567, 0x2569,
126 0x2550, 0x2567, 0x2569,
127 0x2552, 0x255e, 0x2560,
128 0x2564, 0x256a, 0x256c,
129 0x2564, 0x256a, 0x256c,
130 0x2554, 0x2560, 0x2560,
131 0x2560, 0x256c, 0x256c,
132 0x2566, 0x256c, 0x256c,
136 ascii_line_from_render_line (int render_line)
140 case RENDER_LINE_NONE:
141 return ASCII_LINE_NONE;
143 case RENDER_LINE_SINGLE:
144 case RENDER_LINE_DASHED:
145 case RENDER_LINE_THICK:
146 case RENDER_LINE_THIN:
147 return ASCII_LINE_SINGLE;
149 case RENDER_LINE_DOUBLE:
150 return ASCII_LINE_DOUBLE;
153 return ASCII_LINE_NONE;
159 make_box_index (int left_, int right_, int top_, int bottom_)
161 bool rtl = render_direction_rtl ();
162 int left = ascii_line_from_render_line (rtl ? right_ : left_);
163 int right = ascii_line_from_render_line (rtl ? left_ : right_);
164 int top = ascii_line_from_render_line (top_);
165 int bottom = ascii_line_from_render_line (bottom_);
168 idx = idx * ASCII_N_LINES + bottom;
169 idx = idx * ASCII_N_LINES + left;
170 idx = idx * ASCII_N_LINES + top;
174 /* ASCII output driver. */
177 struct output_driver driver;
179 /* User parameters. */
180 bool append; /* Append if output file already exists? */
181 bool emphasis; /* Enable bold and underline in output? */
182 char *chart_file_name; /* Name of files used for charts. */
185 /* Colours for charts */
190 int width; /* Page width. */
191 bool auto_width; /* Use viewwidth as page width? */
193 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
195 const ucs4_t *box; /* Line & box drawing characters. */
197 /* Internal state. */
198 struct file_handle *handle;
199 FILE *file; /* Output file. */
200 bool error; /* Output error? */
201 struct u8_line *lines; /* Page content. */
202 int allocated_lines; /* Number of lines allocated. */
203 int chart_cnt; /* Number of charts so far. */
204 struct render_params params;
207 static const struct output_driver_class ascii_driver_class;
209 static void ascii_submit (struct output_driver *, const struct output_item *);
211 static bool update_page_size (struct ascii_driver *, bool issue_error);
212 static int parse_page_size (struct driver_option *);
214 static bool ascii_open_page (struct ascii_driver *);
216 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
217 enum render_line_style styles[TABLE_N_AXES][2],
218 struct cell_color colors[TABLE_N_AXES][2]);
219 static void ascii_measure_cell_width (void *, const struct table_cell *,
221 static int ascii_measure_cell_height (void *, const struct table_cell *,
223 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
224 int bb[TABLE_N_AXES][2],
225 int spill[TABLE_N_AXES][2],
226 int clip[TABLE_N_AXES][2]);
228 static struct ascii_driver *
229 ascii_driver_cast (struct output_driver *driver)
231 assert (driver->class == &ascii_driver_class);
232 return UP_CAST (driver, struct ascii_driver, driver);
235 static struct driver_option *
236 opt (struct output_driver *d, struct string_map *options, const char *key,
237 const char *default_value)
239 return driver_option_get (d, options, key, default_value);
242 /* Return true iff the terminal appears to be an xterm with
243 UTF-8 capabilities */
245 term_is_utf8_xterm (void)
247 const char *term = getenv ("TERM");
248 const char *xterm_locale = getenv ("XTERM_LOCAL");
249 return (term && xterm_locale
250 && !strcmp (term, "xterm")
251 && (strcasestr (xterm_locale, "utf8")
252 || strcasestr (xterm_locale, "utf-8")));
255 static struct output_driver *
256 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
257 struct string_map *o)
259 enum { BOX_ASCII, BOX_UNICODE } box;
260 int min_break[TABLE_N_AXES];
261 struct output_driver *d;
262 struct ascii_driver *a;
264 a = xzalloc (sizeof *a);
266 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
267 a->append = parse_boolean (opt (d, o, "append", "false"));
268 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
270 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
273 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
275 a->width = parse_page_size (opt (d, o, "width", "79"));
276 a->auto_width = a->width < 0;
277 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
279 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
280 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
283 const char *default_box = (!strcmp (fh_get_file_name (fh), "-")
284 && isatty (STDOUT_FILENO)
285 && (!strcmp (locale_charset (), "UTF-8")
286 || term_is_utf8_xterm ())
287 ? "unicode" : "ascii");
288 box = parse_enum (opt (d, o, "box", default_box),
290 "unicode", BOX_UNICODE,
292 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
297 a->allocated_lines = 0;
300 a->params.draw_line = ascii_draw_line;
301 a->params.measure_cell_width = ascii_measure_cell_width;
302 a->params.measure_cell_height = ascii_measure_cell_height;
303 a->params.adjust_break = NULL;
304 a->params.draw_cell = ascii_draw_cell;
306 a->params.size[H] = a->width;
307 a->params.size[V] = INT_MAX;
308 a->params.font_size[H] = 1;
309 a->params.font_size[V] = 1;
310 for (int i = 0; i < RENDER_N_LINES; i++)
312 int width = i == RENDER_LINE_NONE ? 0 : 1;
313 a->params.line_widths[H][i] = width;
314 a->params.line_widths[V][i] = width;
316 for (int i = 0; i < TABLE_N_AXES; i++)
317 a->params.min_break[i] = a->min_break[i];
318 a->params.supports_margins = false;
319 a->params.rtl = render_direction_rtl ();
321 if (!update_page_size (a, true))
327 output_driver_destroy (d);
332 parse_page_size (struct driver_option *option)
334 int dim = atol (option->default_value);
336 if (option->value != NULL)
338 if (!strcmp (option->value, "auto"))
346 value = strtol (option->value, &tail, 0);
347 if (dim >= 1 && errno != ERANGE && *tail == '\0')
350 msg (MW, _("%s: %s must be positive integer or `auto'"),
351 option->driver_name, option->name);
355 driver_option_destroy (option);
360 /* Re-calculates the page width based on settings, margins, and, if "auto" is
361 set, the size of the user's terminal window or GUI output window. */
363 update_page_size (struct ascii_driver *a, bool issue_error)
365 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
369 a->params.size[H] = a->width = settings_get_viewwidth ();
370 a->params.min_break[H] = a->min_break[H] = a->width / 2;
373 if (a->width < MIN_WIDTH)
377 _("ascii: page must be at least %d characters wide, but "
378 "as configured is only %d characters"),
381 if (a->width < MIN_WIDTH)
382 a->params.size[H] = a->width = MIN_WIDTH;
390 ascii_destroy (struct output_driver *driver)
392 struct ascii_driver *a = ascii_driver_cast (driver);
396 fn_close (a->handle, a->file);
397 fh_unref (a->handle);
398 free (a->chart_file_name);
399 for (i = 0; i < a->allocated_lines; i++)
400 u8_line_destroy (&a->lines[i]);
406 ascii_flush (struct output_driver *driver)
408 struct ascii_driver *a = ascii_driver_cast (driver);
414 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
416 for (size_t y = 0; y < n_lines; y++)
418 struct u8_line *line = &a->lines[y];
420 while (ds_chomp_byte (&line->s, ' '))
422 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
423 putc ('\n', a->file);
425 u8_line_clear (&a->lines[y]);
430 ascii_output_table_item (struct ascii_driver *a,
431 const struct table_item *table_item)
433 struct render_pager *p;
435 update_page_size (a, false);
438 putc ('\n', a->file);
439 else if (!ascii_open_page (a))
442 p = render_pager_create (&a->params, table_item);
443 for (int i = 0; render_pager_has_next (p); i++)
446 putc ('\n', a->file);
447 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
449 render_pager_destroy (p);
453 ascii_output_table_item_unref (struct ascii_driver *a,
454 struct table_item *table_item)
456 ascii_output_table_item (a, table_item);
457 table_item_unref (table_item);
461 ascii_output_text (struct ascii_driver *a, const char *text)
463 ascii_output_table_item_unref (
464 a, table_item_create (table_from_string (TABLE_HALIGN_LEFT, text),
469 ascii_submit (struct output_driver *driver,
470 const struct output_item *output_item)
472 struct ascii_driver *a = ascii_driver_cast (driver);
477 if (is_table_item (output_item))
478 ascii_output_table_item (a, to_table_item (output_item));
480 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
482 struct chart_item *chart_item = to_chart_item (output_item);
485 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
489 if (file_name != NULL)
491 struct text_item *text_item;
493 text_item = text_item_create_format (
494 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
496 ascii_submit (driver, &text_item->output_item);
497 text_item_unref (text_item);
501 #endif /* HAVE_CAIRO */
502 else if (is_text_item (output_item))
504 const struct text_item *text_item = to_text_item (output_item);
505 enum text_item_type type = text_item_get_type (text_item);
509 case TEXT_ITEM_PAGE_TITLE:
510 case TEXT_ITEM_BLANK_LINE:
513 case TEXT_ITEM_EJECT_PAGE:
517 ascii_output_table_item_unref (
518 a, text_item_to_table_item (text_item_ref (text_item)));
522 else if (is_message_item (output_item))
524 const struct message_item *message_item = to_message_item (output_item);
525 char *s = msg_to_string (message_item_get_msg (message_item));
526 ascii_output_text (a, s);
531 const struct output_driver_factory txt_driver_factory =
532 { "txt", "-", ascii_create };
533 const struct output_driver_factory list_driver_factory =
534 { "list", "-", ascii_create };
536 static const struct output_driver_class ascii_driver_class =
544 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
546 static void ascii_layout_cell (struct ascii_driver *,
547 const struct table_cell *,
548 int bb[TABLE_N_AXES][2],
549 int clip[TABLE_N_AXES][2],
550 int *width, int *height);
553 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
554 enum render_line_style styles[TABLE_N_AXES][2],
555 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
557 struct ascii_driver *a = a_;
564 /* Clip to the page. */
565 x0 = MAX (bb[H][0], 0);
566 y0 = MAX (bb[V][0], 0);
567 x1 = MIN (bb[H][1], a->width);
569 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
573 uc = a->box[make_box_index (styles[V][0], styles[V][1],
574 styles[H][0], styles[H][1])];
575 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
576 for (y = y0; y < y1; y++)
578 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
579 for (x = x0; x < x1; x++)
581 memcpy (p, mbchar, mblen);
588 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
589 int *min_width, int *max_width)
591 struct ascii_driver *a = a_;
592 int bb[TABLE_N_AXES][2];
593 int clip[TABLE_N_AXES][2];
600 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
601 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
603 if (cell->n_footnotes || strchr (cell->text, ' '))
606 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
609 *min_width = *max_width;
613 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
615 struct ascii_driver *a = a_;
616 int bb[TABLE_N_AXES][2];
617 int clip[TABLE_N_AXES][2];
624 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
625 ascii_layout_cell (a, cell, bb, clip, &w, &h);
630 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
631 int bb[TABLE_N_AXES][2],
632 int spill[TABLE_N_AXES][2] UNUSED,
633 int clip[TABLE_N_AXES][2])
635 struct ascii_driver *a = a_;
638 ascii_layout_cell (a, cell, bb, clip, &w, &h);
642 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
644 if (y >= a->allocated_lines)
646 size_t new_alloc = MAX (25, a->allocated_lines);
647 while (new_alloc <= y)
648 new_alloc = xtimes (new_alloc, 2);
649 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
650 for (size_t i = a->allocated_lines; i < new_alloc; i++)
651 u8_line_init (&a->lines[i]);
652 a->allocated_lines = new_alloc;
654 return u8_line_reserve (&a->lines[y], x0, x1, n);
658 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
659 bool bold, bool underline,
660 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
661 int y, const uint8_t *string, int n, size_t width)
663 int x0 = MAX (0, clip[H][0]);
664 int y0 = MAX (0, clip[V][0]);
665 int x1 = MIN (a->width, clip[H][1]);
669 if (y < y0 || y >= y1)
672 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
674 case TABLE_HALIGN_LEFT:
677 case TABLE_HALIGN_CENTER:
678 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
680 case TABLE_HALIGN_RIGHT:
681 case TABLE_HALIGN_DECIMAL:
682 x = bb[H][1] - width;
698 mblen = u8_mbtouc (&uc, string, n);
703 w = uc_width (uc, "UTF-8");
718 for (ofs = 0; ofs < n; )
724 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
726 w = uc_width (uc, "UTF-8");
729 if (width + w > x1 - x)
740 if (!a->emphasis || (!bold && !underline))
741 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
749 /* First figure out how many bytes need to be inserted. */
751 for (ofs = 0; ofs < n; ofs += mblen)
756 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
757 w = uc_width (uc, "UTF-8");
768 /* Then insert them. */
769 out = ascii_reserve (a, y, x, x + width, n_out);
770 for (ofs = 0; ofs < n; ofs += mblen)
775 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
776 w = uc_width (uc, "UTF-8");
782 out = mempcpy (out, string + ofs, mblen);
791 out = mempcpy (out, string + ofs, mblen);
797 add_footnote_markers (const char *text, const struct table_cell *cell)
799 struct string s = DS_EMPTY_INITIALIZER;
800 ds_put_cstr (&s, text);
801 for (size_t i = 0; i < cell->n_footnotes; i++)
802 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
803 return ds_steal_cstr (&s);
807 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
808 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
809 int *widthp, int *heightp)
814 /* Get the basic textual contents. */
815 const char *plain_text = (cell->options & TAB_MARKUP
816 ? output_get_text_from_markup (cell->text)
819 /* Append footnote markers if any. */
821 if (cell->n_footnotes)
823 text = add_footnote_markers (plain_text, cell);
824 if (plain_text != cell->text)
825 free (CONST_CAST (char *, plain_text));
830 /* Calculate length; if it's zero, then there's nothing to do. */
831 size_t length = strlen (text);
834 if (text != cell->text)
835 free (CONST_CAST (char *, text));
839 char *breaks = xmalloc (length + 1);
840 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
842 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
843 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
846 int bb_width = bb[H][1] - bb[H][0];
847 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
849 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
850 const char *b = breaks + pos;
851 size_t n = length - pos;
853 size_t last_break_ofs = 0;
854 int last_break_width = 0;
859 for (ofs = 0; ofs < n; )
865 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
866 if (b[ofs] == UC_BREAK_MANDATORY)
868 else if (b[ofs] == UC_BREAK_POSSIBLE)
870 last_break_ofs = ofs;
871 last_break_width = width;
874 w = uc_width (uc, "UTF-8");
877 if (width + w > bb_width)
879 if (isspace (line[ofs]))
881 else if (last_break_ofs != 0)
883 ofs = last_break_ofs;
884 width = last_break_width;
893 /* Trim any trailing spaces off the end of the text to be drawn. */
894 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
895 if (!isspace (line[graph_ofs - 1]))
897 width -= ofs - graph_ofs;
900 text_draw (a, cell->style->cell_style.halign, cell->options,
901 cell->style->font_style.bold,
902 cell->style->font_style.underline,
903 bb, clip, y, line, graph_ofs, width);
905 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
906 past any spaces past the end of the line (but not past a new-line). */
907 if (b[ofs] == UC_BREAK_MANDATORY)
910 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
920 if (text != cell->text)
921 free (CONST_CAST (char *, text));
925 ascii_test_write (struct output_driver *driver,
926 const char *s, int x, int y, bool bold, bool underline)
928 struct ascii_driver *a = ascii_driver_cast (driver);
929 int bb[TABLE_N_AXES][2];
932 if (a->file == NULL && !ascii_open_page (a))
935 struct area_style style = {
936 .cell_style.halign = TABLE_HALIGN_LEFT,
937 .font_style.bold = bold,
938 .font_style.underline = underline,
940 struct table_cell cell = {
941 .text = CONST_CAST (char *, s),
945 bb[TABLE_HORZ][0] = x;
946 bb[TABLE_HORZ][1] = a->width;
947 bb[TABLE_VERT][0] = y;
948 bb[TABLE_VERT][1] = INT_MAX;
950 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
954 ascii_test_set_length (struct output_driver *driver, int y, int length)
956 struct ascii_driver *a = ascii_driver_cast (driver);
958 if (a->file == NULL && !ascii_open_page (a))
963 u8_line_set_length (&a->lines[y], length);
967 ascii_test_flush (struct output_driver *driver)
969 struct ascii_driver *a = ascii_driver_cast (driver);
971 for (size_t i = a->allocated_lines; i-- > 0; )
972 if (a->lines[i].width)
974 ascii_output_lines (a, i + 1);
979 /* ascii_close_page () and support routines. */
981 #if HAVE_DECL_SIGWINCH
982 static struct ascii_driver *the_driver;
985 winch_handler (int signum UNUSED)
987 update_page_size (the_driver, false);
992 ascii_open_page (struct ascii_driver *a)
999 a->file = fn_open (a->handle, a->append ? "a" : "w");
1000 if (a->file != NULL)
1002 if ( isatty (fileno (a->file)))
1004 #if HAVE_DECL_SIGWINCH
1005 struct sigaction action;
1006 sigemptyset (&action.sa_mask);
1007 action.sa_flags = 0;
1008 action.sa_handler = winch_handler;
1010 sigaction (SIGWINCH, &action, NULL);
1012 a->auto_width = true;
1017 msg_error (errno, _("ascii: opening output file `%s'"),
1018 fh_get_file_name (a->handle));