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 */
186 struct cell_color fg;
187 struct cell_color bg;
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 int object_cnt; /* Number of objects 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 void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
216 enum render_line_style styles[TABLE_N_AXES][2],
217 struct cell_color colors[TABLE_N_AXES][2]);
218 static void ascii_measure_cell_width (void *, const struct table_cell *,
220 static int ascii_measure_cell_height (void *, const struct table_cell *,
222 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
223 int bb[TABLE_N_AXES][2],
224 int spill[TABLE_N_AXES][2],
225 int clip[TABLE_N_AXES][2]);
227 #if HAVE_DECL_SIGWINCH
228 static struct ascii_driver *the_driver;
230 static void winch_handler (int);
233 static struct ascii_driver *
234 ascii_driver_cast (struct output_driver *driver)
236 assert (driver->class == &ascii_driver_class);
237 return UP_CAST (driver, struct ascii_driver, driver);
240 static struct driver_option *
241 opt (struct output_driver *d, struct string_map *options, const char *key,
242 const char *default_value)
244 return driver_option_get (d, options, key, default_value);
247 /* Return true iff the terminal appears to be an xterm with
248 UTF-8 capabilities */
250 term_is_utf8_xterm (void)
252 const char *term = getenv ("TERM");
253 const char *xterm_locale = getenv ("XTERM_LOCAL");
254 return (term && xterm_locale
255 && !strcmp (term, "xterm")
256 && (strcasestr (xterm_locale, "utf8")
257 || strcasestr (xterm_locale, "utf-8")));
260 static struct output_driver *
261 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
262 struct string_map *o)
264 enum { BOX_ASCII, BOX_UNICODE } box;
265 int min_break[TABLE_N_AXES];
266 struct output_driver *d;
267 struct ascii_driver *a;
269 a = xzalloc (sizeof *a);
271 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
272 a->append = parse_boolean (opt (d, o, "append", "false"));
273 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
275 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
278 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
280 a->width = parse_page_size (opt (d, o, "width", "79"));
281 a->auto_width = a->width < 0;
282 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
284 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
285 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
288 const char *default_box = (!strcmp (fh_get_file_name (fh), "-")
289 && isatty (STDOUT_FILENO)
290 && (!strcmp (locale_charset (), "UTF-8")
291 || term_is_utf8_xterm ())
292 ? "unicode" : "ascii");
293 box = parse_enum (opt (d, o, "box", default_box),
295 "unicode", BOX_UNICODE,
297 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
302 a->allocated_lines = 0;
306 a->params.draw_line = ascii_draw_line;
307 a->params.measure_cell_width = ascii_measure_cell_width;
308 a->params.measure_cell_height = ascii_measure_cell_height;
309 a->params.adjust_break = NULL;
310 a->params.draw_cell = ascii_draw_cell;
312 a->params.size[H] = a->width;
313 a->params.size[V] = INT_MAX;
314 a->params.font_size[H] = 1;
315 a->params.font_size[V] = 1;
316 for (int i = 0; i < RENDER_N_LINES; i++)
318 int width = i == RENDER_LINE_NONE ? 0 : 1;
319 a->params.line_widths[H][i] = width;
320 a->params.line_widths[V][i] = width;
322 for (int i = 0; i < TABLE_N_AXES; i++)
323 a->params.min_break[i] = a->min_break[i];
324 a->params.supports_margins = false;
325 a->params.rtl = render_direction_rtl ();
327 if (!update_page_size (a, true))
330 a->file = fn_open (a->handle, a->append ? "a" : "w");
333 msg_error (errno, _("ascii: opening output file `%s'"),
334 fh_get_file_name (a->handle));
338 if (isatty (fileno (a->file)))
340 #if HAVE_DECL_SIGWINCH
341 struct sigaction action;
342 sigemptyset (&action.sa_mask);
344 action.sa_handler = winch_handler;
346 sigaction (SIGWINCH, &action, NULL);
348 a->auto_width = true;
354 output_driver_destroy (d);
359 parse_page_size (struct driver_option *option)
361 int dim = atol (option->default_value);
363 if (option->value != NULL)
365 if (!strcmp (option->value, "auto"))
373 value = strtol (option->value, &tail, 0);
374 if (dim >= 1 && errno != ERANGE && *tail == '\0')
377 msg (MW, _("%s: %s must be positive integer or `auto'"),
378 option->driver_name, option->name);
382 driver_option_destroy (option);
387 /* Re-calculates the page width based on settings, margins, and, if "auto" is
388 set, the size of the user's terminal window or GUI output window. */
390 update_page_size (struct ascii_driver *a, bool issue_error)
392 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
396 a->params.size[H] = a->width = settings_get_viewwidth ();
397 a->params.min_break[H] = a->min_break[H] = a->width / 2;
400 if (a->width < MIN_WIDTH)
404 _("ascii: page must be at least %d characters wide, but "
405 "as configured is only %d characters"),
408 if (a->width < MIN_WIDTH)
409 a->params.size[H] = a->width = MIN_WIDTH;
417 ascii_destroy (struct output_driver *driver)
419 struct ascii_driver *a = ascii_driver_cast (driver);
423 fn_close (a->handle, a->file);
424 fh_unref (a->handle);
425 free (a->chart_file_name);
426 for (i = 0; i < a->allocated_lines; i++)
427 u8_line_destroy (&a->lines[i]);
433 ascii_flush (struct output_driver *driver)
435 struct ascii_driver *a = ascii_driver_cast (driver);
441 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
443 for (size_t y = 0; y < n_lines; y++)
445 if (y < a->allocated_lines)
447 struct u8_line *line = &a->lines[y];
449 while (ds_chomp_byte (&line->s, ' '))
451 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
452 u8_line_clear (&a->lines[y]);
454 putc ('\n', a->file);
459 ascii_output_table_item (struct ascii_driver *a,
460 const struct table_item *table_item)
462 struct render_pager *p;
464 update_page_size (a, false);
467 putc ('\n', a->file);
469 p = render_pager_create (&a->params, table_item);
470 for (int i = 0; render_pager_has_next (p); i++)
473 putc ('\n', a->file);
474 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
476 render_pager_destroy (p);
480 ascii_output_table_item_unref (struct ascii_driver *a,
481 struct table_item *table_item)
483 ascii_output_table_item (a, table_item);
484 table_item_unref (table_item);
488 ascii_output_text (struct ascii_driver *a, const char *text)
490 ascii_output_table_item_unref (
491 a, table_item_create (table_from_string (text), NULL, NULL));
495 ascii_submit (struct output_driver *driver,
496 const struct output_item *output_item)
498 struct ascii_driver *a = ascii_driver_cast (driver);
503 if (is_table_item (output_item))
504 ascii_output_table_item (a, to_table_item (output_item));
506 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
508 struct chart_item *chart_item = to_chart_item (output_item);
511 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
515 if (file_name != NULL)
517 struct text_item *text_item;
519 text_item = text_item_create_format (
520 TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
522 ascii_submit (driver, &text_item->output_item);
523 text_item_unref (text_item);
527 #endif /* HAVE_CAIRO */
528 else if (is_text_item (output_item))
530 const struct text_item *text_item = to_text_item (output_item);
531 enum text_item_type type = text_item_get_type (text_item);
533 if (type != TEXT_ITEM_PAGE_TITLE && type != TEXT_ITEM_EJECT_PAGE)
534 ascii_output_table_item_unref (
535 a, text_item_to_table_item (text_item_ref (text_item)));
537 else if (is_message_item (output_item))
539 const struct message_item *message_item = to_message_item (output_item);
540 char *s = msg_to_string (message_item_get_msg (message_item));
541 ascii_output_text (a, s);
546 const struct output_driver_factory txt_driver_factory =
547 { "txt", "-", ascii_create };
548 const struct output_driver_factory list_driver_factory =
549 { "list", "-", ascii_create };
551 static const struct output_driver_class ascii_driver_class =
559 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
561 static void ascii_layout_cell (struct ascii_driver *,
562 const struct table_cell *,
563 int bb[TABLE_N_AXES][2],
564 int clip[TABLE_N_AXES][2],
565 int *width, int *height);
568 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
569 enum render_line_style styles[TABLE_N_AXES][2],
570 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
572 struct ascii_driver *a = a_;
579 /* Clip to the page. */
580 x0 = MAX (bb[H][0], 0);
581 y0 = MAX (bb[V][0], 0);
582 x1 = MIN (bb[H][1], a->width);
584 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
588 uc = a->box[make_box_index (styles[V][0], styles[V][1],
589 styles[H][0], styles[H][1])];
590 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
591 for (y = y0; y < y1; y++)
593 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
594 for (x = x0; x < x1; x++)
596 memcpy (p, mbchar, mblen);
603 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
604 int *min_width, int *max_width)
606 struct ascii_driver *a = a_;
607 int bb[TABLE_N_AXES][2];
608 int clip[TABLE_N_AXES][2];
615 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
616 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
618 if (cell->n_footnotes || strchr (cell->text, ' ')
619 || cell->n_subscripts || cell->superscript)
622 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
625 *min_width = *max_width;
629 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
631 struct ascii_driver *a = a_;
632 int bb[TABLE_N_AXES][2];
633 int clip[TABLE_N_AXES][2];
640 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
641 ascii_layout_cell (a, cell, bb, clip, &w, &h);
646 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
647 int bb[TABLE_N_AXES][2],
648 int spill[TABLE_N_AXES][2] UNUSED,
649 int clip[TABLE_N_AXES][2])
651 struct ascii_driver *a = a_;
654 ascii_layout_cell (a, cell, bb, clip, &w, &h);
658 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
660 if (y >= a->allocated_lines)
662 size_t new_alloc = MAX (25, a->allocated_lines);
663 while (new_alloc <= y)
664 new_alloc = xtimes (new_alloc, 2);
665 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
666 for (size_t i = a->allocated_lines; i < new_alloc; i++)
667 u8_line_init (&a->lines[i]);
668 a->allocated_lines = new_alloc;
670 return u8_line_reserve (&a->lines[y], x0, x1, n);
674 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
675 bool bold, bool underline,
676 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
677 int y, const uint8_t *string, int n, size_t width)
679 int x0 = MAX (0, clip[H][0]);
680 int y0 = MAX (0, clip[V][0]);
681 int x1 = MIN (a->width, clip[H][1]);
685 if (y < y0 || y >= y1)
688 switch (table_halign_interpret (halign, options & TAB_NUMERIC))
690 case TABLE_HALIGN_LEFT:
693 case TABLE_HALIGN_CENTER:
694 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
696 case TABLE_HALIGN_RIGHT:
697 case TABLE_HALIGN_DECIMAL:
698 x = bb[H][1] - width;
714 mblen = u8_mbtouc (&uc, string, n);
719 w = uc_width (uc, "UTF-8");
734 for (ofs = 0; ofs < n; )
740 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
742 w = uc_width (uc, "UTF-8");
745 if (width + w > x1 - x)
756 if (!a->emphasis || (!bold && !underline))
757 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
765 /* First figure out how many bytes need to be inserted. */
767 for (ofs = 0; ofs < n; ofs += mblen)
772 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
773 w = uc_width (uc, "UTF-8");
784 /* Then insert them. */
785 out = ascii_reserve (a, y, x, x + width, n_out);
786 for (ofs = 0; ofs < n; ofs += mblen)
791 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
792 w = uc_width (uc, "UTF-8");
798 out = mempcpy (out, string + ofs, mblen);
807 out = mempcpy (out, string + ofs, mblen);
813 add_markers (const char *text, const struct table_cell *cell)
815 struct string s = DS_EMPTY_INITIALIZER;
816 ds_put_cstr (&s, text);
817 for (size_t i = 0; i < cell->n_subscripts; i++)
818 ds_put_format (&s, "%c%s", i ? ',' : '_', cell->subscripts[i]);
819 if (cell->superscript)
820 ds_put_format (&s, "^%s", cell->superscript);
821 for (size_t i = 0; i < cell->n_footnotes; i++)
822 ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
823 return ds_steal_cstr (&s);
827 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
828 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
829 int *widthp, int *heightp)
834 /* Get the basic textual contents. */
835 const char *plain_text = (cell->options & TAB_MARKUP
836 ? output_get_text_from_markup (cell->text)
839 /* Append footnotes, subscripts, superscript if any. */
841 if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
843 text = add_markers (plain_text, cell);
844 if (plain_text != cell->text)
845 free (CONST_CAST (char *, plain_text));
850 /* Calculate length; if it's zero, then there's nothing to do. */
851 size_t length = strlen (text);
854 if (text != cell->text)
855 free (CONST_CAST (char *, text));
859 char *breaks = xmalloc (length + 1);
860 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
862 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
863 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
866 int bb_width = bb[H][1] - bb[H][0];
867 for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
869 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
870 const char *b = breaks + pos;
871 size_t n = length - pos;
873 size_t last_break_ofs = 0;
874 int last_break_width = 0;
879 for (ofs = 0; ofs < n; )
885 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
886 if (b[ofs] == UC_BREAK_MANDATORY)
888 else if (b[ofs] == UC_BREAK_POSSIBLE)
890 last_break_ofs = ofs;
891 last_break_width = width;
894 w = uc_width (uc, "UTF-8");
897 if (width + w > bb_width)
899 if (isspace (line[ofs]))
901 else if (last_break_ofs != 0)
903 ofs = last_break_ofs;
904 width = last_break_width;
913 /* Trim any trailing spaces off the end of the text to be drawn. */
914 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
915 if (!isspace (line[graph_ofs - 1]))
917 width -= ofs - graph_ofs;
920 text_draw (a, cell->style->cell_style.halign, cell->options,
921 cell->style->font_style.bold,
922 cell->style->font_style.underline,
923 bb, clip, y, line, graph_ofs, width);
925 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
926 past any spaces past the end of the line (but not past a new-line). */
927 if (b[ofs] == UC_BREAK_MANDATORY)
930 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
940 if (text != cell->text)
941 free (CONST_CAST (char *, text));
945 ascii_test_write (struct output_driver *driver,
946 const char *s, int x, int y, bool bold, bool underline)
948 struct ascii_driver *a = ascii_driver_cast (driver);
949 int bb[TABLE_N_AXES][2];
955 struct area_style style = {
956 .cell_style.halign = TABLE_HALIGN_LEFT,
957 .font_style.bold = bold,
958 .font_style.underline = underline,
960 struct table_cell cell = {
961 .text = CONST_CAST (char *, s),
965 bb[TABLE_HORZ][0] = x;
966 bb[TABLE_HORZ][1] = a->width;
967 bb[TABLE_VERT][0] = y;
968 bb[TABLE_VERT][1] = INT_MAX;
970 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
974 ascii_test_set_length (struct output_driver *driver, int y, int length)
976 struct ascii_driver *a = ascii_driver_cast (driver);
983 u8_line_set_length (&a->lines[y], length);
987 ascii_test_flush (struct output_driver *driver)
989 struct ascii_driver *a = ascii_driver_cast (driver);
991 for (size_t i = a->allocated_lines; i-- > 0; )
992 if (a->lines[i].width)
994 ascii_output_lines (a, i + 1);
999 #if HAVE_DECL_SIGWINCH
1001 winch_handler (int signum UNUSED)
1003 update_page_size (the_driver, false);