1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009 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/>. */
25 #include <data/file-name.h>
26 #include <data/settings.h>
27 #include <libpspp/assertion.h>
28 #include <libpspp/compiler.h>
29 #include <libpspp/start-date.h>
30 #include <libpspp/string-map.h>
31 #include <libpspp/version.h>
32 #include <output/cairo.h>
33 #include <output/chart-item-provider.h>
34 #include "output/options.h"
35 #include <output/tab.h>
36 #include <output/text-item.h>
37 #include <output/driver-provider.h>
38 #include <output/render.h>
39 #include <output/table-item.h>
46 #define _(msgid) gettext (msgid)
48 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
52 /* Line styles bit shifts. */
64 make_box_index (int left, int right, int top, int bottom)
66 return ((left << LNS_LEFT) | (right << LNS_RIGHT)
67 | (top << LNS_TOP) | (bottom << LNS_BOTTOM));
70 /* Character attributes. */
71 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
72 #define ATTR_BOX 0x200 /* Line drawing character. */
77 unsigned short *chars; /* Characters and attributes. */
78 int n_chars; /* Length. */
79 int allocated_chars; /* Allocated "chars" elements. */
82 /* How to emphasize text. */
85 EMPH_BOLD, /* Overstrike for bold. */
86 EMPH_UNDERLINE, /* Overstrike for underlining. */
87 EMPH_NONE /* No emphasis. */
90 /* ASCII output driver. */
93 struct output_driver driver;
95 /* User parameters. */
96 bool append; /* Append if output-file already exists? */
97 bool headers; /* Print headers at top of page? */
98 bool paginate; /* Insert formfeeds? */
99 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
100 enum emphasis_style emphasis; /* How to emphasize text. */
101 int tab_width; /* Width of a tab; 0 not to use tabs. */
102 char *chart_file_name; /* Name of files used for charts. */
104 int width; /* Page width. */
105 int length; /* Page length minus margins and header. */
106 bool auto_width; /* Use viewwidth as page width? */
107 bool auto_length; /* Use viewlength as page width? */
109 int top_margin; /* Top margin in lines. */
110 int bottom_margin; /* Bottom margin in lines. */
112 char *box[LNS_COUNT]; /* Line & box drawing characters. */
113 char *init; /* Device initialization string. */
115 /* Internal state. */
118 char *file_name; /* Output file name. */
119 FILE *file; /* Output file. */
120 bool reported_error; /* Reported file open error? */
121 int page_number; /* Current page number. */
122 struct ascii_line *lines; /* Page content. */
123 int allocated_lines; /* Number of lines allocated. */
124 int chart_cnt; /* Number of charts so far. */
128 static int vertical_margins (const struct ascii_driver *);
130 static const char *get_default_box (int right, int bottom, int left, int top);
131 static bool update_page_size (struct ascii_driver *, bool issue_error);
132 static int parse_page_size (struct driver_option *);
134 static void ascii_close_page (struct ascii_driver *);
135 static void ascii_open_page (struct ascii_driver *);
137 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
138 enum render_line_style styles[TABLE_N_AXES][2]);
139 static void ascii_measure_cell_width (void *, const struct table_cell *,
141 static int ascii_measure_cell_height (void *, const struct table_cell *,
143 static void ascii_draw_cell (void *, const struct table_cell *,
144 int bb[TABLE_N_AXES][2],
145 int clip[TABLE_N_AXES][2]);
147 static struct ascii_driver *
148 ascii_driver_cast (struct output_driver *driver)
150 assert (driver->class == &ascii_class);
151 return UP_CAST (driver, struct ascii_driver, driver);
154 static struct driver_option *
155 opt (struct output_driver *d, struct string_map *options, const char *key,
156 const char *default_value)
158 return driver_option_get (d, options, key, default_value);
161 static struct output_driver *
162 ascii_create (const char *name, enum output_device_type device_type,
163 struct string_map *o)
165 struct output_driver *d;
166 struct ascii_driver *a;
168 int right, bottom, left, top;
170 a = xzalloc (sizeof *a);
172 output_driver_init (&a->driver, &ascii_class, name, device_type);
173 a->append = parse_boolean (opt (d, o, "append", "false"));
174 a->headers = parse_boolean (opt (d, o, "headers", "true"));
175 a->paginate = parse_boolean (opt (d, o, "paginate", "true"));
176 a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "false"));
177 a->emphasis = parse_enum (opt (d, o, "emphasis", "bold"),
179 "underline", EMPH_UNDERLINE,
182 a->tab_width = parse_int (opt (d, o, "tab-width", "0"), 8, INT_MAX);
184 if (parse_enum (opt (d, o, "chart-type", "png"),
188 a->chart_file_name = parse_chart_file_name (opt (d, o, "chart-files",
191 a->chart_file_name = NULL;
193 a->top_margin = parse_int (opt (d, o, "top-margin", "2"), 0, INT_MAX);
194 a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "2"), 0, INT_MAX);
196 a->width = parse_page_size (opt (d, o, "width", "79"));
197 paper_length = parse_page_size (opt (d, o, "length", "66"));
198 a->auto_width = a->width < 0;
199 a->auto_length = paper_length < 0;
200 a->length = paper_length - vertical_margins (a);
202 for (right = 0; right < 4; right++)
203 for (bottom = 0; bottom < 4; bottom++)
204 for (left = 0; left < 4; left++)
205 for (top = 0; top < 4; top++)
207 int indx = make_box_index (left, right, top, bottom);
208 const char *default_value;
211 sprintf (name, "box[%d%d%d%d]", right, bottom, left, top);
212 default_value = get_default_box (right, bottom, left, top);
213 a->box[indx] = parse_string (opt (d, o, name, default_value));
215 a->init = parse_string (opt (d, o, "init", ""));
217 a->title = xstrdup ("");
218 a->subtitle = xstrdup ("");
219 a->file_name = parse_string (opt (d, o, "output-file", "pspp.list"));
221 a->reported_error = false;
224 a->allocated_lines = 0;
227 if (!update_page_size (a, true))
233 output_driver_destroy (d);
238 get_default_box (int right, int bottom, int left, int top)
240 switch ((top << 12) | (left << 8) | (bottom << 4) | (right << 0))
245 case 0x0100: case 0x0101: case 0x0001:
248 case 0x1000: case 0x1010: case 0x0010:
251 case 0x0300: case 0x0303: case 0x0003:
252 case 0x0200: case 0x0202: case 0x0002:
256 return left > 1 || top > 1 || right > 1 || bottom > 1 ? "#" : "+";
261 parse_page_size (struct driver_option *option)
263 int dim = atol (option->default_value);
265 if (option->value != NULL)
267 if (!strcmp (option->value, "auto"))
275 value = strtol (option->value, &tail, 0);
276 if (dim >= 1 && errno != ERANGE && *tail == '\0')
279 error (0, 0, _("%s: %s must be positive integer or `auto'"),
280 option->driver_name, option->name);
284 driver_option_destroy (option);
290 vertical_margins (const struct ascii_driver *a)
292 return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
295 /* Re-calculates the page width and length based on settings,
296 margins, and, if "auto" is set, the size of the user's
297 terminal window or GUI output window. */
299 update_page_size (struct ascii_driver *a, bool issue_error)
301 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
304 a->width = settings_get_viewwidth ();
306 a->length = settings_get_viewlength () - vertical_margins (a);
308 if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
312 _("ascii: page excluding margins and headers "
313 "must be at least %d characters wide by %d lines long, but "
314 "as configured is only %d characters by %d lines"),
315 MIN_WIDTH, MIN_LENGTH,
316 a->width, a->length);
317 if (a->width < MIN_WIDTH)
318 a->width = MIN_WIDTH;
319 if (a->length < MIN_LENGTH)
320 a->length = MIN_LENGTH;
328 ascii_destroy (struct output_driver *driver)
330 struct ascii_driver *a = ascii_driver_cast (driver);
334 ascii_close_page (a);
337 fn_close (a->file_name, a->file);
341 free (a->chart_file_name);
342 for (i = 0; i < LNS_COUNT; i++)
345 for (i = 0; i < a->allocated_lines; i++)
346 free (a->lines[i].chars);
352 ascii_flush (struct output_driver *driver)
354 struct ascii_driver *a = ascii_driver_cast (driver);
357 ascii_close_page (a);
359 if (fn_close (a->file_name, a->file) != 0)
360 error (0, errno, _("ascii: closing output file \"%s\""),
367 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
369 cell->contents = caption;
370 cell->options = TAB_LEFT;
371 cell->destructor = NULL;
375 ascii_submit (struct output_driver *driver,
376 const struct output_item *output_item)
378 struct ascii_driver *a = ascii_driver_cast (driver);
379 if (is_table_item (output_item))
381 struct table_item *table_item = to_table_item (output_item);
382 const char *caption = table_item_get_caption (table_item);
383 struct render_params params;
384 struct render_page *page;
385 struct render_break x_break;
389 update_page_size (a, false);
393 /* XXX doesn't do well with very large captions */
394 struct table_cell cell;
395 ascii_init_caption_cell (caption, &cell);
396 caption_height = ascii_measure_cell_height (a, &cell, a->width);
401 params.draw_line = ascii_draw_line;
402 params.measure_cell_width = ascii_measure_cell_width;
403 params.measure_cell_height = ascii_measure_cell_height;
404 params.draw_cell = ascii_draw_cell,
406 params.size[H] = a->width;
407 params.size[V] = a->length - caption_height;
408 params.font_size[H] = 1;
409 params.font_size[V] = 1;
410 for (i = 0; i < RENDER_N_LINES; i++)
412 int width = i == RENDER_LINE_NONE ? 0 : 1;
413 params.line_widths[H][i] = width;
414 params.line_widths[V][i] = width;
420 page = render_page_create (¶ms, table_item_get_table (table_item));
421 for (render_break_init (&x_break, page, H);
422 render_break_has_next (&x_break); )
424 struct render_page *x_slice;
425 struct render_break y_break;
427 x_slice = render_break_next (&x_break, a->width);
428 for (render_break_init (&y_break, x_slice, V);
429 render_break_has_next (&y_break); )
431 struct render_page *y_slice;
437 space = a->length - a->y - caption_height;
438 if (render_break_next_size (&y_break) > space)
441 ascii_close_page (a);
446 y_slice = render_break_next (&y_break, space);
449 struct table_cell cell;
450 int bb[TABLE_N_AXES][2];
452 ascii_init_caption_cell (caption, &cell);
456 bb[V][1] = caption_height;
457 ascii_draw_cell (a, &cell, bb, bb);
458 a->y += caption_height;
461 render_page_draw (y_slice);
462 a->y += render_page_get_size (y_slice, V);
463 render_page_unref (y_slice);
465 render_break_destroy (&y_break);
467 render_break_destroy (&x_break);
469 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
471 struct chart_item *chart_item = to_chart_item (output_item);
474 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
476 if (file_name != NULL)
478 struct text_item *text_item;
480 text_item = text_item_create_format (
481 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
483 ascii_submit (driver, &text_item->output_item);
484 text_item_unref (text_item);
488 else if (is_text_item (output_item))
490 const struct text_item *text_item = to_text_item (output_item);
491 enum text_item_type type = text_item_get_type (text_item);
492 const char *text = text_item_get_text (text_item);
496 case TEXT_ITEM_TITLE:
498 a->title = xstrdup (text);
501 case TEXT_ITEM_SUBTITLE:
503 a->subtitle = xstrdup (text);
506 case TEXT_ITEM_COMMAND_CLOSE:
509 case TEXT_ITEM_BLANK_LINE:
514 case TEXT_ITEM_EJECT_PAGE:
516 ascii_close_page (a);
521 struct table_item *item;
523 item = table_item_create (table_from_string (0, text), NULL);
524 ascii_submit (&a->driver, &item->output_item);
525 table_item_unref (item);
532 const struct output_driver_class ascii_class =
548 static void ascii_expand_line (struct ascii_driver *, int y, int length);
549 static void ascii_layout_cell (struct ascii_driver *,
550 const struct table_cell *,
551 int bb[TABLE_N_AXES][2],
552 int clip[TABLE_N_AXES][2], enum wrap_mode wrap,
553 int *width, int *height);
556 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
557 enum render_line_style styles[TABLE_N_AXES][2])
559 struct ascii_driver *a = a_;
560 unsigned short int value;
564 /* Clip to the page. */
565 if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
567 x1 = MIN (bb[H][1], a->width);
568 y1 = MIN (bb[V][1] + a->y, a->length);
571 value = ATTR_BOX | make_box_index (styles[V][0], styles[V][1],
572 styles[H][0], styles[H][1]);
573 for (y = bb[V][0] + a->y; y < y1; y++)
575 ascii_expand_line (a, y, x1);
576 for (x = bb[H][0]; x < x1; x++)
577 a->lines[y].chars[x] = value;
582 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
583 int *min_width, int *max_width)
585 struct ascii_driver *a = a_;
586 int bb[TABLE_N_AXES][2];
587 int clip[TABLE_N_AXES][2];
594 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
595 ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, max_width, &h);
597 if (strchr (cell->contents, ' '))
600 ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, min_width, &h);
603 *min_width = *max_width;
607 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
609 struct ascii_driver *a = a_;
610 int bb[TABLE_N_AXES][2];
611 int clip[TABLE_N_AXES][2];
618 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
619 ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, &w, &h);
624 ascii_draw_cell (void *a_, const struct table_cell *cell,
625 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
627 struct ascii_driver *a = a_;
630 ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, &w, &h);
633 /* Ensures that at least the first LENGTH characters of line Y in
634 ascii driver A have been cleared out. */
636 ascii_expand_line (struct ascii_driver *a, int y, int length)
638 struct ascii_line *line = &a->lines[y];
639 if (line->n_chars < length)
642 if (line->allocated_chars < length)
644 line->allocated_chars = MAX (length, MIN (length * 2, a->width));
645 line->chars = xnrealloc (line->chars, line->allocated_chars,
646 sizeof *line->chars);
648 for (x = line->n_chars; x < length; x++)
649 line->chars[x] = ' ';
650 line->n_chars = length;
655 text_draw (struct ascii_driver *a, const struct table_cell *cell,
656 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
657 int y, const char *string, int n)
659 int x0 = MAX (0, clip[H][0]);
660 int y0 = MAX (0, clip[V][0] + a->y);
662 int y1 = MIN (a->length, clip[V][1] + a->y);
666 if (y < y0 || y >= y1)
669 switch (cell->options & TAB_ALIGNMENT)
675 x = (bb[H][0] + bb[H][1] - n + 1) / 2;
697 int attr = cell->options & TAB_EMPH ? ATTR_EMPHASIS : 0;
700 ascii_expand_line (a, y, x + n);
701 for (i = 0; i < n; i++)
702 a->lines[y].chars[x + i] = string[i] | attr;
707 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
708 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
709 enum wrap_mode wrap, int *width, int *height)
711 size_t length = strlen (cell->contents);
716 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
718 const char *line = &cell->contents[pos];
719 const char *new_line;
722 /* Find line length without considering word wrap. */
723 line_len = MIN (bb[H][1] - bb[H][0], length - pos);
724 new_line = memchr (line, '\n', line_len);
725 if (new_line != NULL)
726 line_len = new_line - line;
729 if (pos + line_len < length && wrap != WRAP_CHAR)
731 size_t space_len = line_len;
732 while (space_len > 0 && !isspace ((unsigned char) line[space_len]))
735 line_len = space_len;
736 else if (wrap == WRAP_WORD)
738 while (pos + line_len < length
739 && !isspace ((unsigned char) line[line_len]))
743 if (line_len > *width)
747 text_draw (a, cell, bb, clip, y, line, line_len);
751 if (pos < length && isspace ((unsigned char) cell->contents[pos]))
754 *height = y - bb[V][0];
757 /* ascii_close_page () and support routines. */
760 ascii_open_page (struct ascii_driver *a)
766 a->file = fn_open (a->file_name, a->append ? "a" : "w");
770 fputs (a->init, a->file);
774 /* Report the error to the user and complete
775 initialization. If we do not finish initialization,
776 then calls to other driver functions will segfault
777 later. It would be better to simply drop the driver
778 entirely, but we do not have a convenient mechanism
780 if (!a->reported_error)
781 error (0, errno, _("ascii: opening output file \"%s\""),
783 a->reported_error = true;
789 if (a->length > a->allocated_lines)
791 a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
792 for (i = a->allocated_lines; i < a->length; i++)
794 struct ascii_line *line = &a->lines[i];
796 line->allocated_chars = 0;
798 a->allocated_lines = a->length;
801 for (i = 0; i < a->length; i++)
802 a->lines[i].n_chars = 0;
805 /* Writes LINE to A's output file. */
807 output_line (struct ascii_driver *a, const struct ascii_line *line)
812 length = line->n_chars;
813 while (length > 0 && line->chars[length - 1] == ' ')
816 for (i = 0; i < length; i++)
818 int attribute = line->chars[i] & (ATTR_BOX | ATTR_EMPHASIS);
819 int ch = line->chars[i] & ~(ATTR_BOX | ATTR_EMPHASIS);
824 fputs (a->box[ch], a->file);
828 if (a->emphasis == EMPH_BOLD)
829 fprintf (a->file, "%c\b%c", ch, ch);
830 else if (a->emphasis == EMPH_UNDERLINE)
831 fprintf (a->file, "_\b%c", ch);
842 putc ('\n', a->file);
846 output_title_line (FILE *out, int width, const char *left, const char *right)
848 struct string s = DS_EMPTY_INITIALIZER;
849 ds_put_char_multiple (&s, ' ', width);
852 size_t length = MIN (strlen (left), width);
853 memcpy (ds_end (&s) - width, left, length);
857 size_t length = MIN (strlen (right), width);
858 memcpy (ds_end (&s) - length, right, length);
860 ds_put_char (&s, '\n');
861 fputs (ds_cstr (&s), out);
866 ascii_close_page (struct ascii_driver *a)
874 if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
875 && !a->paginate && a->page_number > 1)
876 putc ('\n', a->file);
878 for (i = 0; i < a->top_margin; i++)
879 putc ('\n', a->file);
884 r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
885 r2 = xasprintf ("%s - %s" , version, host_system);
887 output_title_line (a->file, a->width, a->title, r1);
888 output_title_line (a->file, a->width, a->subtitle, r2);
889 putc ('\n', a->file);
896 for (y = 0; y < a->allocated_lines; y++)
898 struct ascii_line *line = &a->lines[y];
900 if (a->squeeze_blank_lines && y > 0 && line->n_chars == 0)
906 putc ('\n', a->file);
910 output_line (a, line);
913 if (!a->squeeze_blank_lines)
914 for (y = a->allocated_lines; y < a->length; y++)
915 putc ('\n', a->file);
917 for (i = 0; i < a->bottom_margin; i++)
918 putc ('\n', a->file);
920 putc ('\f', a->file);