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. */
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 bool ascii_open_page (struct ascii_driver *);
217 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
218 enum render_line_style styles[TABLE_N_AXES][2],
219 struct cell_color colors[TABLE_N_AXES][2]);
220 static void ascii_measure_cell_width (void *, const struct table_cell *,
222 static int ascii_measure_cell_height (void *, const struct table_cell *,
224 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
225 int bb[TABLE_N_AXES][2],
226 int spill[TABLE_N_AXES][2],
227 int clip[TABLE_N_AXES][2]);
229 static struct ascii_driver *
230 ascii_driver_cast (struct output_driver *driver)
232 assert (driver->class == &ascii_driver_class);
233 return UP_CAST (driver, struct ascii_driver, driver);
236 static struct driver_option *
237 opt (struct output_driver *d, struct string_map *options, const char *key,
238 const char *default_value)
240 return driver_option_get (d, options, key, default_value);
243 static struct output_driver *
244 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
245 struct string_map *o)
247 enum { BOX_ASCII, BOX_UNICODE } box;
248 int min_break[TABLE_N_AXES];
249 struct output_driver *d;
250 struct ascii_driver *a;
252 a = xzalloc (sizeof *a);
254 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
255 a->append = parse_boolean (opt (d, o, "append", "false"));
256 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
258 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
261 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
263 a->width = parse_page_size (opt (d, o, "width", "79"));
264 a->auto_width = a->width < 0;
265 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
267 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
268 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
270 box = parse_enum (opt (d, o, "box", "ascii"),
272 "unicode", BOX_UNICODE,
274 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
279 a->allocated_lines = 0;
282 a->params.draw_line = ascii_draw_line;
283 a->params.measure_cell_width = ascii_measure_cell_width;
284 a->params.measure_cell_height = ascii_measure_cell_height;
285 a->params.adjust_break = NULL;
286 a->params.draw_cell = ascii_draw_cell;
288 a->params.size[H] = a->width;
289 a->params.size[V] = INT_MAX;
290 a->params.font_size[H] = 1;
291 a->params.font_size[V] = 1;
292 for (int i = 0; i < RENDER_N_LINES; i++)
294 int width = i == RENDER_LINE_NONE ? 0 : 1;
295 a->params.line_widths[H][i] = width;
296 a->params.line_widths[V][i] = width;
298 for (int i = 0; i < TABLE_N_AXES; i++)
299 a->params.min_break[i] = a->min_break[i];
300 a->params.supports_margins = false;
301 a->params.rtl = render_direction_rtl ();
303 if (!update_page_size (a, true))
309 output_driver_destroy (d);
314 parse_page_size (struct driver_option *option)
316 int dim = atol (option->default_value);
318 if (option->value != NULL)
320 if (!strcmp (option->value, "auto"))
328 value = strtol (option->value, &tail, 0);
329 if (dim >= 1 && errno != ERANGE && *tail == '\0')
332 msg (MW, _("%s: %s must be positive integer or `auto'"),
333 option->driver_name, option->name);
337 driver_option_destroy (option);
342 /* Re-calculates the page width based on settings, margins, and, if "auto" is
343 set, the size of the user's terminal window or GUI output window. */
345 update_page_size (struct ascii_driver *a, bool issue_error)
347 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
351 a->params.size[H] = a->width = settings_get_viewwidth ();
352 a->params.min_break[H] = a->min_break[H] = a->width / 2;
355 if (a->width < MIN_WIDTH)
359 _("ascii: page must be at least %d characters wide, but "
360 "as configured is only %d characters"),
363 if (a->width < MIN_WIDTH)
364 a->params.size[H] = a->width = MIN_WIDTH;
372 ascii_destroy (struct output_driver *driver)
374 struct ascii_driver *a = ascii_driver_cast (driver);
378 fn_close (a->handle, a->file);
379 fh_unref (a->handle);
380 free (a->chart_file_name);
381 for (i = 0; i < a->allocated_lines; i++)
382 u8_line_destroy (&a->lines[i]);
388 ascii_flush (struct output_driver *driver)
390 struct ascii_driver *a = ascii_driver_cast (driver);
396 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
398 for (size_t y = 0; y < n_lines; y++)
400 struct u8_line *line = &a->lines[y];
402 while (ds_chomp_byte (&line->s, ' '))
404 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
405 putc ('\n', a->file);
407 u8_line_clear (&a->lines[y]);
412 ascii_output_table_item (struct ascii_driver *a,
413 const struct table_item *table_item)
415 struct render_pager *p;
417 update_page_size (a, false);
420 putc ('\n', a->file);
421 else if (!ascii_open_page (a))
424 p = render_pager_create (&a->params, table_item);
425 for (int i = 0; render_pager_has_next (p); i++)
428 putc ('\n', a->file);
429 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
431 render_pager_destroy (p);
435 ascii_output_text (struct ascii_driver *a, const char *text)
437 struct table_item *table_item;
439 table_item = table_item_create (table_from_string (TAB_LEFT, text),
441 ascii_output_table_item (a, table_item);
442 table_item_unref (table_item);
446 ascii_submit (struct output_driver *driver,
447 const struct output_item *output_item)
449 struct ascii_driver *a = ascii_driver_cast (driver);
454 if (is_table_item (output_item))
455 ascii_output_table_item (a, to_table_item (output_item));
457 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
459 struct chart_item *chart_item = to_chart_item (output_item);
462 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
466 if (file_name != NULL)
468 struct text_item *text_item;
470 text_item = text_item_create_format (
471 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
473 ascii_submit (driver, &text_item->output_item);
474 text_item_unref (text_item);
478 #endif /* HAVE_CAIRO */
479 else if (is_text_item (output_item))
481 const struct text_item *text_item = to_text_item (output_item);
482 enum text_item_type type = text_item_get_type (text_item);
486 case TEXT_ITEM_PAGE_TITLE:
487 case TEXT_ITEM_BLANK_LINE:
490 case TEXT_ITEM_EJECT_PAGE:
494 ascii_output_table_item (a, text_item_to_table_item (text_item_ref (text_item)));
498 else if (is_message_item (output_item))
500 const struct message_item *message_item = to_message_item (output_item);
501 char *s = msg_to_string (message_item_get_msg (message_item));
502 ascii_output_text (a, s);
507 const struct output_driver_factory txt_driver_factory =
508 { "txt", "-", ascii_create };
509 const struct output_driver_factory list_driver_factory =
510 { "list", "-", ascii_create };
512 static const struct output_driver_class ascii_driver_class =
520 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
522 static void ascii_layout_cell (struct ascii_driver *,
523 const struct table_cell *,
524 int bb[TABLE_N_AXES][2],
525 int clip[TABLE_N_AXES][2],
526 int *width, int *height);
529 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
530 enum render_line_style styles[TABLE_N_AXES][2],
531 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
533 struct ascii_driver *a = a_;
540 /* Clip to the page. */
541 x0 = MAX (bb[H][0], 0);
542 y0 = MAX (bb[V][0], 0);
543 x1 = MIN (bb[H][1], a->width);
545 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
549 uc = a->box[make_box_index (styles[V][0], styles[V][1],
550 styles[H][0], styles[H][1])];
551 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
552 for (y = y0; y < y1; y++)
554 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
555 for (x = x0; x < x1; x++)
557 memcpy (p, mbchar, mblen);
564 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
565 int *min_width, int *max_width)
567 struct ascii_driver *a = a_;
568 int bb[TABLE_N_AXES][2];
569 int clip[TABLE_N_AXES][2];
576 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
577 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
579 if (cell->n_contents != 1
580 || cell->contents[0].n_footnotes
581 || strchr (cell->contents[0].text, ' '))
584 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
587 *min_width = *max_width;
591 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
593 struct ascii_driver *a = a_;
594 int bb[TABLE_N_AXES][2];
595 int clip[TABLE_N_AXES][2];
602 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
603 ascii_layout_cell (a, cell, bb, clip, &w, &h);
608 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
609 int bb[TABLE_N_AXES][2],
610 int spill[TABLE_N_AXES][2] UNUSED,
611 int clip[TABLE_N_AXES][2])
613 struct ascii_driver *a = a_;
616 ascii_layout_cell (a, cell, bb, clip, &w, &h);
620 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
622 if (y >= a->allocated_lines)
624 size_t new_alloc = MAX (25, a->allocated_lines);
625 while (new_alloc <= y)
626 new_alloc = xtimes (new_alloc, 2);
627 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
628 for (size_t i = a->allocated_lines; i < new_alloc; i++)
629 u8_line_init (&a->lines[i]);
630 a->allocated_lines = new_alloc;
632 return u8_line_reserve (&a->lines[y], x0, x1, n);
636 text_draw (struct ascii_driver *a, unsigned int options,
637 bool bold, bool underline,
638 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
639 int y, const uint8_t *string, int n, size_t width)
641 int x0 = MAX (0, clip[H][0]);
642 int y0 = MAX (0, clip[V][0]);
643 int x1 = MIN (a->width, clip[H][1]);
647 if (y < y0 || y >= y1)
650 switch (options & TAB_HALIGN)
656 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
659 x = bb[H][1] - width;
675 mblen = u8_mbtouc (&uc, string, n);
680 w = uc_width (uc, "UTF-8");
695 for (ofs = 0; ofs < n; )
701 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
703 w = uc_width (uc, "UTF-8");
706 if (width + w > x1 - x)
717 if (!a->emphasis || (!bold && !underline))
718 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
726 /* First figure out how many bytes need to be inserted. */
728 for (ofs = 0; ofs < n; ofs += mblen)
733 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
734 w = uc_width (uc, "UTF-8");
745 /* Then insert them. */
746 out = ascii_reserve (a, y, x, x + width, n_out);
747 for (ofs = 0; ofs < n; ofs += mblen)
752 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
753 w = uc_width (uc, "UTF-8");
759 out = mempcpy (out, string + ofs, mblen);
768 out = mempcpy (out, string + ofs, mblen);
774 add_footnote_markers (const char *text, const struct cell_contents *contents)
776 struct string s = DS_EMPTY_INITIALIZER;
777 ds_put_cstr (&s, text);
778 for (size_t i = 0; i < contents->n_footnotes; i++)
779 ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
780 return ds_steal_cstr (&s);
784 ascii_layout_cell_text (struct ascii_driver *a,
785 const struct cell_contents *contents,
786 bool bold, bool underline,
787 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
796 /* Get the basic textual contents. */
797 const char *plain_text = (contents->options & TAB_MARKUP
798 ? output_get_text_from_markup (contents->text)
801 /* Append footnote markers if any. */
803 if (contents->n_footnotes)
805 text = add_footnote_markers (plain_text, contents);
806 if (plain_text != contents->text)
807 free (CONST_CAST (char *, plain_text));
812 /* Calculate length; if it's zero, then there's nothing to do. */
813 size_t length = strlen (text);
816 if (text != contents->text)
817 free (CONST_CAST (char *, text));
821 breaks = xmalloc (length + 1);
822 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
824 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
825 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
828 bb_width = bb[H][1] - bb[H][0];
829 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
831 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
832 const char *b = breaks + pos;
833 size_t n = length - pos;
835 size_t last_break_ofs = 0;
836 int last_break_width = 0;
841 for (ofs = 0; ofs < n; )
847 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
848 if (b[ofs] == UC_BREAK_MANDATORY)
850 else if (b[ofs] == UC_BREAK_POSSIBLE)
852 last_break_ofs = ofs;
853 last_break_width = width;
856 w = uc_width (uc, "UTF-8");
859 if (width + w > bb_width)
861 if (isspace (line[ofs]))
863 else if (last_break_ofs != 0)
865 ofs = last_break_ofs;
866 width = last_break_width;
875 /* Trim any trailing spaces off the end of the text to be drawn. */
876 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
877 if (!isspace (line[graph_ofs - 1]))
879 width -= ofs - graph_ofs;
882 text_draw (a, contents->options, bold, underline,
883 bb, clip, y, line, graph_ofs, width);
885 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
886 past any spaces past the end of the line (but not past a new-line). */
887 if (b[ofs] == UC_BREAK_MANDATORY)
890 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
899 if (text != contents->text)
900 free (CONST_CAST (char *, text));
906 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
907 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
908 int *widthp, int *heightp)
910 int bb[TABLE_N_AXES][2];
916 memcpy (bb, bb_, sizeof bb);
917 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
919 const struct cell_contents *contents = &cell->contents[i];
921 /* Put a blank line between contents. */
925 if (bb[V][0] >= bb[V][1])
929 bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold,
930 cell->style->underline,
933 *heightp = bb[V][0] - bb_[V][0];
937 ascii_test_write (struct output_driver *driver,
938 const char *s, int x, int y, bool bold, bool underline)
940 struct ascii_driver *a = ascii_driver_cast (driver);
941 int bb[TABLE_N_AXES][2];
944 if (a->file == NULL && !ascii_open_page (a))
947 struct cell_contents contents = {
949 .text = CONST_CAST (char *, s),
951 struct cell_style cell_style = {
953 .underline = underline,
955 struct table_cell cell = {
956 .contents = &contents,
958 .style = &cell_style,
961 bb[TABLE_HORZ][0] = x;
962 bb[TABLE_HORZ][1] = a->width;
963 bb[TABLE_VERT][0] = y;
964 bb[TABLE_VERT][1] = INT_MAX;
966 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
970 ascii_test_set_length (struct output_driver *driver, int y, int length)
972 struct ascii_driver *a = ascii_driver_cast (driver);
974 if (a->file == NULL && !ascii_open_page (a))
979 u8_line_set_length (&a->lines[y], length);
983 ascii_test_flush (struct output_driver *driver)
985 struct ascii_driver *a = ascii_driver_cast (driver);
987 for (size_t i = a->allocated_lines; i-- > 0; )
988 if (a->lines[i].width)
990 ascii_output_lines (a, i + 1);
995 /* ascii_close_page () and support routines. */
997 #if HAVE_DECL_SIGWINCH
998 static struct ascii_driver *the_driver;
1001 winch_handler (int signum UNUSED)
1003 update_page_size (the_driver, false);
1008 ascii_open_page (struct ascii_driver *a)
1013 if (a->file == NULL)
1015 a->file = fn_open (a->handle, a->append ? "a" : "w");
1016 if (a->file != NULL)
1018 if ( isatty (fileno (a->file)))
1020 #if HAVE_DECL_SIGWINCH
1021 struct sigaction action;
1022 sigemptyset (&action.sa_mask);
1023 action.sa_flags = 0;
1024 action.sa_handler = winch_handler;
1026 sigaction (SIGWINCH, &action, NULL);
1028 a->auto_width = true;
1033 msg_error (errno, _("ascii: opening output file `%s'"),
1034 fh_get_file_name (a->handle));