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. */
175 struct file_handle *handle;
176 FILE *file; /* Output file. */
177 bool error; /* Output error? */
178 struct u8_line *lines; /* Page content. */
179 int allocated_lines; /* Number of lines allocated. */
180 int chart_cnt; /* Number of charts so far. */
183 static const struct output_driver_class ascii_driver_class;
185 static void ascii_submit (struct output_driver *, const struct output_item *);
187 static bool update_page_size (struct ascii_driver *, bool issue_error);
188 static int parse_page_size (struct driver_option *);
190 static bool ascii_open_page (struct ascii_driver *);
192 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
193 enum render_line_style styles[TABLE_N_AXES][2]);
194 static void ascii_measure_cell_width (void *, const struct table_cell *,
195 int footnote_idx, int *min, int *max);
196 static int ascii_measure_cell_height (void *, const struct table_cell *,
197 int footnote_idx, int width);
198 static void ascii_draw_cell (void *, const struct table_cell *,
199 int footnote_idx, int bb[TABLE_N_AXES][2],
200 int clip[TABLE_N_AXES][2]);
202 static struct ascii_driver *
203 ascii_driver_cast (struct output_driver *driver)
205 assert (driver->class == &ascii_driver_class);
206 return UP_CAST (driver, struct ascii_driver, driver);
209 static struct driver_option *
210 opt (struct output_driver *d, struct string_map *options, const char *key,
211 const char *default_value)
213 return driver_option_get (d, options, key, default_value);
216 static struct output_driver *
217 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
218 struct string_map *o)
220 enum { BOX_ASCII, BOX_UNICODE } box;
221 int min_break[TABLE_N_AXES];
222 struct output_driver *d;
223 struct ascii_driver *a;
225 a = xzalloc (sizeof *a);
227 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
228 a->append = parse_boolean (opt (d, o, "append", "false"));
229 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
231 "underline", EMPH_UNDERLINE,
235 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
238 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
240 a->width = parse_page_size (opt (d, o, "width", "79"));
241 a->auto_width = a->width < 0;
242 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
244 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
245 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
247 box = parse_enum (opt (d, o, "box", "ascii"),
249 "unicode", BOX_UNICODE,
251 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
253 a->command_name = NULL;
257 a->allocated_lines = 0;
260 if (!update_page_size (a, true))
266 output_driver_destroy (d);
271 parse_page_size (struct driver_option *option)
273 int dim = atol (option->default_value);
275 if (option->value != NULL)
277 if (!strcmp (option->value, "auto"))
285 value = strtol (option->value, &tail, 0);
286 if (dim >= 1 && errno != ERANGE && *tail == '\0')
289 msg (MW, _("%s: %s must be positive integer or `auto'"),
290 option->driver_name, option->name);
294 driver_option_destroy (option);
299 /* Re-calculates the page width based on settings, margins, and, if "auto" is
300 set, the size of the user's terminal window or GUI output window. */
302 update_page_size (struct ascii_driver *a, bool issue_error)
304 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
308 a->width = settings_get_viewwidth ();
309 a->min_break[H] = a->width / 2;
312 if (a->width < MIN_WIDTH)
316 _("ascii: page must be at least %d characters wide, but "
317 "as configured is only %d characters"),
320 if (a->width < MIN_WIDTH)
321 a->width = MIN_WIDTH;
329 ascii_destroy (struct output_driver *driver)
331 struct ascii_driver *a = ascii_driver_cast (driver);
335 fn_close (a->handle, a->file);
336 fh_unref (a->handle);
337 free (a->command_name);
338 free (a->chart_file_name);
339 for (i = 0; i < a->allocated_lines; i++)
340 u8_line_destroy (&a->lines[i]);
346 ascii_flush (struct output_driver *driver)
348 struct ascii_driver *a = ascii_driver_cast (driver);
354 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
356 for (size_t y = 0; y < n_lines; y++)
358 struct u8_line *line = &a->lines[y];
360 while (ds_chomp_byte (&line->s, ' '))
362 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
363 putc ('\n', a->file);
365 u8_line_clear (&a->lines[y]);
370 ascii_output_table_item (struct ascii_driver *a,
371 const struct table_item *table_item)
373 struct render_params params;
374 struct render_pager *p;
377 update_page_size (a, false);
379 params.draw_line = ascii_draw_line;
380 params.measure_cell_width = ascii_measure_cell_width;
381 params.measure_cell_height = ascii_measure_cell_height;
382 params.adjust_break = NULL;
383 params.draw_cell = ascii_draw_cell;
385 params.size[H] = a->width;
386 params.size[V] = INT_MAX;
387 params.font_size[H] = 1;
388 params.font_size[V] = 1;
389 for (i = 0; i < RENDER_N_LINES; i++)
391 int width = i == RENDER_LINE_NONE ? 0 : 1;
392 params.line_widths[H][i] = width;
393 params.line_widths[V][i] = width;
395 for (i = 0; i < TABLE_N_AXES; i++)
396 params.min_break[i] = a->min_break[i];
397 params.supports_margins = false;
400 putc ('\n', a->file);
401 else if (!ascii_open_page (a))
404 p = render_pager_create (¶ms, table_item);
405 for (int i = 0; render_pager_has_next (p); i++)
408 putc ('\n', a->file);
409 ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
411 render_pager_destroy (p);
415 ascii_output_text (struct ascii_driver *a, const char *text)
417 struct table_item *table_item;
419 table_item = table_item_create (table_from_string (TAB_LEFT, text),
421 ascii_output_table_item (a, table_item);
422 table_item_unref (table_item);
426 ascii_submit (struct output_driver *driver,
427 const struct output_item *output_item)
429 struct ascii_driver *a = ascii_driver_cast (driver);
431 output_driver_track_current_command (output_item, &a->command_name);
436 if (is_table_item (output_item))
437 ascii_output_table_item (a, to_table_item (output_item));
439 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
441 struct chart_item *chart_item = to_chart_item (output_item);
444 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
448 if (file_name != NULL)
450 struct text_item *text_item;
452 text_item = text_item_create_format (
453 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
455 ascii_submit (driver, &text_item->output_item);
456 text_item_unref (text_item);
460 #endif /* HAVE_CAIRO */
461 else if (is_text_item (output_item))
463 const struct text_item *text_item = to_text_item (output_item);
464 enum text_item_type type = text_item_get_type (text_item);
465 const char *text = text_item_get_text (text_item);
469 case TEXT_ITEM_TITLE:
470 case TEXT_ITEM_SUBTITLE:
471 case TEXT_ITEM_COMMAND_OPEN:
472 case TEXT_ITEM_COMMAND_CLOSE:
475 case TEXT_ITEM_BLANK_LINE:
478 case TEXT_ITEM_EJECT_PAGE:
482 ascii_output_text (a, text);
486 else if (is_message_item (output_item))
488 const struct message_item *message_item = to_message_item (output_item);
489 const struct msg *msg = message_item_get_msg (message_item);
490 char *s = msg_to_string (msg, a->command_name);
491 ascii_output_text (a, s);
496 const struct output_driver_factory txt_driver_factory =
497 { "txt", "-", ascii_create };
498 const struct output_driver_factory list_driver_factory =
499 { "list", "-", ascii_create };
501 static const struct output_driver_class ascii_driver_class =
509 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
511 static void ascii_layout_cell (struct ascii_driver *,
512 const struct table_cell *,
514 int bb[TABLE_N_AXES][2],
515 int clip[TABLE_N_AXES][2],
516 int *width, int *height);
519 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
520 enum render_line_style styles[TABLE_N_AXES][2])
522 struct ascii_driver *a = a_;
529 /* Clip to the page. */
530 x0 = MAX (bb[H][0], 0);
531 y0 = MAX (bb[V][0], 0);
532 x1 = MIN (bb[H][1], a->width);
534 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
538 uc = a->box[make_box_index (styles[V][0], styles[V][1],
539 styles[H][0], styles[H][1])];
540 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
541 for (y = y0; y < y1; y++)
543 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
544 for (x = x0; x < x1; x++)
546 memcpy (p, mbchar, mblen);
553 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
554 int footnote_idx, int *min_width, int *max_width)
556 struct ascii_driver *a = a_;
557 int bb[TABLE_N_AXES][2];
558 int clip[TABLE_N_AXES][2];
565 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
566 ascii_layout_cell (a, cell, footnote_idx, bb, clip, max_width, &h);
568 if (cell->n_contents != 1
569 || cell->contents[0].n_footnotes
570 || strchr (cell->contents[0].text, ' '))
573 ascii_layout_cell (a, cell, footnote_idx, bb, clip, min_width, &h);
576 *min_width = *max_width;
580 ascii_measure_cell_height (void *a_, const struct table_cell *cell,
581 int footnote_idx, int 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, footnote_idx, bb, clip, &w, &h);
598 ascii_draw_cell (void *a_, const struct table_cell *cell, int footnote_idx,
599 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
601 struct ascii_driver *a = a_;
604 ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h);
608 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
610 if (y >= a->allocated_lines)
612 size_t new_alloc = MAX (25, a->allocated_lines);
613 while (new_alloc <= y)
614 new_alloc = xtimes (new_alloc, 2);
615 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
616 for (size_t i = a->allocated_lines; i < new_alloc; i++)
617 u8_line_init (&a->lines[i]);
618 a->allocated_lines = new_alloc;
620 return u8_line_reserve (&a->lines[y], x0, x1, n);
624 text_draw (struct ascii_driver *a, unsigned int options,
625 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
626 int y, const uint8_t *string, int n, size_t width)
628 int x0 = MAX (0, clip[H][0]);
629 int y0 = MAX (0, clip[V][0]);
630 int x1 = MIN (a->width, clip[H][1]);
634 if (y < y0 || y >= y1)
637 switch (options & TAB_ALIGNMENT)
643 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
646 x = bb[H][1] - width;
662 mblen = u8_mbtouc (&uc, string, n);
667 w = uc_width (uc, "UTF-8");
682 for (ofs = 0; ofs < n; )
688 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
690 w = uc_width (uc, "UTF-8");
693 if (width + w > x1 - x)
704 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
705 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
713 /* First figure out how many bytes need to be inserted. */
715 for (ofs = 0; ofs < n; ofs += mblen)
720 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
721 w = uc_width (uc, "UTF-8");
724 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
727 /* Then insert them. */
728 out = ascii_reserve (a, y, x, x + width, n_out);
729 for (ofs = 0; ofs < n; ofs += mblen)
734 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
735 w = uc_width (uc, "UTF-8");
739 if (a->emphasis == EMPH_UNDERLINE)
742 out = mempcpy (out, string + ofs, mblen);
745 out = mempcpy (out, string + ofs, mblen);
751 ascii_layout_cell_text (struct ascii_driver *a,
752 const struct cell_contents *contents, int *footnote_idx,
753 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
764 length = strlen (contents->text);
765 if (contents->n_footnotes)
771 ds_extend (&s, length + contents->n_footnotes * 4);
772 ds_put_cstr (&s, contents->text);
773 for (i = 0; i < contents->n_footnotes; i++)
777 str_format_26adic (++*footnote_idx, false, marker, sizeof marker);
778 ds_put_format (&s, "[%s]", marker);
781 length = ds_length (&s);
782 text = ds_steal_cstr (&s);
788 text = contents->text;
791 breaks = xmalloc (length + 1);
792 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
794 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
795 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
798 bb_width = bb[H][1] - bb[H][0];
799 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
801 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
802 const char *b = breaks + pos;
803 size_t n = length - pos;
805 size_t last_break_ofs = 0;
806 int last_break_width = 0;
811 for (ofs = 0; ofs < n; )
817 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
818 if (b[ofs] == UC_BREAK_MANDATORY)
820 else if (b[ofs] == UC_BREAK_POSSIBLE)
822 last_break_ofs = ofs;
823 last_break_width = width;
826 w = uc_width (uc, "UTF-8");
829 if (width + w > bb_width)
831 if (isspace (line[ofs]))
833 else if (last_break_ofs != 0)
835 ofs = last_break_ofs;
836 width = last_break_width;
845 /* Trim any trailing spaces off the end of the text to be drawn. */
846 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
847 if (!isspace (line[graph_ofs - 1]))
849 width -= ofs - graph_ofs;
852 text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
854 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
855 past any spaces past the end of the line (but not past a new-line). */
856 if (b[ofs] == UC_BREAK_MANDATORY)
859 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
868 if (text != contents->text)
869 free (CONST_CAST (char *, text));
875 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
877 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
878 int *widthp, int *heightp)
880 int bb[TABLE_N_AXES][2];
886 memcpy (bb, bb_, sizeof bb);
887 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
889 const struct cell_contents *contents = &cell->contents[i];
891 /* Put a blank line between contents. */
895 if (bb[V][0] >= bb[V][1])
899 bb[V][0] = ascii_layout_cell_text (a, contents, &footnote_idx,
902 *heightp = bb[V][0] - bb_[V][0];
906 ascii_test_write (struct output_driver *driver,
907 const char *s, int x, int y, unsigned int options)
909 struct ascii_driver *a = ascii_driver_cast (driver);
910 struct cell_contents contents;
911 struct table_cell cell;
912 int bb[TABLE_N_AXES][2];
915 if (a->file == NULL && !ascii_open_page (a))
918 contents.options = options | TAB_LEFT;
919 contents.text = CONST_CAST (char *, s);
920 contents.n_footnotes = 0;
922 memset (&cell, 0, sizeof cell);
923 cell.contents = &contents;
926 bb[TABLE_HORZ][0] = x;
927 bb[TABLE_HORZ][1] = a->width;
928 bb[TABLE_VERT][0] = y;
929 bb[TABLE_VERT][1] = INT_MAX;
931 ascii_layout_cell (a, &cell, 0, bb, bb, &width, &height);
935 ascii_test_set_length (struct output_driver *driver, int y, int length)
937 struct ascii_driver *a = ascii_driver_cast (driver);
939 if (a->file == NULL && !ascii_open_page (a))
944 u8_line_set_length (&a->lines[y], length);
948 ascii_test_flush (struct output_driver *driver)
950 struct ascii_driver *a = ascii_driver_cast (driver);
952 for (size_t i = a->allocated_lines; i-- > 0; )
953 if (a->lines[i].width)
955 ascii_output_lines (a, i + 1);
960 /* ascii_close_page () and support routines. */
962 #if HAVE_DECL_SIGWINCH
963 static struct ascii_driver *the_driver;
966 winch_handler (int signum UNUSED)
968 update_page_size (the_driver, false);
973 ascii_open_page (struct ascii_driver *a)
980 a->file = fn_open (a->handle, a->append ? "a" : "w");
983 if ( isatty (fileno (a->file)))
985 #if HAVE_DECL_SIGWINCH
986 struct sigaction action;
987 sigemptyset (&action.sa_mask);
989 action.sa_handler = winch_handler;
991 sigaction (SIGWINCH, &action, NULL);
993 a->auto_width = true;
998 msg_error (errno, _("ascii: opening output file `%s'"),
999 fh_get_file_name (a->handle));