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 /* ASCII output driver. */
178 struct output_driver driver;
180 /* User parameters. */
181 bool append; /* Append if output file already exists? */
182 bool emphasis; /* Enable bold and underline in output? */
183 char *chart_file_name; /* Name of files used for charts. */
186 /* Colours for charts */
191 int width; /* Page width. */
192 bool auto_width; /* Use viewwidth as page width? */
194 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
196 const ucs4_t *box; /* Line & box drawing characters. */
198 /* Internal state. */
199 struct file_handle *handle;
200 FILE *file; /* Output file. */
201 bool error; /* Output error? */
202 struct u8_line *lines; /* Page content. */
203 int allocated_lines; /* Number of lines allocated. */
204 int chart_cnt; /* Number of charts so far. */
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 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 *,
223 int bb[TABLE_N_AXES][2],
224 int spill[TABLE_N_AXES][2],
225 int clip[TABLE_N_AXES][2]);
227 static struct ascii_driver *
228 ascii_driver_cast (struct output_driver *driver)
230 assert (driver->class == &ascii_driver_class);
231 return UP_CAST (driver, struct ascii_driver, driver);
234 static struct driver_option *
235 opt (struct output_driver *d, struct string_map *options, const char *key,
236 const char *default_value)
238 return driver_option_get (d, options, key, default_value);
241 static struct output_driver *
242 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
243 struct string_map *o)
245 enum { BOX_ASCII, BOX_UNICODE } box;
246 int min_break[TABLE_N_AXES];
247 struct output_driver *d;
248 struct ascii_driver *a;
250 a = xzalloc (sizeof *a);
252 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
253 a->append = parse_boolean (opt (d, o, "append", "false"));
254 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
256 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
259 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
261 a->width = parse_page_size (opt (d, o, "width", "79"));
262 a->auto_width = a->width < 0;
263 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
265 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
266 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
268 box = parse_enum (opt (d, o, "box", "ascii"),
270 "unicode", BOX_UNICODE,
272 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
277 a->allocated_lines = 0;
280 if (!update_page_size (a, true))
286 output_driver_destroy (d);
291 parse_page_size (struct driver_option *option)
293 int dim = atol (option->default_value);
295 if (option->value != NULL)
297 if (!strcmp (option->value, "auto"))
305 value = strtol (option->value, &tail, 0);
306 if (dim >= 1 && errno != ERANGE && *tail == '\0')
309 msg (MW, _("%s: %s must be positive integer or `auto'"),
310 option->driver_name, option->name);
314 driver_option_destroy (option);
319 /* Re-calculates the page width based on settings, margins, and, if "auto" is
320 set, the size of the user's terminal window or GUI output window. */
322 update_page_size (struct ascii_driver *a, bool issue_error)
324 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
328 a->width = settings_get_viewwidth ();
329 a->min_break[H] = a->width / 2;
332 if (a->width < MIN_WIDTH)
336 _("ascii: page must be at least %d characters wide, but "
337 "as configured is only %d characters"),
340 if (a->width < MIN_WIDTH)
341 a->width = MIN_WIDTH;
349 ascii_destroy (struct output_driver *driver)
351 struct ascii_driver *a = ascii_driver_cast (driver);
355 fn_close (a->handle, a->file);
356 fh_unref (a->handle);
357 free (a->chart_file_name);
358 for (i = 0; i < a->allocated_lines; i++)
359 u8_line_destroy (&a->lines[i]);
365 ascii_flush (struct output_driver *driver)
367 struct ascii_driver *a = ascii_driver_cast (driver);
373 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
375 for (size_t y = 0; y < n_lines; y++)
377 struct u8_line *line = &a->lines[y];
379 while (ds_chomp_byte (&line->s, ' '))
381 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
382 putc ('\n', a->file);
384 u8_line_clear (&a->lines[y]);
389 ascii_output_table_item (struct ascii_driver *a,
390 const struct table_item *table_item)
392 struct render_params params;
393 struct render_pager *p;
396 update_page_size (a, false);
398 params.draw_line = ascii_draw_line;
399 params.measure_cell_width = ascii_measure_cell_width;
400 params.measure_cell_height = ascii_measure_cell_height;
401 params.adjust_break = NULL;
402 params.draw_cell = ascii_draw_cell;
404 params.size[H] = a->width;
405 params.size[V] = INT_MAX;
406 params.font_size[H] = 1;
407 params.font_size[V] = 1;
408 for (i = 0; i < RENDER_N_LINES; i++)
410 int width = i == RENDER_LINE_NONE ? 0 : 1;
411 params.line_widths[H][i] = width;
412 params.line_widths[V][i] = width;
414 for (i = 0; i < TABLE_N_AXES; i++)
415 params.min_break[i] = a->min_break[i];
416 params.supports_margins = false;
419 putc ('\n', a->file);
420 else if (!ascii_open_page (a))
423 p = render_pager_create (¶ms, table_item);
424 for (int i = 0; render_pager_has_next (p); i++)
427 putc ('\n', a->file);
428 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
430 render_pager_destroy (p);
434 ascii_output_text (struct ascii_driver *a, const char *text)
436 struct table_item *table_item;
438 table_item = table_item_create (table_from_string (TAB_LEFT, text),
440 ascii_output_table_item (a, table_item);
441 table_item_unref (table_item);
445 ascii_submit (struct output_driver *driver,
446 const struct output_item *output_item)
448 struct ascii_driver *a = ascii_driver_cast (driver);
453 if (is_table_item (output_item))
454 ascii_output_table_item (a, to_table_item (output_item));
456 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
458 struct chart_item *chart_item = to_chart_item (output_item);
461 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
465 if (file_name != NULL)
467 struct text_item *text_item;
469 text_item = text_item_create_format (
470 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
472 ascii_submit (driver, &text_item->output_item);
473 text_item_unref (text_item);
477 #endif /* HAVE_CAIRO */
478 else if (is_text_item (output_item))
480 const struct text_item *text_item = to_text_item (output_item);
481 enum text_item_type type = text_item_get_type (text_item);
482 const char *text = text_item_get_text (text_item);
486 case TEXT_ITEM_TITLE:
487 case TEXT_ITEM_SUBTITLE:
488 case TEXT_ITEM_COMMAND_OPEN:
489 case TEXT_ITEM_COMMAND_CLOSE:
492 case TEXT_ITEM_BLANK_LINE:
495 case TEXT_ITEM_EJECT_PAGE:
499 ascii_output_text (a, text);
503 else if (is_message_item (output_item))
505 const struct message_item *message_item = to_message_item (output_item);
506 const struct msg *msg = message_item_get_msg (message_item);
507 char *s = msg_to_string (msg, message_item->command_name);
508 ascii_output_text (a, s);
513 const struct output_driver_factory txt_driver_factory =
514 { "txt", "-", ascii_create };
515 const struct output_driver_factory list_driver_factory =
516 { "list", "-", ascii_create };
518 static const struct output_driver_class ascii_driver_class =
526 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
528 static void ascii_layout_cell (struct ascii_driver *,
529 const struct table_cell *,
530 int bb[TABLE_N_AXES][2],
531 int clip[TABLE_N_AXES][2],
532 int *width, int *height);
535 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
536 enum render_line_style styles[TABLE_N_AXES][2])
538 struct ascii_driver *a = a_;
545 /* Clip to the page. */
546 x0 = MAX (bb[H][0], 0);
547 y0 = MAX (bb[V][0], 0);
548 x1 = MIN (bb[H][1], a->width);
550 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
554 uc = a->box[make_box_index (styles[V][0], styles[V][1],
555 styles[H][0], styles[H][1])];
556 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
557 for (y = y0; y < y1; y++)
559 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
560 for (x = x0; x < x1; x++)
562 memcpy (p, mbchar, mblen);
569 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
570 int *min_width, int *max_width)
572 struct ascii_driver *a = a_;
573 int bb[TABLE_N_AXES][2];
574 int clip[TABLE_N_AXES][2];
581 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
582 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
584 if (cell->n_contents != 1
585 || cell->contents[0].n_footnotes
586 || strchr (cell->contents[0].text, ' '))
589 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
592 *min_width = *max_width;
596 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
598 struct ascii_driver *a = a_;
599 int bb[TABLE_N_AXES][2];
600 int clip[TABLE_N_AXES][2];
607 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
608 ascii_layout_cell (a, cell, bb, clip, &w, &h);
613 ascii_draw_cell (void *a_, const struct table_cell *cell,
614 int bb[TABLE_N_AXES][2],
615 int spill[TABLE_N_AXES][2] UNUSED,
616 int clip[TABLE_N_AXES][2])
618 struct ascii_driver *a = a_;
621 ascii_layout_cell (a, cell, bb, clip, &w, &h);
625 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
627 if (y >= a->allocated_lines)
629 size_t new_alloc = MAX (25, a->allocated_lines);
630 while (new_alloc <= y)
631 new_alloc = xtimes (new_alloc, 2);
632 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
633 for (size_t i = a->allocated_lines; i < new_alloc; i++)
634 u8_line_init (&a->lines[i]);
635 a->allocated_lines = new_alloc;
637 return u8_line_reserve (&a->lines[y], x0, x1, n);
641 text_draw (struct ascii_driver *a, unsigned int options,
642 bool bold, bool underline,
643 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
644 int y, const uint8_t *string, int n, size_t width)
646 int x0 = MAX (0, clip[H][0]);
647 int y0 = MAX (0, clip[V][0]);
648 int x1 = MIN (a->width, clip[H][1]);
652 if (y < y0 || y >= y1)
655 switch (options & TAB_HALIGN)
661 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
664 x = bb[H][1] - width;
680 mblen = u8_mbtouc (&uc, string, n);
685 w = uc_width (uc, "UTF-8");
700 for (ofs = 0; ofs < n; )
706 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
708 w = uc_width (uc, "UTF-8");
711 if (width + w > x1 - x)
722 if (!a->emphasis || (!bold && !underline))
723 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
731 /* First figure out how many bytes need to be inserted. */
733 for (ofs = 0; ofs < n; ofs += mblen)
738 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
739 w = uc_width (uc, "UTF-8");
750 /* Then insert them. */
751 out = ascii_reserve (a, y, x, x + width, n_out);
752 for (ofs = 0; ofs < n; ofs += mblen)
757 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
758 w = uc_width (uc, "UTF-8");
764 out = mempcpy (out, string + ofs, mblen);
773 out = mempcpy (out, string + ofs, mblen);
779 ascii_layout_cell_text (struct ascii_driver *a,
780 const struct cell_contents *contents,
781 bool bold, bool underline,
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, bold, underline,
877 bb, clip, y, line, graph_ofs, width);
879 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
880 past any spaces past the end of the line (but not past a new-line). */
881 if (b[ofs] == UC_BREAK_MANDATORY)
884 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
893 if (text != contents->text)
894 free (CONST_CAST (char *, text));
900 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
901 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
902 int *widthp, int *heightp)
904 int bb[TABLE_N_AXES][2];
910 memcpy (bb, bb_, sizeof bb);
911 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
913 const struct cell_contents *contents = &cell->contents[i];
915 /* Put a blank line between contents. */
919 if (bb[V][0] >= bb[V][1])
923 bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold,
924 cell->style->underline,
927 *heightp = bb[V][0] - bb_[V][0];
931 ascii_test_write (struct output_driver *driver,
932 const char *s, int x, int y, bool bold, bool underline)
934 struct ascii_driver *a = ascii_driver_cast (driver);
935 int bb[TABLE_N_AXES][2];
938 if (a->file == NULL && !ascii_open_page (a))
941 struct cell_contents contents = {
943 .text = CONST_CAST (char *, s),
945 struct cell_style cell_style = {
947 .underline = underline,
949 struct table_cell cell = {
950 .contents = &contents,
952 .style = &cell_style,
955 bb[TABLE_HORZ][0] = x;
956 bb[TABLE_HORZ][1] = a->width;
957 bb[TABLE_VERT][0] = y;
958 bb[TABLE_VERT][1] = INT_MAX;
960 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
964 ascii_test_set_length (struct output_driver *driver, int y, int length)
966 struct ascii_driver *a = ascii_driver_cast (driver);
968 if (a->file == NULL && !ascii_open_page (a))
973 u8_line_set_length (&a->lines[y], length);
977 ascii_test_flush (struct output_driver *driver)
979 struct ascii_driver *a = ascii_driver_cast (driver);
981 for (size_t i = a->allocated_lines; i-- > 0; )
982 if (a->lines[i].width)
984 ascii_output_lines (a, i + 1);
989 /* ascii_close_page () and support routines. */
991 #if HAVE_DECL_SIGWINCH
992 static struct ascii_driver *the_driver;
995 winch_handler (int signum UNUSED)
997 update_page_size (the_driver, false);
1002 ascii_open_page (struct ascii_driver *a)
1007 if (a->file == NULL)
1009 a->file = fn_open (a->handle, a->append ? "a" : "w");
1010 if (a->file != NULL)
1012 if ( isatty (fileno (a->file)))
1014 #if HAVE_DECL_SIGWINCH
1015 struct sigaction action;
1016 sigemptyset (&action.sa_mask);
1017 action.sa_flags = 0;
1018 action.sa_handler = winch_handler;
1020 sigaction (SIGWINCH, &action, NULL);
1022 a->auto_width = true;
1027 msg_error (errno, _("ascii: opening output file `%s'"),
1028 fh_get_file_name (a->handle));