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 struct cell_color colors[TABLE_N_AXES][2]);
219 static void ascii_measure_cell_width (void *, const struct table_cell *,
221 static int ascii_measure_cell_height (void *, const struct table_cell *,
223 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
224 int bb[TABLE_N_AXES][2],
225 int spill[TABLE_N_AXES][2],
226 int clip[TABLE_N_AXES][2]);
228 static struct ascii_driver *
229 ascii_driver_cast (struct output_driver *driver)
231 assert (driver->class == &ascii_driver_class);
232 return UP_CAST (driver, struct ascii_driver, driver);
235 static struct driver_option *
236 opt (struct output_driver *d, struct string_map *options, const char *key,
237 const char *default_value)
239 return driver_option_get (d, options, key, default_value);
242 static struct output_driver *
243 ascii_create (struct file_handle *fh, enum settings_output_devices device_type,
244 struct string_map *o)
246 enum { BOX_ASCII, BOX_UNICODE } box;
247 int min_break[TABLE_N_AXES];
248 struct output_driver *d;
249 struct ascii_driver *a;
251 a = xzalloc (sizeof *a);
253 output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
254 a->append = parse_boolean (opt (d, o, "append", "false"));
255 a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
257 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
260 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
262 a->width = parse_page_size (opt (d, o, "width", "79"));
263 a->auto_width = a->width < 0;
264 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
266 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
267 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
269 box = parse_enum (opt (d, o, "box", "ascii"),
271 "unicode", BOX_UNICODE,
273 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
278 a->allocated_lines = 0;
281 if (!update_page_size (a, true))
287 output_driver_destroy (d);
292 parse_page_size (struct driver_option *option)
294 int dim = atol (option->default_value);
296 if (option->value != NULL)
298 if (!strcmp (option->value, "auto"))
306 value = strtol (option->value, &tail, 0);
307 if (dim >= 1 && errno != ERANGE && *tail == '\0')
310 msg (MW, _("%s: %s must be positive integer or `auto'"),
311 option->driver_name, option->name);
315 driver_option_destroy (option);
320 /* Re-calculates the page width based on settings, margins, and, if "auto" is
321 set, the size of the user's terminal window or GUI output window. */
323 update_page_size (struct ascii_driver *a, bool issue_error)
325 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
329 a->width = settings_get_viewwidth ();
330 a->min_break[H] = a->width / 2;
333 if (a->width < MIN_WIDTH)
337 _("ascii: page must be at least %d characters wide, but "
338 "as configured is only %d characters"),
341 if (a->width < MIN_WIDTH)
342 a->width = MIN_WIDTH;
350 ascii_destroy (struct output_driver *driver)
352 struct ascii_driver *a = ascii_driver_cast (driver);
356 fn_close (a->handle, a->file);
357 fh_unref (a->handle);
358 free (a->chart_file_name);
359 for (i = 0; i < a->allocated_lines; i++)
360 u8_line_destroy (&a->lines[i]);
366 ascii_flush (struct output_driver *driver)
368 struct ascii_driver *a = ascii_driver_cast (driver);
374 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
376 for (size_t y = 0; y < n_lines; y++)
378 struct u8_line *line = &a->lines[y];
380 while (ds_chomp_byte (&line->s, ' '))
382 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
383 putc ('\n', a->file);
385 u8_line_clear (&a->lines[y]);
390 ascii_output_table_item (struct ascii_driver *a,
391 const struct table_item *table_item)
393 struct render_params params;
394 struct render_pager *p;
397 update_page_size (a, false);
399 params.draw_line = ascii_draw_line;
400 params.measure_cell_width = ascii_measure_cell_width;
401 params.measure_cell_height = ascii_measure_cell_height;
402 params.adjust_break = NULL;
403 params.draw_cell = ascii_draw_cell;
405 params.size[H] = a->width;
406 params.size[V] = INT_MAX;
407 params.font_size[H] = 1;
408 params.font_size[V] = 1;
409 for (i = 0; i < RENDER_N_LINES; i++)
411 int width = i == RENDER_LINE_NONE ? 0 : 1;
412 params.line_widths[H][i] = width;
413 params.line_widths[V][i] = width;
415 for (i = 0; i < TABLE_N_AXES; i++)
416 params.min_break[i] = a->min_break[i];
417 params.supports_margins = false;
420 putc ('\n', a->file);
421 else if (!ascii_open_page (a))
424 p = render_pager_create (¶ms, 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);
483 const char *text = text_item_get_text (text_item);
487 case TEXT_ITEM_PAGE_TITLE:
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],
537 struct cell_color colors[TABLE_N_AXES][2] UNUSED)
539 struct ascii_driver *a = a_;
546 /* Clip to the page. */
547 x0 = MAX (bb[H][0], 0);
548 y0 = MAX (bb[V][0], 0);
549 x1 = MIN (bb[H][1], a->width);
551 if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
555 uc = a->box[make_box_index (styles[V][0], styles[V][1],
556 styles[H][0], styles[H][1])];
557 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
558 for (y = y0; y < y1; y++)
560 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
561 for (x = x0; x < x1; x++)
563 memcpy (p, mbchar, mblen);
570 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
571 int *min_width, int *max_width)
573 struct ascii_driver *a = a_;
574 int bb[TABLE_N_AXES][2];
575 int clip[TABLE_N_AXES][2];
582 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
583 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
585 if (cell->n_contents != 1
586 || cell->contents[0].n_footnotes
587 || strchr (cell->contents[0].text, ' '))
590 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
593 *min_width = *max_width;
597 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
599 struct ascii_driver *a = a_;
600 int bb[TABLE_N_AXES][2];
601 int clip[TABLE_N_AXES][2];
608 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
609 ascii_layout_cell (a, cell, bb, clip, &w, &h);
614 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
615 int bb[TABLE_N_AXES][2],
616 int spill[TABLE_N_AXES][2] UNUSED,
617 int clip[TABLE_N_AXES][2])
619 struct ascii_driver *a = a_;
622 ascii_layout_cell (a, cell, bb, clip, &w, &h);
626 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
628 if (y >= a->allocated_lines)
630 size_t new_alloc = MAX (25, a->allocated_lines);
631 while (new_alloc <= y)
632 new_alloc = xtimes (new_alloc, 2);
633 a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
634 for (size_t i = a->allocated_lines; i < new_alloc; i++)
635 u8_line_init (&a->lines[i]);
636 a->allocated_lines = new_alloc;
638 return u8_line_reserve (&a->lines[y], x0, x1, n);
642 text_draw (struct ascii_driver *a, unsigned int options,
643 bool bold, bool underline,
644 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
645 int y, const uint8_t *string, int n, size_t width)
647 int x0 = MAX (0, clip[H][0]);
648 int y0 = MAX (0, clip[V][0]);
649 int x1 = MIN (a->width, clip[H][1]);
653 if (y < y0 || y >= y1)
656 switch (options & TAB_HALIGN)
662 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
665 x = bb[H][1] - width;
681 mblen = u8_mbtouc (&uc, string, n);
686 w = uc_width (uc, "UTF-8");
701 for (ofs = 0; ofs < n; )
707 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
709 w = uc_width (uc, "UTF-8");
712 if (width + w > x1 - x)
723 if (!a->emphasis || (!bold && !underline))
724 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
732 /* First figure out how many bytes need to be inserted. */
734 for (ofs = 0; ofs < n; ofs += mblen)
739 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
740 w = uc_width (uc, "UTF-8");
751 /* Then insert them. */
752 out = ascii_reserve (a, y, x, x + width, n_out);
753 for (ofs = 0; ofs < n; ofs += mblen)
758 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
759 w = uc_width (uc, "UTF-8");
765 out = mempcpy (out, string + ofs, mblen);
774 out = mempcpy (out, string + ofs, mblen);
780 ascii_layout_cell_text (struct ascii_driver *a,
781 const struct cell_contents *contents,
782 bool bold, bool underline,
783 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
794 length = strlen (contents->text);
795 if (contents->n_footnotes)
801 ds_extend (&s, length + contents->n_footnotes * 4);
802 ds_put_cstr (&s, contents->text);
803 for (i = 0; i < contents->n_footnotes; i++)
804 ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
806 length = ds_length (&s);
807 text = ds_steal_cstr (&s);
813 text = contents->text;
816 breaks = xmalloc (length + 1);
817 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
819 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
820 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
823 bb_width = bb[H][1] - bb[H][0];
824 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
826 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
827 const char *b = breaks + pos;
828 size_t n = length - pos;
830 size_t last_break_ofs = 0;
831 int last_break_width = 0;
836 for (ofs = 0; ofs < n; )
842 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
843 if (b[ofs] == UC_BREAK_MANDATORY)
845 else if (b[ofs] == UC_BREAK_POSSIBLE)
847 last_break_ofs = ofs;
848 last_break_width = width;
851 w = uc_width (uc, "UTF-8");
854 if (width + w > bb_width)
856 if (isspace (line[ofs]))
858 else if (last_break_ofs != 0)
860 ofs = last_break_ofs;
861 width = last_break_width;
870 /* Trim any trailing spaces off the end of the text to be drawn. */
871 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
872 if (!isspace (line[graph_ofs - 1]))
874 width -= ofs - graph_ofs;
877 text_draw (a, contents->options, bold, underline,
878 bb, clip, y, line, graph_ofs, width);
880 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
881 past any spaces past the end of the line (but not past a new-line). */
882 if (b[ofs] == UC_BREAK_MANDATORY)
885 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
894 if (text != contents->text)
895 free (CONST_CAST (char *, text));
901 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
902 int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
903 int *widthp, int *heightp)
905 int bb[TABLE_N_AXES][2];
911 memcpy (bb, bb_, sizeof bb);
912 for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
914 const struct cell_contents *contents = &cell->contents[i];
916 /* Put a blank line between contents. */
920 if (bb[V][0] >= bb[V][1])
924 bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold,
925 cell->style->underline,
928 *heightp = bb[V][0] - bb_[V][0];
932 ascii_test_write (struct output_driver *driver,
933 const char *s, int x, int y, bool bold, bool underline)
935 struct ascii_driver *a = ascii_driver_cast (driver);
936 int bb[TABLE_N_AXES][2];
939 if (a->file == NULL && !ascii_open_page (a))
942 struct cell_contents contents = {
944 .text = CONST_CAST (char *, s),
946 struct cell_style cell_style = {
948 .underline = underline,
950 struct table_cell cell = {
951 .contents = &contents,
953 .style = &cell_style,
956 bb[TABLE_HORZ][0] = x;
957 bb[TABLE_HORZ][1] = a->width;
958 bb[TABLE_VERT][0] = y;
959 bb[TABLE_VERT][1] = INT_MAX;
961 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
965 ascii_test_set_length (struct output_driver *driver, int y, int length)
967 struct ascii_driver *a = ascii_driver_cast (driver);
969 if (a->file == NULL && !ascii_open_page (a))
974 u8_line_set_length (&a->lines[y], length);
978 ascii_test_flush (struct output_driver *driver)
980 struct ascii_driver *a = ascii_driver_cast (driver);
982 for (size_t i = a->allocated_lines; i-- > 0; )
983 if (a->lines[i].width)
985 ascii_output_lines (a, i + 1);
990 /* ascii_close_page () and support routines. */
992 #if HAVE_DECL_SIGWINCH
993 static struct ascii_driver *the_driver;
996 winch_handler (int signum UNUSED)
998 update_page_size (the_driver, false);
1003 ascii_open_page (struct ascii_driver *a)
1008 if (a->file == NULL)
1010 a->file = fn_open (a->handle, a->append ? "a" : "w");
1011 if (a->file != NULL)
1013 if ( isatty (fileno (a->file)))
1015 #if HAVE_DECL_SIGWINCH
1016 struct sigaction action;
1017 sigemptyset (&action.sa_mask);
1018 action.sa_flags = 0;
1019 action.sa_handler = winch_handler;
1021 sigaction (SIGWINCH, &action, NULL);
1023 a->auto_width = true;
1028 msg_error (errno, _("ascii: opening output file `%s'"),
1029 fh_get_file_name (a->handle));