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. */
63 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
64 * RENDER_N_LINES * RENDER_N_LINES)
66 static const ucs4_t ascii_box_chars[N_BOX] =
97 static const ucs4_t unicode_box_chars[N_BOX] =
99 0x0020, 0x2575, 0x2551,
100 0x2574, 0x256f, 0x255c,
101 0x2550, 0x255b, 0x255d,
102 0x2577, 0x2502, 0x2551,
103 0x256e, 0x2524, 0x2562,
104 0x2555, 0x2561, 0x2563,
105 0x2551, 0x2551, 0x2551,
106 0x2556, 0x2562, 0x2562,
107 0x2557, 0x2563, 0x2563,
108 0x2576, 0x2570, 0x2559,
109 0x2500, 0x2534, 0x2568,
110 0x2550, 0x2567, 0x2569,
111 0x256d, 0x251c, 0x255f,
112 0x252c, 0x253c, 0x256a,
113 0x2564, 0x256a, 0x256c,
114 0x2553, 0x255f, 0x255f,
115 0x2565, 0x256b, 0x256b,
116 0x2566, 0x256c, 0x256c,
117 0x2550, 0x2558, 0x255a,
118 0x2550, 0x2567, 0x2569,
119 0x2550, 0x2567, 0x2569,
120 0x2552, 0x255e, 0x2560,
121 0x2564, 0x256a, 0x256c,
122 0x2564, 0x256a, 0x256c,
123 0x2554, 0x2560, 0x2560,
124 0x2560, 0x256c, 0x256c,
125 0x2566, 0x256c, 0x256c,
129 make_box_index (int left, int right, int top, int bottom)
131 int start_side = left;
132 int end_side = right;
133 if (render_direction_rtl ())
139 return ((end_side * RENDER_N_LINES + bottom) * RENDER_N_LINES + start_side) * RENDER_N_LINES + top;
142 /* How to emphasize text. */
145 EMPH_BOLD, /* Overstrike for bold. */
146 EMPH_UNDERLINE, /* Overstrike for underlining. */
147 EMPH_NONE /* No emphasis. */
150 /* ASCII output driver. */
153 struct output_driver driver;
155 /* User parameters. */
156 bool append; /* Append if output file already exists? */
157 enum emphasis_style emphasis; /* How to emphasize text. */
158 char *chart_file_name; /* Name of files used for charts. */
161 /* Colours for charts */
166 int width; /* Page width. */
167 bool auto_width; /* Use viewwidth as page width? */
169 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
171 const ucs4_t *box; /* Line & box drawing characters. */
173 /* Internal state. */
174 struct file_handle *handle;
175 FILE *file; /* Output file. */
176 bool error; /* Output error? */
177 struct u8_line *lines; /* Page content. */
178 int allocated_lines; /* Number of lines allocated. */
179 int chart_cnt; /* Number of charts so far. */
182 static const struct output_driver_class ascii_driver_class;
184 static void ascii_submit (struct output_driver *, const struct output_item *);
186 static bool update_page_size (struct ascii_driver *, bool issue_error);
187 static int parse_page_size (struct driver_option *);
189 static bool ascii_open_page (struct ascii_driver *);
191 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
192 enum render_line_style styles[TABLE_N_AXES][2]);
193 static void ascii_measure_cell_width (void *, const struct table_cell *,
195 static int ascii_measure_cell_height (void *, const struct table_cell *,
197 static void ascii_draw_cell (void *, const struct table_cell *,
198 int bb[TABLE_N_AXES][2],
199 int clip[TABLE_N_AXES][2]);
201 static struct ascii_driver *
202 ascii_driver_cast (struct output_driver *driver)
204 assert (driver->class == &ascii_driver_class);
205 return UP_CAST (driver, struct ascii_driver, driver);
208 static struct driver_option *
209 opt (struct output_driver *d, struct string_map *options, const char *key,
210 const char *default_value)
212 return driver_option_get (d, options, key, default_value);
215 static struct output_driver *
216 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
217 struct string_map *o)
219 enum { BOX_ASCII, BOX_UNICODE } box;
220 int min_break[TABLE_N_AXES];
221 struct output_driver *d;
222 struct ascii_driver *a;
224 a = xzalloc (sizeof *a);
226 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
227 a->append = parse_boolean (opt (d, o, "append", "false"));
228 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
230 "underline", EMPH_UNDERLINE,
234 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
237 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
239 a->width = parse_page_size (opt (d, o, "width", "79"));
240 a->auto_width = a->width < 0;
241 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
243 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
244 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
246 box = parse_enum (opt (d, o, "box", "ascii"),
248 "unicode", BOX_UNICODE,
250 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
255 a->allocated_lines = 0;
258 if (!update_page_size (a, true))
264 output_driver_destroy (d);
269 parse_page_size (struct driver_option *option)
271 int dim = atol (option->default_value);
273 if (option->value != NULL)
275 if (!strcmp (option->value, "auto"))
283 value = strtol (option->value, &tail, 0);
284 if (dim >= 1 && errno != ERANGE && *tail == '\0')
287 msg (MW, _("%s: %s must be positive integer or `auto'"),
288 option->driver_name, option->name);
292 driver_option_destroy (option);
297 /* Re-calculates the page width based on settings, margins, and, if "auto" is
298 set, the size of the user's terminal window or GUI output window. */
300 update_page_size (struct ascii_driver *a, bool issue_error)
302 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
306 a->width = settings_get_viewwidth ();
307 a->min_break[H] = a->width / 2;
310 if (a->width < MIN_WIDTH)
314 _("ascii: page must be at least %d characters wide, but "
315 "as configured is only %d characters"),
318 if (a->width < MIN_WIDTH)
319 a->width = MIN_WIDTH;
327 ascii_destroy (struct output_driver *driver)
329 struct ascii_driver *a = ascii_driver_cast (driver);
333 fn_close (a->handle, a->file);
334 fh_unref (a->handle);
335 free (a->chart_file_name);
336 for (i = 0; i < a->allocated_lines; i++)
337 u8_line_destroy (&a->lines[i]);
343 ascii_flush (struct output_driver *driver)
345 struct ascii_driver *a = ascii_driver_cast (driver);
351 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
353 for (size_t y = 0; y < n_lines; y++)
355 struct u8_line *line = &a->lines[y];
357 while (ds_chomp_byte (&line->s, ' '))
359 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
360 putc ('\n', a->file);
362 u8_line_clear (&a->lines[y]);
367 ascii_output_table_item (struct ascii_driver *a,
368 const struct table_item *table_item)
370 struct render_params params;
371 struct render_pager *p;
374 update_page_size (a, false);
376 params.draw_line = ascii_draw_line;
377 params.measure_cell_width = ascii_measure_cell_width;
378 params.measure_cell_height = ascii_measure_cell_height;
379 params.adjust_break = NULL;
380 params.draw_cell = ascii_draw_cell;
382 params.size[H] = a->width;
383 params.size[V] = INT_MAX;
384 params.font_size[H] = 1;
385 params.font_size[V] = 1;
386 for (i = 0; i < RENDER_N_LINES; i++)
388 int width = i == RENDER_LINE_NONE ? 0 : 1;
389 params.line_widths[H][i] = width;
390 params.line_widths[V][i] = width;
392 for (i = 0; i < TABLE_N_AXES; i++)
393 params.min_break[i] = a->min_break[i];
394 params.supports_margins = false;
397 putc ('\n', a->file);
398 else if (!ascii_open_page (a))
401 p = render_pager_create (¶ms, table_item);
402 for (int i = 0; render_pager_has_next (p); i++)
405 putc ('\n', a->file);
406 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
408 render_pager_destroy (p);
412 ascii_output_text (struct ascii_driver *a, const char *text)
414 struct table_item *table_item;
416 table_item = table_item_create (table_from_string (TAB_LEFT, text),
418 ascii_output_table_item (a, table_item);
419 table_item_unref (table_item);
423 ascii_submit (struct output_driver *driver,
424 const struct output_item *output_item)
426 struct ascii_driver *a = ascii_driver_cast (driver);
431 if (is_table_item (output_item))
432 ascii_output_table_item (a, to_table_item (output_item));
434 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
436 struct chart_item *chart_item = to_chart_item (output_item);
439 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
443 if (file_name != NULL)
445 struct text_item *text_item;
447 text_item = text_item_create_format (
448 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
450 ascii_submit (driver, &text_item->output_item);
451 text_item_unref (text_item);
455 #endif /* HAVE_CAIRO */
456 else if (is_text_item (output_item))
458 const struct text_item *text_item = to_text_item (output_item);
459 enum text_item_type type = text_item_get_type (text_item);
460 const char *text = text_item_get_text (text_item);
464 case TEXT_ITEM_TITLE:
465 case TEXT_ITEM_SUBTITLE:
466 case TEXT_ITEM_COMMAND_OPEN:
467 case TEXT_ITEM_COMMAND_CLOSE:
470 case TEXT_ITEM_BLANK_LINE:
473 case TEXT_ITEM_EJECT_PAGE:
477 ascii_output_text (a, text);
481 else if (is_message_item (output_item))
483 const struct message_item *message_item = to_message_item (output_item);
484 const struct msg *msg = message_item_get_msg (message_item);
485 char *s = msg_to_string (msg, message_item->command_name);
486 ascii_output_text (a, s);
491 const struct output_driver_factory txt_driver_factory =
492 { "txt", "-", ascii_create };
493 const struct output_driver_factory list_driver_factory =
494 { "list", "-", ascii_create };
496 static const struct output_driver_class ascii_driver_class =
504 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
506 static void ascii_layout_cell (struct ascii_driver *,
507 const struct table_cell *,
508 int bb[TABLE_N_AXES][2],
509 int clip[TABLE_N_AXES][2],
510 int *width, int *height);
513 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
514 enum render_line_style styles[TABLE_N_AXES][2])
516 struct ascii_driver *a = a_;
523 /* Clip to the page. */
524 x0 = MAX (bb[H][0], 0);
525 y0 = MAX (bb[V][0], 0);
526 x1 = MIN (bb[H][1], a->width);
528 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
532 uc = a->box[make_box_index (styles[V][0], styles[V][1],
533 styles[H][0], styles[H][1])];
534 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
535 for (y = y0; y < y1; y++)
537 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
538 for (x = x0; x < x1; x++)
540 memcpy (p, mbchar, mblen);
547 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
548 int *min_width, int *max_width)
550 struct ascii_driver *a = a_;
551 int bb[TABLE_N_AXES][2];
552 int clip[TABLE_N_AXES][2];
559 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
560 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
562 if (cell->n_contents != 1
563 || cell->contents[0].n_footnotes
564 || strchr (cell->contents[0].text, ' '))
567 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
570 *min_width = *max_width;
574 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
576 struct ascii_driver *a = a_;
577 int bb[TABLE_N_AXES][2];
578 int clip[TABLE_N_AXES][2];
585 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
586 ascii_layout_cell (a, cell, bb, clip, &w, &h);
591 ascii_draw_cell (void *a_, const struct table_cell *cell,
592 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
594 struct ascii_driver *a = a_;
597 ascii_layout_cell (a, cell, bb, clip, &w, &h);
601 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
603 if (y >= a->allocated_lines)
605 size_t new_alloc = MAX (25, a->allocated_lines);
606 while (new_alloc <= y)
607 new_alloc = xtimes (new_alloc, 2);
608 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
609 for (size_t i = a->allocated_lines; i < new_alloc; i++)
610 u8_line_init (&a->lines[i]);
611 a->allocated_lines = new_alloc;
613 return u8_line_reserve (&a->lines[y], x0, x1, n);
617 text_draw (struct ascii_driver *a, unsigned int options,
618 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
619 int y, const uint8_t *string, int n, size_t width)
621 int x0 = MAX (0, clip[H][0]);
622 int y0 = MAX (0, clip[V][0]);
623 int x1 = MIN (a->width, clip[H][1]);
627 if (y < y0 || y >= y1)
630 switch (options & TAB_HALIGN)
636 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
639 x = bb[H][1] - width;
655 mblen = u8_mbtouc (&uc, string, n);
660 w = uc_width (uc, "UTF-8");
675 for (ofs = 0; ofs < n; )
681 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
683 w = uc_width (uc, "UTF-8");
686 if (width + w > x1 - x)
697 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
698 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
706 /* First figure out how many bytes need to be inserted. */
708 for (ofs = 0; ofs < n; ofs += mblen)
713 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
714 w = uc_width (uc, "UTF-8");
717 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
720 /* Then insert them. */
721 out = ascii_reserve (a, y, x, x + width, n_out);
722 for (ofs = 0; ofs < n; ofs += mblen)
727 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
728 w = uc_width (uc, "UTF-8");
732 if (a->emphasis == EMPH_UNDERLINE)
735 out = mempcpy (out, string + ofs, mblen);
738 out = mempcpy (out, string + ofs, mblen);
744 ascii_layout_cell_text (struct ascii_driver *a,
745 const struct cell_contents *contents,
746 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
757 length = strlen (contents->text);
758 if (contents->n_footnotes)
764 ds_extend (&s, length + contents->n_footnotes * 4);
765 ds_put_cstr (&s, contents->text);
766 for (i = 0; i < contents->n_footnotes; i++)
767 ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
769 length = ds_length (&s);
770 text = ds_steal_cstr (&s);
776 text = contents->text;
779 breaks = xmalloc (length + 1);
780 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
782 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
783 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
786 bb_width = bb[H][1] - bb[H][0];
787 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
789 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
790 const char *b = breaks + pos;
791 size_t n = length - pos;
793 size_t last_break_ofs = 0;
794 int last_break_width = 0;
799 for (ofs = 0; ofs < n; )
805 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
806 if (b[ofs] == UC_BREAK_MANDATORY)
808 else if (b[ofs] == UC_BREAK_POSSIBLE)
810 last_break_ofs = ofs;
811 last_break_width = width;
814 w = uc_width (uc, "UTF-8");
817 if (width + w > bb_width)
819 if (isspace (line[ofs]))
821 else if (last_break_ofs != 0)
823 ofs = last_break_ofs;
824 width = last_break_width;
833 /* Trim any trailing spaces off the end of the text to be drawn. */
834 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
835 if (!isspace (line[graph_ofs - 1]))
837 width -= ofs - graph_ofs;
840 text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
842 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
843 past any spaces past the end of the line (but not past a new-line). */
844 if (b[ofs] == UC_BREAK_MANDATORY)
847 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
856 if (text != contents->text)
857 free (CONST_CAST (char *, text));
863 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
864 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
865 int *widthp, int *heightp)
867 int bb[TABLE_N_AXES][2];
873 memcpy (bb, bb_, sizeof bb);
874 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
876 const struct cell_contents *contents = &cell->contents[i];
878 /* Put a blank line between contents. */
882 if (bb[V][0] >= bb[V][1])
886 bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp);
888 *heightp = bb[V][0] - bb_[V][0];
892 ascii_test_write (struct output_driver *driver,
893 const char *s, int x, int y, unsigned int options)
895 struct ascii_driver *a = ascii_driver_cast (driver);
896 int bb[TABLE_N_AXES][2];
899 if (a->file == NULL && !ascii_open_page (a))
902 struct cell_contents contents = {
903 .options = options | TAB_LEFT,
904 .text = CONST_CAST (char *, s),
907 struct table_cell cell = {
908 .contents = &contents,
912 bb[TABLE_HORZ][0] = x;
913 bb[TABLE_HORZ][1] = a->width;
914 bb[TABLE_VERT][0] = y;
915 bb[TABLE_VERT][1] = INT_MAX;
917 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
921 ascii_test_set_length (struct output_driver *driver, int y, int length)
923 struct ascii_driver *a = ascii_driver_cast (driver);
925 if (a->file == NULL && !ascii_open_page (a))
930 u8_line_set_length (&a->lines[y], length);
934 ascii_test_flush (struct output_driver *driver)
936 struct ascii_driver *a = ascii_driver_cast (driver);
938 for (size_t i = a->allocated_lines; i-- > 0; )
939 if (a->lines[i].width)
941 ascii_output_lines (a, i + 1);
946 /* ascii_close_page () and support routines. */
948 #if HAVE_DECL_SIGWINCH
949 static struct ascii_driver *the_driver;
952 winch_handler (int signum UNUSED)
954 update_page_size (the_driver, false);
959 ascii_open_page (struct ascii_driver *a)
966 a->file = fn_open (a->handle, a->append ? "a" : "w");
969 if ( isatty (fileno (a->file)))
971 #if HAVE_DECL_SIGWINCH
972 struct sigaction action;
973 sigemptyset (&action.sa_mask);
975 action.sa_handler = winch_handler;
977 sigaction (SIGWINCH, &action, NULL);
979 a->auto_width = true;
984 msg_error (errno, _("ascii: opening output file `%s'"),
985 fh_get_file_name (a->handle));