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/tab.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 /* How to emphasize text. */
178 EMPH_BOLD, /* Overstrike for bold. */
179 EMPH_UNDERLINE, /* Overstrike for underlining. */
180 EMPH_NONE /* No emphasis. */
183 /* ASCII output driver. */
186 struct output_driver driver;
188 /* User parameters. */
189 bool append; /* Append if output file already exists? */
190 enum emphasis_style emphasis; /* How to emphasize text. */
191 char *chart_file_name; /* Name of files used for charts. */
194 /* Colours for charts */
199 int width; /* Page width. */
200 bool auto_width; /* Use viewwidth as page width? */
202 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
204 const ucs4_t *box; /* Line & box drawing characters. */
206 /* Internal state. */
207 struct file_handle *handle;
208 FILE *file; /* Output file. */
209 bool error; /* Output error? */
210 struct u8_line *lines; /* Page content. */
211 int allocated_lines; /* Number of lines allocated. */
212 int chart_cnt; /* Number of charts so far. */
215 static const struct output_driver_class ascii_driver_class;
217 static void ascii_submit (struct output_driver *, const struct output_item *);
219 static bool update_page_size (struct ascii_driver *, bool issue_error);
220 static int parse_page_size (struct driver_option *);
222 static bool ascii_open_page (struct ascii_driver *);
224 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
225 enum render_line_style styles[TABLE_N_AXES][2]);
226 static void ascii_measure_cell_width (void *, const struct table_cell *,
228 static int ascii_measure_cell_height (void *, const struct table_cell *,
230 static void ascii_draw_cell (void *, const struct table_cell *,
231 int bb[TABLE_N_AXES][2],
232 int spill[TABLE_N_AXES][2],
233 int clip[TABLE_N_AXES][2]);
235 static struct ascii_driver *
236 ascii_driver_cast (struct output_driver *driver)
238 assert (driver->class == &ascii_driver_class);
239 return UP_CAST (driver, struct ascii_driver, driver);
242 static struct driver_option *
243 opt (struct output_driver *d, struct string_map *options, const char *key,
244 const char *default_value)
246 return driver_option_get (d, options, key, default_value);
249 static struct output_driver *
250 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
251 struct string_map *o)
253 enum { BOX_ASCII, BOX_UNICODE } box;
254 int min_break[TABLE_N_AXES];
255 struct output_driver *d;
256 struct ascii_driver *a;
258 a = xzalloc (sizeof *a);
260 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
261 a->append = parse_boolean (opt (d, o, "append", "false"));
262 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
264 "underline", EMPH_UNDERLINE,
268 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
271 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
273 a->width = parse_page_size (opt (d, o, "width", "79"));
274 a->auto_width = a->width < 0;
275 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
277 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
278 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
280 box = parse_enum (opt (d, o, "box", "ascii"),
282 "unicode", BOX_UNICODE,
284 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
289 a->allocated_lines = 0;
292 if (!update_page_size (a, true))
298 output_driver_destroy (d);
303 parse_page_size (struct driver_option *option)
305 int dim = atol (option->default_value);
307 if (option->value != NULL)
309 if (!strcmp (option->value, "auto"))
317 value = strtol (option->value, &tail, 0);
318 if (dim >= 1 && errno != ERANGE && *tail == '\0')
321 msg (MW, _("%s: %s must be positive integer or `auto'"),
322 option->driver_name, option->name);
326 driver_option_destroy (option);
331 /* Re-calculates the page width based on settings, margins, and, if "auto" is
332 set, the size of the user's terminal window or GUI output window. */
334 update_page_size (struct ascii_driver *a, bool issue_error)
336 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
340 a->width = settings_get_viewwidth ();
341 a->min_break[H] = a->width / 2;
344 if (a->width < MIN_WIDTH)
348 _("ascii: page must be at least %d characters wide, but "
349 "as configured is only %d characters"),
352 if (a->width < MIN_WIDTH)
353 a->width = MIN_WIDTH;
361 ascii_destroy (struct output_driver *driver)
363 struct ascii_driver *a = ascii_driver_cast (driver);
367 fn_close (a->handle, a->file);
368 fh_unref (a->handle);
369 free (a->chart_file_name);
370 for (i = 0; i < a->allocated_lines; i++)
371 u8_line_destroy (&a->lines[i]);
377 ascii_flush (struct output_driver *driver)
379 struct ascii_driver *a = ascii_driver_cast (driver);
385 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
387 for (size_t y = 0; y < n_lines; y++)
389 struct u8_line *line = &a->lines[y];
391 while (ds_chomp_byte (&line->s, ' '))
393 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
394 putc ('\n', a->file);
396 u8_line_clear (&a->lines[y]);
401 ascii_output_table_item (struct ascii_driver *a,
402 const struct table_item *table_item)
404 struct render_params params;
405 struct render_pager *p;
408 update_page_size (a, false);
410 params.draw_line = ascii_draw_line;
411 params.measure_cell_width = ascii_measure_cell_width;
412 params.measure_cell_height = ascii_measure_cell_height;
413 params.adjust_break = NULL;
414 params.draw_cell = ascii_draw_cell;
416 params.size[H] = a->width;
417 params.size[V] = INT_MAX;
418 params.font_size[H] = 1;
419 params.font_size[V] = 1;
420 for (i = 0; i < RENDER_N_LINES; i++)
422 int width = i == RENDER_LINE_NONE ? 0 : 1;
423 params.line_widths[H][i] = width;
424 params.line_widths[V][i] = width;
426 for (i = 0; i < TABLE_N_AXES; i++)
427 params.min_break[i] = a->min_break[i];
428 params.supports_margins = false;
431 putc ('\n', a->file);
432 else if (!ascii_open_page (a))
435 p = render_pager_create (¶ms, table_item);
436 for (int i = 0; render_pager_has_next (p); i++)
439 putc ('\n', a->file);
440 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
442 render_pager_destroy (p);
446 ascii_output_text (struct ascii_driver *a, const char *text)
448 struct table_item *table_item;
450 table_item = table_item_create (table_from_string (TAB_LEFT, text),
452 ascii_output_table_item (a, table_item);
453 table_item_unref (table_item);
457 ascii_submit (struct output_driver *driver,
458 const struct output_item *output_item)
460 struct ascii_driver *a = ascii_driver_cast (driver);
465 if (is_table_item (output_item))
466 ascii_output_table_item (a, to_table_item (output_item));
468 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
470 struct chart_item *chart_item = to_chart_item (output_item);
473 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
477 if (file_name != NULL)
479 struct text_item *text_item;
481 text_item = text_item_create_format (
482 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
484 ascii_submit (driver, &text_item->output_item);
485 text_item_unref (text_item);
489 #endif /* HAVE_CAIRO */
490 else if (is_text_item (output_item))
492 const struct text_item *text_item = to_text_item (output_item);
493 enum text_item_type type = text_item_get_type (text_item);
494 const char *text = text_item_get_text (text_item);
498 case TEXT_ITEM_TITLE:
499 case TEXT_ITEM_SUBTITLE:
500 case TEXT_ITEM_COMMAND_OPEN:
501 case TEXT_ITEM_COMMAND_CLOSE:
504 case TEXT_ITEM_BLANK_LINE:
507 case TEXT_ITEM_EJECT_PAGE:
511 ascii_output_text (a, text);
515 else if (is_message_item (output_item))
517 const struct message_item *message_item = to_message_item (output_item);
518 const struct msg *msg = message_item_get_msg (message_item);
519 char *s = msg_to_string (msg, message_item->command_name);
520 ascii_output_text (a, s);
525 const struct output_driver_factory txt_driver_factory =
526 { "txt", "-", ascii_create };
527 const struct output_driver_factory list_driver_factory =
528 { "list", "-", ascii_create };
530 static const struct output_driver_class ascii_driver_class =
538 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
540 static void ascii_layout_cell (struct ascii_driver *,
541 const struct table_cell *,
542 int bb[TABLE_N_AXES][2],
543 int clip[TABLE_N_AXES][2],
544 int *width, int *height);
547 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
548 enum render_line_style styles[TABLE_N_AXES][2])
550 struct ascii_driver *a = a_;
557 /* Clip to the page. */
558 x0 = MAX (bb[H][0], 0);
559 y0 = MAX (bb[V][0], 0);
560 x1 = MIN (bb[H][1], a->width);
562 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
566 uc = a->box[make_box_index (styles[V][0], styles[V][1],
567 styles[H][0], styles[H][1])];
568 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
569 for (y = y0; y < y1; y++)
571 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
572 for (x = x0; x < x1; x++)
574 memcpy (p, mbchar, mblen);
581 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
582 int *min_width, int *max_width)
584 struct ascii_driver *a = a_;
585 int bb[TABLE_N_AXES][2];
586 int clip[TABLE_N_AXES][2];
593 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
594 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
596 if (cell->n_contents != 1
597 || cell->contents[0].n_footnotes
598 || strchr (cell->contents[0].text, ' '))
601 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
604 *min_width = *max_width;
608 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
610 struct ascii_driver *a = a_;
611 int bb[TABLE_N_AXES][2];
612 int clip[TABLE_N_AXES][2];
619 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
620 ascii_layout_cell (a, cell, bb, clip, &w, &h);
625 ascii_draw_cell (void *a_, const struct table_cell *cell,
626 int bb[TABLE_N_AXES][2],
627 int spill[TABLE_N_AXES][2] UNUSED,
628 int clip[TABLE_N_AXES][2])
630 struct ascii_driver *a = a_;
633 ascii_layout_cell (a, cell, bb, clip, &w, &h);
637 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
639 if (y >= a->allocated_lines)
641 size_t new_alloc = MAX (25, a->allocated_lines);
642 while (new_alloc <= y)
643 new_alloc = xtimes (new_alloc, 2);
644 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
645 for (size_t i = a->allocated_lines; i < new_alloc; i++)
646 u8_line_init (&a->lines[i]);
647 a->allocated_lines = new_alloc;
649 return u8_line_reserve (&a->lines[y], x0, x1, n);
653 text_draw (struct ascii_driver *a, unsigned int options,
654 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
655 int y, const uint8_t *string, int n, size_t width)
657 int x0 = MAX (0, clip[H][0]);
658 int y0 = MAX (0, clip[V][0]);
659 int x1 = MIN (a->width, clip[H][1]);
663 if (y < y0 || y >= y1)
666 switch (options & TAB_HALIGN)
672 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
675 x = bb[H][1] - width;
691 mblen = u8_mbtouc (&uc, string, n);
696 w = uc_width (uc, "UTF-8");
711 for (ofs = 0; ofs < n; )
717 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
719 w = uc_width (uc, "UTF-8");
722 if (width + w > x1 - x)
733 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
734 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
742 /* First figure out how many bytes need to be inserted. */
744 for (ofs = 0; ofs < n; ofs += mblen)
749 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
750 w = uc_width (uc, "UTF-8");
753 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
756 /* Then insert them. */
757 out = ascii_reserve (a, y, x, x + width, n_out);
758 for (ofs = 0; ofs < n; ofs += mblen)
763 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
764 w = uc_width (uc, "UTF-8");
768 if (a->emphasis == EMPH_UNDERLINE)
771 out = mempcpy (out, string + ofs, mblen);
774 out = mempcpy (out, string + ofs, mblen);
780 ascii_layout_cell_text (struct ascii_driver *a,
781 const struct cell_contents *contents,
782 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
793 length = strlen (contents->text);
794 if (contents->n_footnotes)
800 ds_extend (&s, length + contents->n_footnotes * 4);
801 ds_put_cstr (&s, contents->text);
802 for (i = 0; i < contents->n_footnotes; i++)
803 ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
805 length = ds_length (&s);
806 text = ds_steal_cstr (&s);
812 text = contents->text;
815 breaks = xmalloc (length + 1);
816 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
818 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
819 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
822 bb_width = bb[H][1] - bb[H][0];
823 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
825 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
826 const char *b = breaks + pos;
827 size_t n = length - pos;
829 size_t last_break_ofs = 0;
830 int last_break_width = 0;
835 for (ofs = 0; ofs < n; )
841 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
842 if (b[ofs] == UC_BREAK_MANDATORY)
844 else if (b[ofs] == UC_BREAK_POSSIBLE)
846 last_break_ofs = ofs;
847 last_break_width = width;
850 w = uc_width (uc, "UTF-8");
853 if (width + w > bb_width)
855 if (isspace (line[ofs]))
857 else if (last_break_ofs != 0)
859 ofs = last_break_ofs;
860 width = last_break_width;
869 /* Trim any trailing spaces off the end of the text to be drawn. */
870 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
871 if (!isspace (line[graph_ofs - 1]))
873 width -= ofs - graph_ofs;
876 text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
878 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
879 past any spaces past the end of the line (but not past a new-line). */
880 if (b[ofs] == UC_BREAK_MANDATORY)
883 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
892 if (text != contents->text)
893 free (CONST_CAST (char *, text));
899 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
900 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
901 int *widthp, int *heightp)
903 int bb[TABLE_N_AXES][2];
909 memcpy (bb, bb_, sizeof bb);
910 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
912 const struct cell_contents *contents = &cell->contents[i];
914 /* Put a blank line between contents. */
918 if (bb[V][0] >= bb[V][1])
922 bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp);
924 *heightp = bb[V][0] - bb_[V][0];
928 ascii_test_write (struct output_driver *driver,
929 const char *s, int x, int y, unsigned int options)
931 struct ascii_driver *a = ascii_driver_cast (driver);
932 int bb[TABLE_N_AXES][2];
935 if (a->file == NULL && !ascii_open_page (a))
938 struct cell_contents contents = {
939 .options = options | TAB_LEFT,
940 .text = CONST_CAST (char *, s),
943 struct table_cell cell = {
944 .contents = &contents,
948 bb[TABLE_HORZ][0] = x;
949 bb[TABLE_HORZ][1] = a->width;
950 bb[TABLE_VERT][0] = y;
951 bb[TABLE_VERT][1] = INT_MAX;
953 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
957 ascii_test_set_length (struct output_driver *driver, int y, int length)
959 struct ascii_driver *a = ascii_driver_cast (driver);
961 if (a->file == NULL && !ascii_open_page (a))
966 u8_line_set_length (&a->lines[y], length);
970 ascii_test_flush (struct output_driver *driver)
972 struct ascii_driver *a = ascii_driver_cast (driver);
974 for (size_t i = a->allocated_lines; i-- > 0; )
975 if (a->lines[i].width)
977 ascii_output_lines (a, i + 1);
982 /* ascii_close_page () and support routines. */
984 #if HAVE_DECL_SIGWINCH
985 static struct ascii_driver *the_driver;
988 winch_handler (int signum UNUSED)
990 update_page_size (the_driver, false);
995 ascii_open_page (struct ascii_driver *a)
1000 if (a->file == NULL)
1002 a->file = fn_open (a->handle, a->append ? "a" : "w");
1003 if (a->file != NULL)
1005 if ( isatty (fileno (a->file)))
1007 #if HAVE_DECL_SIGWINCH
1008 struct sigaction action;
1009 sigemptyset (&action.sa_mask);
1010 action.sa_flags = 0;
1011 action.sa_handler = winch_handler;
1013 sigaction (SIGWINCH, &action, NULL);
1015 a->auto_width = true;
1020 msg_error (errno, _("ascii: opening output file `%s'"),
1021 fh_get_file_name (a->handle));