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 *,
194 int footnote_idx, int *min, int *max);
195 static int ascii_measure_cell_height (void *, const struct table_cell *,
196 int footnote_idx, int width);
197 static void ascii_draw_cell (void *, const struct table_cell *,
198 int footnote_idx, 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 *,
509 int bb[TABLE_N_AXES][2],
510 int clip[TABLE_N_AXES][2],
511 int *width, int *height);
514 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
515 enum render_line_style styles[TABLE_N_AXES][2])
517 struct ascii_driver *a = a_;
524 /* Clip to the page. */
525 x0 = MAX (bb[H][0], 0);
526 y0 = MAX (bb[V][0], 0);
527 x1 = MIN (bb[H][1], a->width);
529 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
533 uc = a->box[make_box_index (styles[V][0], styles[V][1],
534 styles[H][0], styles[H][1])];
535 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
536 for (y = y0; y < y1; y++)
538 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
539 for (x = x0; x < x1; x++)
541 memcpy (p, mbchar, mblen);
548 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
549 int footnote_idx, int *min_width, int *max_width)
551 struct ascii_driver *a = a_;
552 int bb[TABLE_N_AXES][2];
553 int clip[TABLE_N_AXES][2];
560 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
561 ascii_layout_cell (a, cell, footnote_idx, bb, clip, max_width, &h);
563 if (cell->n_contents != 1
564 || cell->contents[0].n_footnotes
565 || strchr (cell->contents[0].text, ' '))
568 ascii_layout_cell (a, cell, footnote_idx, bb, clip, min_width, &h);
571 *min_width = *max_width;
575 ascii_measure_cell_height (void *a_, const struct table_cell *cell,
576 int footnote_idx, int width)
578 struct ascii_driver *a = a_;
579 int bb[TABLE_N_AXES][2];
580 int clip[TABLE_N_AXES][2];
587 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
588 ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h);
593 ascii_draw_cell (void *a_, const struct table_cell *cell, int footnote_idx,
594 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
596 struct ascii_driver *a = a_;
599 ascii_layout_cell (a, cell, footnote_idx, bb, clip, &w, &h);
603 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
605 if (y >= a->allocated_lines)
607 size_t new_alloc = MAX (25, a->allocated_lines);
608 while (new_alloc <= y)
609 new_alloc = xtimes (new_alloc, 2);
610 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
611 for (size_t i = a->allocated_lines; i < new_alloc; i++)
612 u8_line_init (&a->lines[i]);
613 a->allocated_lines = new_alloc;
615 return u8_line_reserve (&a->lines[y], x0, x1, n);
619 text_draw (struct ascii_driver *a, unsigned int options,
620 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
621 int y, const uint8_t *string, int n, size_t width)
623 int x0 = MAX (0, clip[H][0]);
624 int y0 = MAX (0, clip[V][0]);
625 int x1 = MIN (a->width, clip[H][1]);
629 if (y < y0 || y >= y1)
632 switch (options & TAB_ALIGNMENT)
638 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
641 x = bb[H][1] - width;
657 mblen = u8_mbtouc (&uc, string, n);
662 w = uc_width (uc, "UTF-8");
677 for (ofs = 0; ofs < n; )
683 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
685 w = uc_width (uc, "UTF-8");
688 if (width + w > x1 - x)
699 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
700 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
708 /* First figure out how many bytes need to be inserted. */
710 for (ofs = 0; ofs < n; ofs += mblen)
715 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
716 w = uc_width (uc, "UTF-8");
719 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
722 /* Then insert them. */
723 out = ascii_reserve (a, y, x, x + width, n_out);
724 for (ofs = 0; ofs < n; ofs += mblen)
729 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
730 w = uc_width (uc, "UTF-8");
734 if (a->emphasis == EMPH_UNDERLINE)
737 out = mempcpy (out, string + ofs, mblen);
740 out = mempcpy (out, string + ofs, mblen);
746 ascii_layout_cell_text (struct ascii_driver *a,
747 const struct cell_contents *contents, int *footnote_idx,
748 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
759 length = strlen (contents->text);
760 if (contents->n_footnotes)
766 ds_extend (&s, length + contents->n_footnotes * 4);
767 ds_put_cstr (&s, contents->text);
768 for (i = 0; i < contents->n_footnotes; i++)
772 str_format_26adic (++*footnote_idx, false, marker, sizeof marker);
773 ds_put_format (&s, "[%s]", marker);
776 length = ds_length (&s);
777 text = ds_steal_cstr (&s);
783 text = contents->text;
786 breaks = xmalloc (length + 1);
787 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
789 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
790 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
793 bb_width = bb[H][1] - bb[H][0];
794 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
796 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
797 const char *b = breaks + pos;
798 size_t n = length - pos;
800 size_t last_break_ofs = 0;
801 int last_break_width = 0;
806 for (ofs = 0; ofs < n; )
812 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
813 if (b[ofs] == UC_BREAK_MANDATORY)
815 else if (b[ofs] == UC_BREAK_POSSIBLE)
817 last_break_ofs = ofs;
818 last_break_width = width;
821 w = uc_width (uc, "UTF-8");
824 if (width + w > bb_width)
826 if (isspace (line[ofs]))
828 else if (last_break_ofs != 0)
830 ofs = last_break_ofs;
831 width = last_break_width;
840 /* Trim any trailing spaces off the end of the text to be drawn. */
841 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
842 if (!isspace (line[graph_ofs - 1]))
844 width -= ofs - graph_ofs;
847 text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
849 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
850 past any spaces past the end of the line (but not past a new-line). */
851 if (b[ofs] == UC_BREAK_MANDATORY)
854 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
863 if (text != contents->text)
864 free (CONST_CAST (char *, text));
870 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
872 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
873 int *widthp, int *heightp)
875 int bb[TABLE_N_AXES][2];
881 memcpy (bb, bb_, sizeof bb);
882 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
884 const struct cell_contents *contents = &cell->contents[i];
886 /* Put a blank line between contents. */
890 if (bb[V][0] >= bb[V][1])
894 bb[V][0] = ascii_layout_cell_text (a, contents, &footnote_idx,
897 *heightp = bb[V][0] - bb_[V][0];
901 ascii_test_write (struct output_driver *driver,
902 const char *s, int x, int y, unsigned int options)
904 struct ascii_driver *a = ascii_driver_cast (driver);
905 struct cell_contents contents;
906 struct table_cell cell;
907 int bb[TABLE_N_AXES][2];
910 if (a->file == NULL && !ascii_open_page (a))
913 contents.options = options | TAB_LEFT;
914 contents.text = CONST_CAST (char *, s);
915 contents.n_footnotes = 0;
917 memset (&cell, 0, sizeof cell);
918 cell.contents = &contents;
921 bb[TABLE_HORZ][0] = x;
922 bb[TABLE_HORZ][1] = a->width;
923 bb[TABLE_VERT][0] = y;
924 bb[TABLE_VERT][1] = INT_MAX;
926 ascii_layout_cell (a, &cell, 0, bb, bb, &width, &height);
930 ascii_test_set_length (struct output_driver *driver, int y, int length)
932 struct ascii_driver *a = ascii_driver_cast (driver);
934 if (a->file == NULL && !ascii_open_page (a))
939 u8_line_set_length (&a->lines[y], length);
943 ascii_test_flush (struct output_driver *driver)
945 struct ascii_driver *a = ascii_driver_cast (driver);
947 for (size_t i = a->allocated_lines; i-- > 0; )
948 if (a->lines[i].width)
950 ascii_output_lines (a, i + 1);
955 /* ascii_close_page () and support routines. */
957 #if HAVE_DECL_SIGWINCH
958 static struct ascii_driver *the_driver;
961 winch_handler (int signum UNUSED)
963 update_page_size (the_driver, false);
968 ascii_open_page (struct ascii_driver *a)
975 a->file = fn_open (a->handle, a->append ? "a" : "w");
978 if ( isatty (fileno (a->file)))
980 #if HAVE_DECL_SIGWINCH
981 struct sigaction action;
982 sigemptyset (&action.sa_mask);
984 action.sa_handler = winch_handler;
986 sigaction (SIGWINCH, &action, NULL);
988 a->auto_width = true;
993 msg_error (errno, _("ascii: opening output file `%s'"),
994 fh_get_file_name (a->handle));