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 clip[TABLE_N_AXES][2]);
234 static struct ascii_driver *
235 ascii_driver_cast (struct output_driver *driver)
237 assert (driver->class == &ascii_driver_class);
238 return UP_CAST (driver, struct ascii_driver, driver);
241 static struct driver_option *
242 opt (struct output_driver *d, struct string_map *options, const char *key,
243 const char *default_value)
245 return driver_option_get (d, options, key, default_value);
248 static struct output_driver *
249 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
250 struct string_map *o)
252 enum { BOX_ASCII, BOX_UNICODE } box;
253 int min_break[TABLE_N_AXES];
254 struct output_driver *d;
255 struct ascii_driver *a;
257 a = xzalloc (sizeof *a);
259 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
260 a->append = parse_boolean (opt (d, o, "append", "false"));
261 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
263 "underline", EMPH_UNDERLINE,
267 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
270 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
272 a->width = parse_page_size (opt (d, o, "width", "79"));
273 a->auto_width = a->width < 0;
274 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
276 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
277 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
279 box = parse_enum (opt (d, o, "box", "ascii"),
281 "unicode", BOX_UNICODE,
283 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
288 a->allocated_lines = 0;
291 if (!update_page_size (a, true))
297 output_driver_destroy (d);
302 parse_page_size (struct driver_option *option)
304 int dim = atol (option->default_value);
306 if (option->value != NULL)
308 if (!strcmp (option->value, "auto"))
316 value = strtol (option->value, &tail, 0);
317 if (dim >= 1 && errno != ERANGE && *tail == '\0')
320 msg (MW, _("%s: %s must be positive integer or `auto'"),
321 option->driver_name, option->name);
325 driver_option_destroy (option);
330 /* Re-calculates the page width based on settings, margins, and, if "auto" is
331 set, the size of the user's terminal window or GUI output window. */
333 update_page_size (struct ascii_driver *a, bool issue_error)
335 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
339 a->width = settings_get_viewwidth ();
340 a->min_break[H] = a->width / 2;
343 if (a->width < MIN_WIDTH)
347 _("ascii: page must be at least %d characters wide, but "
348 "as configured is only %d characters"),
351 if (a->width < MIN_WIDTH)
352 a->width = MIN_WIDTH;
360 ascii_destroy (struct output_driver *driver)
362 struct ascii_driver *a = ascii_driver_cast (driver);
366 fn_close (a->handle, a->file);
367 fh_unref (a->handle);
368 free (a->chart_file_name);
369 for (i = 0; i < a->allocated_lines; i++)
370 u8_line_destroy (&a->lines[i]);
376 ascii_flush (struct output_driver *driver)
378 struct ascii_driver *a = ascii_driver_cast (driver);
384 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
386 for (size_t y = 0; y < n_lines; y++)
388 struct u8_line *line = &a->lines[y];
390 while (ds_chomp_byte (&line->s, ' '))
392 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
393 putc ('\n', a->file);
395 u8_line_clear (&a->lines[y]);
400 ascii_output_table_item (struct ascii_driver *a,
401 const struct table_item *table_item)
403 struct render_params params;
404 struct render_pager *p;
407 update_page_size (a, false);
409 params.draw_line = ascii_draw_line;
410 params.measure_cell_width = ascii_measure_cell_width;
411 params.measure_cell_height = ascii_measure_cell_height;
412 params.adjust_break = NULL;
413 params.draw_cell = ascii_draw_cell;
415 params.size[H] = a->width;
416 params.size[V] = INT_MAX;
417 params.font_size[H] = 1;
418 params.font_size[V] = 1;
419 for (i = 0; i < RENDER_N_LINES; i++)
421 int width = i == RENDER_LINE_NONE ? 0 : 1;
422 params.line_widths[H][i] = width;
423 params.line_widths[V][i] = width;
425 for (i = 0; i < TABLE_N_AXES; i++)
426 params.min_break[i] = a->min_break[i];
427 params.supports_margins = false;
430 putc ('\n', a->file);
431 else if (!ascii_open_page (a))
434 p = render_pager_create (¶ms, table_item);
435 for (int i = 0; render_pager_has_next (p); i++)
438 putc ('\n', a->file);
439 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
441 render_pager_destroy (p);
445 ascii_output_text (struct ascii_driver *a, const char *text)
447 struct table_item *table_item;
449 table_item = table_item_create (table_from_string (TAB_LEFT, text),
451 ascii_output_table_item (a, table_item);
452 table_item_unref (table_item);
456 ascii_submit (struct output_driver *driver,
457 const struct output_item *output_item)
459 struct ascii_driver *a = ascii_driver_cast (driver);
464 if (is_table_item (output_item))
465 ascii_output_table_item (a, to_table_item (output_item));
467 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
469 struct chart_item *chart_item = to_chart_item (output_item);
472 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
476 if (file_name != NULL)
478 struct text_item *text_item;
480 text_item = text_item_create_format (
481 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
483 ascii_submit (driver, &text_item->output_item);
484 text_item_unref (text_item);
488 #endif /* HAVE_CAIRO */
489 else if (is_text_item (output_item))
491 const struct text_item *text_item = to_text_item (output_item);
492 enum text_item_type type = text_item_get_type (text_item);
493 const char *text = text_item_get_text (text_item);
497 case TEXT_ITEM_TITLE:
498 case TEXT_ITEM_SUBTITLE:
499 case TEXT_ITEM_COMMAND_OPEN:
500 case TEXT_ITEM_COMMAND_CLOSE:
503 case TEXT_ITEM_BLANK_LINE:
506 case TEXT_ITEM_EJECT_PAGE:
510 ascii_output_text (a, text);
514 else if (is_message_item (output_item))
516 const struct message_item *message_item = to_message_item (output_item);
517 const struct msg *msg = message_item_get_msg (message_item);
518 char *s = msg_to_string (msg, message_item->command_name);
519 ascii_output_text (a, s);
524 const struct output_driver_factory txt_driver_factory =
525 { "txt", "-", ascii_create };
526 const struct output_driver_factory list_driver_factory =
527 { "list", "-", ascii_create };
529 static const struct output_driver_class ascii_driver_class =
537 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
539 static void ascii_layout_cell (struct ascii_driver *,
540 const struct table_cell *,
541 int bb[TABLE_N_AXES][2],
542 int clip[TABLE_N_AXES][2],
543 int *width, int *height);
546 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
547 enum render_line_style styles[TABLE_N_AXES][2])
549 struct ascii_driver *a = a_;
556 /* Clip to the page. */
557 x0 = MAX (bb[H][0], 0);
558 y0 = MAX (bb[V][0], 0);
559 x1 = MIN (bb[H][1], a->width);
561 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
565 uc = a->box[make_box_index (styles[V][0], styles[V][1],
566 styles[H][0], styles[H][1])];
567 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
568 for (y = y0; y < y1; y++)
570 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
571 for (x = x0; x < x1; x++)
573 memcpy (p, mbchar, mblen);
580 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
581 int *min_width, int *max_width)
583 struct ascii_driver *a = a_;
584 int bb[TABLE_N_AXES][2];
585 int clip[TABLE_N_AXES][2];
592 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
593 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
595 if (cell->n_contents != 1
596 || cell->contents[0].n_footnotes
597 || strchr (cell->contents[0].text, ' '))
600 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
603 *min_width = *max_width;
607 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
609 struct ascii_driver *a = a_;
610 int bb[TABLE_N_AXES][2];
611 int clip[TABLE_N_AXES][2];
618 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
619 ascii_layout_cell (a, cell, bb, clip, &w, &h);
624 ascii_draw_cell (void *a_, const struct table_cell *cell,
625 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
627 struct ascii_driver *a = a_;
630 ascii_layout_cell (a, cell, bb, clip, &w, &h);
634 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
636 if (y >= a->allocated_lines)
638 size_t new_alloc = MAX (25, a->allocated_lines);
639 while (new_alloc <= y)
640 new_alloc = xtimes (new_alloc, 2);
641 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
642 for (size_t i = a->allocated_lines; i < new_alloc; i++)
643 u8_line_init (&a->lines[i]);
644 a->allocated_lines = new_alloc;
646 return u8_line_reserve (&a->lines[y], x0, x1, n);
650 text_draw (struct ascii_driver *a, unsigned int options,
651 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
652 int y, const uint8_t *string, int n, size_t width)
654 int x0 = MAX (0, clip[H][0]);
655 int y0 = MAX (0, clip[V][0]);
656 int x1 = MIN (a->width, clip[H][1]);
660 if (y < y0 || y >= y1)
663 switch (options & TAB_HALIGN)
669 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
672 x = bb[H][1] - width;
688 mblen = u8_mbtouc (&uc, string, n);
693 w = uc_width (uc, "UTF-8");
708 for (ofs = 0; ofs < n; )
714 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
716 w = uc_width (uc, "UTF-8");
719 if (width + w > x1 - x)
730 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
731 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
739 /* First figure out how many bytes need to be inserted. */
741 for (ofs = 0; ofs < n; ofs += mblen)
746 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
747 w = uc_width (uc, "UTF-8");
750 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
753 /* Then insert them. */
754 out = ascii_reserve (a, y, x, x + width, n_out);
755 for (ofs = 0; ofs < n; ofs += mblen)
760 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
761 w = uc_width (uc, "UTF-8");
765 if (a->emphasis == EMPH_UNDERLINE)
768 out = mempcpy (out, string + ofs, mblen);
771 out = mempcpy (out, string + ofs, mblen);
777 ascii_layout_cell_text (struct ascii_driver *a,
778 const struct cell_contents *contents,
779 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
790 length = strlen (contents->text);
791 if (contents->n_footnotes)
797 ds_extend (&s, length + contents->n_footnotes * 4);
798 ds_put_cstr (&s, contents->text);
799 for (i = 0; i < contents->n_footnotes; i++)
800 ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
802 length = ds_length (&s);
803 text = ds_steal_cstr (&s);
809 text = contents->text;
812 breaks = xmalloc (length + 1);
813 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
815 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
816 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
819 bb_width = bb[H][1] - bb[H][0];
820 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
822 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
823 const char *b = breaks + pos;
824 size_t n = length - pos;
826 size_t last_break_ofs = 0;
827 int last_break_width = 0;
832 for (ofs = 0; ofs < n; )
838 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
839 if (b[ofs] == UC_BREAK_MANDATORY)
841 else if (b[ofs] == UC_BREAK_POSSIBLE)
843 last_break_ofs = ofs;
844 last_break_width = width;
847 w = uc_width (uc, "UTF-8");
850 if (width + w > bb_width)
852 if (isspace (line[ofs]))
854 else if (last_break_ofs != 0)
856 ofs = last_break_ofs;
857 width = last_break_width;
866 /* Trim any trailing spaces off the end of the text to be drawn. */
867 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
868 if (!isspace (line[graph_ofs - 1]))
870 width -= ofs - graph_ofs;
873 text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
875 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
876 past any spaces past the end of the line (but not past a new-line). */
877 if (b[ofs] == UC_BREAK_MANDATORY)
880 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
889 if (text != contents->text)
890 free (CONST_CAST (char *, text));
896 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
897 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
898 int *widthp, int *heightp)
900 int bb[TABLE_N_AXES][2];
906 memcpy (bb, bb_, sizeof bb);
907 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
909 const struct cell_contents *contents = &cell->contents[i];
911 /* Put a blank line between contents. */
915 if (bb[V][0] >= bb[V][1])
919 bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp);
921 *heightp = bb[V][0] - bb_[V][0];
925 ascii_test_write (struct output_driver *driver,
926 const char *s, int x, int y, unsigned int options)
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 cell_contents contents = {
936 .options = options | TAB_LEFT,
937 .text = CONST_CAST (char *, s),
940 struct table_cell cell = {
941 .contents = &contents,
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));