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/pool.h>
30 #include <libpspp/start-date.h>
31 #include <libpspp/version.h>
32 #include <output/chart-provider.h>
33 #include <output/chart.h>
34 #include <output/output.h>
41 #define _(msgid) gettext (msgid)
43 /* ASCII driver options: (defaults listed first)
45 output-file="pspp.list"
46 append=no|yes If output-file exists, append to it?
47 chart-files="pspp-#.png" Name used for charts.
50 paginate=on|off Formfeeds are desired?
51 tab-width=8 Width of a tab; 0 to not use tabs.
53 headers=on|off Put headers at top of page?
54 emphasis=bold|underline|none Style to use for emphasis.
57 squeeze=off|on Squeeze multiple newlines into exactly one.
62 box[x]="strng" Sets box character X (X in base 4: 0-3333).
63 init="string" Set initialization string.
66 /* Disable messages by failed range checks. */
67 /*#define SUPPRESS_WARNINGS 1 */
69 /* Line styles bit shifts. */
80 /* Character attributes. */
81 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
82 #define ATTR_BOX 0x200 /* Line drawing character. */
87 unsigned short *chars; /* Characters and attributes. */
88 int char_cnt; /* Length. */
89 int char_cap; /* Allocated bytes. */
92 /* How to emphasize text. */
95 EMPH_BOLD, /* Overstrike for bold. */
96 EMPH_UNDERLINE, /* Overstrike for underlining. */
97 EMPH_NONE /* No emphasis. */
100 /* ASCII output driver extension record. */
101 struct ascii_driver_ext
105 /* User parameters. */
106 bool append; /* Append if output-file already exists? */
107 bool headers; /* Print headers at top of page? */
108 bool paginate; /* Insert formfeeds? */
109 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
110 enum emphasis_style emphasis; /* How to emphasize text. */
111 int tab_width; /* Width of a tab; 0 not to use tabs. */
112 bool enable_charts; /* Enable charts? */
113 const char *chart_file_name; /* Name of files used for charts. */
115 bool auto_width; /* Use viewwidth as page width? */
116 bool auto_length; /* Use viewlength as page width? */
117 int page_length; /* Page length before subtracting margins. */
118 int top_margin; /* Top margin in lines. */
119 int bottom_margin; /* Bottom margin in lines. */
121 char *box[LNS_COUNT]; /* Line & box drawing characters. */
122 char *init; /* Device initialization string. */
124 /* Internal state. */
125 char *file_name; /* Output file name. */
126 FILE *file; /* Output file. */
127 bool reported_error; /* Reported file open error? */
128 int page_number; /* Current page number. */
129 struct line *lines; /* Page content. */
130 int line_cap; /* Number of lines allocated. */
131 int chart_cnt; /* Number of charts so far. */
134 static void ascii_flush (struct outp_driver *);
135 static int get_default_box_char (size_t idx);
136 static bool update_page_size (struct outp_driver *, bool issue_error);
137 static bool handle_option (void *this, const char *key,
138 const struct string *val);
141 ascii_open_driver (const char *name, int types, struct substring options)
143 struct outp_driver *this;
144 struct ascii_driver_ext *x;
147 this = outp_allocate_driver (&ascii_class, name, types);
149 this->font_height = 1;
150 this->prop_em_width = 1;
151 this->fixed_width = 1;
152 for (i = 0; i < OUTP_L_COUNT; i++)
153 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
155 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
159 x->squeeze_blank_lines = false;
160 x->emphasis = EMPH_BOLD;
162 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
163 x->enable_charts = true;
164 x->auto_width = false;
165 x->auto_length = false;
168 x->bottom_margin = 2;
169 for (i = 0; i < LNS_COUNT; i++)
172 x->file_name = pool_strdup (x->pool, "pspp.list");
174 x->reported_error = false;
180 if (!outp_parse_options (this->name, options, handle_option, this))
183 if (!update_page_size (this, true))
186 for (i = 0; i < LNS_COUNT; i++)
187 if (x->box[i] == NULL)
190 s[0] = get_default_box_char (i);
192 x->box[i] = pool_strdup (x->pool, s);
195 outp_register_driver (this);
200 pool_destroy (x->pool);
201 outp_free_driver (this);
206 get_default_box_char (size_t idx)
208 /* Disassemble IDX into components. */
209 unsigned top = (idx >> LNS_TOP) & 3;
210 unsigned left = (idx >> LNS_LEFT) & 3;
211 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
212 unsigned right = (idx >> LNS_RIGHT) & 3;
214 /* Reassemble components into nibbles in the order TLBR.
215 This makes it easy to read the case labels. */
216 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
222 case 0x0100: case 0x0101: case 0x0001:
225 case 0x1000: case 0x1010: case 0x0010:
228 case 0x0300: case 0x0303: case 0x0003:
229 case 0x0200: case 0x0202: case 0x0002:
233 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
237 /* Re-calculates the page width and length based on settings,
238 margins, and, if "auto" is set, the size of the user's
239 terminal window or GUI output window. */
241 update_page_size (struct outp_driver *this, bool issue_error)
243 struct ascii_driver_ext *x = this->ext;
244 int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0);
247 this->width = settings_get_viewwidth ();
249 x->page_length = settings_get_viewlength ();
251 this->length = x->page_length - margins;
253 if (this->width < 59 || this->length < 15)
257 _("ascii: page excluding margins and headers "
258 "must be at least 59 characters wide by 15 lines long, but "
259 "as configured is only %d characters by %d lines"),
260 this->width, this->length);
261 if (this->width < 59)
263 if (this->length < 15)
266 x->page_length = this->length + margins;
275 ascii_close_driver (struct outp_driver *this)
277 struct ascii_driver_ext *x = this->ext;
280 pool_detach_file (x->pool, x->file);
281 pool_destroy (x->pool);
286 /* Generic option types. */
296 static const struct outp_option option_tab[] =
298 {"headers", boolean_arg, 0},
299 {"paginate", boolean_arg, 1},
300 {"squeeze", boolean_arg, 2},
301 {"append", boolean_arg, 3},
303 {"emphasis", emphasis_arg, 0},
305 {"length", page_size_arg, 0},
306 {"width", page_size_arg, 1},
308 {"top-margin", nonneg_int_arg, 0},
309 {"bottom-margin", nonneg_int_arg, 1},
310 {"tab-width", nonneg_int_arg, 2},
312 {"output-file", string_arg, 0},
313 {"chart-files", string_arg, 1},
314 {"chart-type", string_arg, 2},
315 {"init", string_arg, 3},
321 handle_option (void *this_, const char *key,
322 const struct string *val)
324 struct outp_driver *this = this_;
325 struct ascii_driver_ext *x = this->ext;
329 value = ds_cstr (val);
330 if (!strncmp (key, "box[", 4))
333 int indx = strtol (&key[4], &tail, 4);
334 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
336 error (0, 0, _("ascii: bad index value for `box' key: syntax "
337 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
338 "expressed in base 4"),
342 if (x->box[indx] != NULL)
343 error (0, 0, _("ascii: multiple values for %s"), key);
344 x->box[indx] = pool_strdup (x->pool, value);
348 switch (outp_match_keyword (key, option_tab, &subcat))
351 error (0, 0, _("ascii: unknown parameter `%s'"), key);
358 if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
360 if (!(this->device & OUTP_DEV_SCREEN))
362 /* We only let `screen' devices have `auto'
363 length or width because output to such devices
364 is flushed before each new command. Resizing
365 a device in the middle of output seems like a
367 error (0, 0, _("ascii: only screen devices may have `auto' "
370 else if (subcat == 0)
371 x->auto_length = true;
373 x->auto_width = true;
378 arg = strtol (value, &tail, 0);
379 if (arg < 1 || errno == ERANGE || *tail)
381 error (0, 0, _("ascii: positive integer required as "
389 x->page_length = arg;
401 if (!strcmp (value, "bold"))
402 x->emphasis = EMPH_BOLD;
403 else if (!strcmp (value, "underline"))
404 x->emphasis = EMPH_UNDERLINE;
405 else if (!strcmp (value, "none"))
406 x->emphasis = EMPH_NONE;
409 _("ascii: `emphasis' value must be `bold', "
410 "`underline', or `none'"));
418 arg = strtol (value, &tail, 0);
419 if (arg < 0 || errno == ERANGE || *tail)
422 _("ascii: zero or positive integer required as `%s' value"),
432 x->bottom_margin = arg;
445 if (!strcmp (value, "on") || !strcmp (value, "true")
446 || !strcmp (value, "yes") || atoi (value))
448 else if (!strcmp (value, "off") || !strcmp (value, "false")
449 || !strcmp (value, "no") || !strcmp (value, "0"))
453 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
459 x->headers = setting;
462 x->paginate = setting;
465 x->squeeze_blank_lines = setting;
479 x->file_name = pool_strdup (x->pool, value);
482 if (ds_find_char (val, '#') != SIZE_MAX)
483 x->chart_file_name = pool_strdup (x->pool, value);
485 error (0, 0, _("`chart-files' value must contain `#'"));
488 if (!strcmp (value, "png"))
489 x->enable_charts = true;
490 else if (!strcmp (value, "none"))
491 x->enable_charts = false;
495 _("ascii: `png' or `none' expected for `chart-type'"));
500 x->init = pool_strdup (x->pool, value);
512 ascii_open_page (struct outp_driver *this)
514 struct ascii_driver_ext *x = this->ext;
517 update_page_size (this, false);
521 x->file = fn_open (x->file_name, x->append ? "a" : "w");
524 pool_attach_file (x->pool, x->file);
526 fputs (x->init, x->file);
530 /* Report the error to the user and complete
531 initialization. If we do not finish initialization,
532 then calls to other driver functions will segfault
533 later. It would be better to simply drop the driver
534 entirely, but we do not have a convenient mechanism
536 if (!x->reported_error)
537 error (0, errno, _("ascii: opening output file \"%s\""),
539 x->reported_error = true;
545 if (this->length > x->line_cap)
547 x->lines = pool_nrealloc (x->pool,
548 x->lines, this->length, sizeof *x->lines);
549 for (i = x->line_cap; i < this->length; i++)
551 struct line *line = &x->lines[i];
555 x->line_cap = this->length;
558 for (i = 0; i < this->length; i++)
559 x->lines[i].char_cnt = 0;
562 /* Ensures that at least the first LENGTH characters of line Y in
563 THIS driver identified X have been cleared out. */
565 expand_line (struct outp_driver *this, int y, int length)
567 struct ascii_driver_ext *ext = this->ext;
568 struct line *line = &ext->lines[y];
569 if (line->char_cnt < length)
572 if (line->char_cap < length)
574 line->char_cap = MIN (length * 2, this->width);
575 line->chars = pool_nrealloc (ext->pool,
577 line->char_cap, sizeof *line->chars);
579 for (x = line->char_cnt; x < length; x++)
580 line->chars[x] = ' ';
581 line->char_cnt = length;
586 ascii_line (struct outp_driver *this,
587 int x0, int y0, int x1, int y1,
588 enum outp_line_style top, enum outp_line_style left,
589 enum outp_line_style bottom, enum outp_line_style right)
591 struct ascii_driver_ext *ext = this->ext;
593 unsigned short value;
595 assert (this->page_open);
597 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
599 #if !SUPPRESS_WARNINGS
600 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
601 x0, y0, x1, y1, this->width, this->length);
607 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
608 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
609 for (y = y0; y < y1; y++)
613 expand_line (this, y, x1);
614 for (x = x0; x < x1; x++)
615 ext->lines[y].chars[x] = value;
620 text_draw (struct outp_driver *this,
623 enum outp_justification justification, int width,
624 const char *string, size_t length)
626 struct ascii_driver_ext *ext = this->ext;
627 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
631 switch (justification)
636 x += (width - length + 1) / 2;
645 if (y >= this->length || x >= this->width)
648 if (x + length > this->width)
649 length = this->width - x;
651 line_len = x + length;
653 expand_line (this, y, line_len);
655 ext->lines[y].chars[x++] = *string++ | attr;
658 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
659 to the maximum width of a line and *HEIGHT to the number of
660 lines, if those arguments are non-null. Actually draws the
661 text if DRAW is true. */
663 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
664 int *width, int *height)
669 const char *cp = ss_data (text->string);
672 height_left = text->v;
674 while (height_left > 0)
680 /* Initially the line is up to text->h characters long. */
681 chars_left = ss_end (text->string) - cp;
684 line_len = MIN (chars_left, text->h);
686 /* A new-line terminates the line prematurely. */
687 end = memchr (cp, '\n', line_len);
691 /* Don't cut off words if it can be avoided. */
692 if (cp + line_len < ss_end (text->string))
694 size_t space_len = line_len;
695 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
698 line_len = space_len;
705 text->x, text->y + (text->v - height_left),
706 text->justification, text->h,
711 if (line_len > max_width)
712 max_width = line_len;
716 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
723 *height = text->v - height_left;
727 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
728 int *width, int *height)
730 delineate (this, t, false, width, height);
734 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
736 assert (this->page_open);
737 delineate (this, t, true, NULL, NULL);
740 /* ascii_close_page () and support routines. */
742 /* Writes the LENGTH characters in S to OUT. */
744 output_line (struct outp_driver *this, const struct line *line,
747 struct ascii_driver_ext *ext = this->ext;
748 const unsigned short *s = line->chars;
751 for (length = line->char_cnt; length-- > 0; s++)
753 ds_put_cstr (out, ext->box[*s & 0xff]);
756 if (*s & ATTR_EMPHASIS)
758 if (ext->emphasis == EMPH_BOLD)
760 ds_put_char (out, *s);
761 ds_put_char (out, '\b');
763 else if (ext->emphasis == EMPH_UNDERLINE)
764 ds_put_cstr (out, "_\b");
766 ds_put_char (out, *s);
771 append_lr_justified (struct string *out, int width,
772 const char *left, const char *right)
774 ds_put_char_multiple (out, ' ', width);
777 size_t length = MIN (strlen (left), width);
778 memcpy (ds_end (out) - width, left, length);
782 size_t length = MIN (strlen (right), width);
783 memcpy (ds_end (out) - length, right, length);
785 ds_put_char (out, '\n');
789 dump_output (struct outp_driver *this, struct string *out)
791 struct ascii_driver_ext *x = this->ext;
792 fwrite (ds_data (out), ds_length (out), 1, x->file);
797 ascii_close_page (struct outp_driver *this)
799 struct ascii_driver_ext *x = this->ext;
806 ds_init_empty (&out);
808 ds_put_char_multiple (&out, '\n', x->top_margin);
813 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
814 r2 = xasprintf ("%s - %s" , version, host_system);
816 append_lr_justified (&out, this->width, outp_title, r1);
817 append_lr_justified (&out, this->width, outp_subtitle, r2);
818 ds_put_char (&out, '\n');
823 dump_output (this, &out);
825 for (line_num = 0; line_num < this->length; line_num++)
828 /* Squeeze multiple blank lines into a single blank line if
830 if (x->squeeze_blank_lines)
832 if (line_num >= x->line_cap)
835 && x->lines[line_num].char_cnt == 0
836 && x->lines[line_num - 1].char_cnt == 0)
840 if (line_num < x->line_cap)
841 output_line (this, &x->lines[line_num], &out);
842 ds_put_char (&out, '\n');
843 dump_output (this, &out);
846 ds_put_char_multiple (&out, '\n', x->bottom_margin);
848 ds_put_char (&out, '\f');
850 dump_output (this, &out);
854 /* Flushes all output to the user and lets the user deal with it.
855 This is applied only to output drivers that are designated as
856 "screen" drivers that the user is interacting with in real
859 ascii_flush (struct outp_driver *this)
861 struct ascii_driver_ext *x = this->ext;
864 if (fn_close (x->file_name, x->file) != 0)
865 error (0, errno, _("ascii: closing output file \"%s\""),
867 pool_detach_file (x->pool, x->file);
873 ascii_output_chart (struct outp_driver *this, const struct chart *chart)
875 struct ascii_driver_ext *x = this->ext;
880 /* Draw chart into separate file */
881 file_name = chart_draw_png (chart, x->chart_file_name, x->chart_cnt++);
883 /* Mention chart in output.
884 First advance current position. */
885 if (!this->page_open)
886 outp_open_page (this);
890 if (this->cp_y >= this->length)
892 outp_close_page (this);
893 outp_open_page (this);
897 /* Then write the text. */
898 text = xasprintf ("See %s for a chart.", file_name);
900 t.justification = OUTP_LEFT;
901 t.string = ss_cstr (text);
906 ascii_text_draw (this, &t);
913 const struct outp_class ascii_class =