1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007 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 <libpspp/alloc.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>
39 #define _(msgid) gettext (msgid)
41 /* ASCII driver options: (defaults listed first)
43 output-file="pspp.list"
44 append=no|yes If output-file exists, append to it?
45 chart-files="pspp-#.png" Name used for charts.
46 chart-type=png Format of charts (use "none" to disable).
48 paginate=on|off Formfeeds are desired?
49 tab-width=8 Width of a tab; 0 to not use tabs.
51 headers=on|off Put headers at top of page?
52 emphasis=bold|underline|none Style to use for emphasis.
55 squeeze=off|on Squeeze multiple newlines into exactly one.
60 box[x]="strng" Sets box character X (X in base 4: 0-3333).
61 init="string" Set initialization string.
64 /* Disable messages by failed range checks. */
65 /*#define SUPPRESS_WARNINGS 1 */
67 /* Line styles bit shifts. */
78 /* Character attributes. */
79 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
80 #define ATTR_BOX 0x200 /* Line drawing character. */
85 unsigned short *chars; /* Characters and attributes. */
86 int char_cnt; /* Length. */
87 int char_cap; /* Allocated bytes. */
90 /* How to emphasize text. */
93 EMPH_BOLD, /* Overstrike for bold. */
94 EMPH_UNDERLINE, /* Overstrike for underlining. */
95 EMPH_NONE /* No emphasis. */
98 /* ASCII output driver extension record. */
99 struct ascii_driver_ext
103 /* User parameters. */
104 bool append; /* Append if output-file already exists? */
105 bool headers; /* Print headers at top of page? */
106 bool paginate; /* Insert formfeeds? */
107 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
108 enum emphasis_style emphasis; /* How to emphasize text. */
109 int tab_width; /* Width of a tab; 0 not to use tabs. */
110 const char *chart_type; /* Type of charts to output; NULL for none. */
111 const char *chart_file_name; /* Name of files used for charts. */
113 int page_length; /* Page length before subtracting margins. */
114 int top_margin; /* Top margin in lines. */
115 int bottom_margin; /* Bottom margin in lines. */
117 char *box[LNS_COUNT]; /* Line & box drawing characters. */
118 char *init; /* Device initialization string. */
120 /* Internal state. */
121 char *file_name; /* Output file name. */
122 FILE *file; /* Output file. */
123 bool reported_error; /* Reported file open error? */
124 int page_number; /* Current page number. */
125 struct line *lines; /* Page content. */
126 int line_cap; /* Number of lines allocated. */
127 int chart_cnt; /* Number of charts so far. */
130 static void ascii_flush (struct outp_driver *);
131 static int get_default_box_char (size_t idx);
132 static bool handle_option (struct outp_driver *this, const char *key,
133 const struct string *val);
136 ascii_open_driver (struct outp_driver *this, struct substring options)
138 struct ascii_driver_ext *x;
142 this->font_height = 1;
143 this->prop_em_width = 1;
144 this->fixed_width = 1;
145 for (i = 0; i < OUTP_L_COUNT; i++)
146 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
148 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
152 x->squeeze_blank_lines = false;
153 x->emphasis = EMPH_BOLD;
155 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
156 x->chart_type = pool_strdup (x->pool, "png");
159 x->bottom_margin = 2;
160 for (i = 0; i < LNS_COUNT; i++)
163 x->file_name = pool_strdup (x->pool, "pspp.list");
165 x->reported_error = false;
171 if (!outp_parse_options (options, handle_option, this))
174 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
178 if (this->width < 59 || this->length < 15)
181 _("ascii: page excluding margins and headers "
182 "must be at least 59 characters wide by 15 lines long, but as "
183 "configured is only %d characters by %d lines"),
184 this->width, this->length);
188 for (i = 0; i < LNS_COUNT; i++)
189 if (x->box[i] == NULL)
192 s[0] = get_default_box_char (i);
194 x->box[i] = pool_strdup (x->pool, s);
200 pool_destroy (x->pool);
205 get_default_box_char (size_t idx)
207 /* Disassemble IDX into components. */
208 unsigned top = (idx >> LNS_TOP) & 3;
209 unsigned left = (idx >> LNS_LEFT) & 3;
210 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
211 unsigned right = (idx >> LNS_RIGHT) & 3;
213 /* Reassemble components into nibbles in the order TLBR.
214 This makes it easy to read the case labels. */
215 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
221 case 0x0100: case 0x0101: case 0x0001:
224 case 0x1000: case 0x1010: case 0x0010:
227 case 0x0300: case 0x0303: case 0x0003:
228 case 0x0200: case 0x0202: case 0x0002:
232 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
237 ascii_close_driver (struct outp_driver *this)
239 struct ascii_driver_ext *x = this->ext;
242 pool_detach_file (x->pool, x->file);
243 pool_destroy (x->pool);
248 /* Generic option types. */
258 static const struct outp_option option_tab[] =
260 {"headers", boolean_arg, 0},
261 {"paginate", boolean_arg, 1},
262 {"squeeze", boolean_arg, 2},
263 {"append", boolean_arg, 3},
265 {"emphasis", emphasis_arg, 0},
267 {"length", pos_int_arg, 0},
268 {"width", pos_int_arg, 1},
270 {"top-margin", nonneg_int_arg, 0},
271 {"bottom-margin", nonneg_int_arg, 1},
272 {"tab-width", nonneg_int_arg, 2},
274 {"output-file", string_arg, 0},
275 {"chart-files", string_arg, 1},
276 {"chart-type", string_arg, 2},
277 {"init", string_arg, 3},
283 handle_option (struct outp_driver *this, const char *key,
284 const struct string *val)
286 struct ascii_driver_ext *x = this->ext;
290 value = ds_cstr (val);
291 if (!strncmp (key, "box[", 4))
294 int indx = strtol (&key[4], &tail, 4);
295 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
297 error (0, 0, _("ascii: bad index value for `box' key: syntax "
298 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
299 "expressed in base 4"),
303 if (x->box[indx] != NULL)
304 error (0, 0, _("ascii: multiple values for %s"), key);
305 x->box[indx] = pool_strdup (x->pool, value);
309 switch (outp_match_keyword (key, option_tab, &subcat))
312 error (0, 0, _("ascii: unknown parameter `%s'"), key);
320 arg = strtol (value, &tail, 0);
321 if (arg < 1 || errno == ERANGE || *tail)
323 error (0, 0, _("ascii: positive integer required as `%s' value"),
330 x->page_length = arg;
341 if (!strcmp (value, "bold"))
342 x->emphasis = EMPH_BOLD;
343 else if (!strcmp (value, "underline"))
344 x->emphasis = EMPH_UNDERLINE;
345 else if (!strcmp (value, "none"))
346 x->emphasis = EMPH_NONE;
349 _("ascii: `emphasis' value must be `bold', "
350 "`underline', or `none'"));
358 arg = strtol (value, &tail, 0);
359 if (arg < 0 || errno == ERANGE || *tail)
362 _("ascii: zero or positive integer required as `%s' value"),
372 x->bottom_margin = arg;
385 if (!strcmp (value, "on") || !strcmp (value, "true")
386 || !strcmp (value, "yes") || atoi (value))
388 else if (!strcmp (value, "off") || !strcmp (value, "false")
389 || !strcmp (value, "no") || !strcmp (value, "0"))
393 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
399 x->headers = setting;
402 x->paginate = setting;
405 x->squeeze_blank_lines = setting;
419 x->file_name = pool_strdup (x->pool, value);
422 if (ds_find_char (val, '#') != SIZE_MAX)
423 x->chart_file_name = pool_strdup (x->pool, value);
425 error (0, 0, _("`chart-files' value must contain `#'"));
428 if (value[0] != '\0')
429 x->chart_type = pool_strdup (x->pool, value);
431 x->chart_type = NULL;
434 x->init = pool_strdup (x->pool, value);
446 ascii_open_page (struct outp_driver *this)
448 struct ascii_driver_ext *x = this->ext;
453 x->file = fn_open (x->file_name, x->append ? "a" : "w");
456 pool_attach_file (x->pool, x->file);
458 fputs (x->init, x->file);
462 /* Report the error to the user and complete
463 initialization. If we do not finish initialization,
464 then calls to other driver functions will segfault
465 later. It would be better to simply drop the driver
466 entirely, but we do not have a convenient mechanism
468 if (!x->reported_error)
469 error (0, errno, _("ascii: opening output file \"%s\""),
471 x->reported_error = true;
477 if (this->length > x->line_cap)
479 x->lines = pool_nrealloc (x->pool,
480 x->lines, this->length, sizeof *x->lines);
481 for (i = x->line_cap; i < this->length; i++)
483 struct line *line = &x->lines[i];
487 x->line_cap = this->length;
490 for (i = 0; i < this->length; i++)
491 x->lines[i].char_cnt = 0;
494 /* Ensures that at least the first LENGTH characters of line Y in
495 THIS driver identified X have been cleared out. */
497 expand_line (struct outp_driver *this, int y, int length)
499 struct ascii_driver_ext *ext = this->ext;
500 struct line *line = &ext->lines[y];
501 if (line->char_cnt < length)
504 if (line->char_cap < length)
506 line->char_cap = MIN (length * 2, this->width);
507 line->chars = pool_nrealloc (ext->pool,
509 line->char_cap, sizeof *line->chars);
511 for (x = line->char_cnt; x < length; x++)
512 line->chars[x] = ' ';
513 line->char_cnt = length;
518 ascii_line (struct outp_driver *this,
519 int x0, int y0, int x1, int y1,
520 enum outp_line_style top, enum outp_line_style left,
521 enum outp_line_style bottom, enum outp_line_style right)
523 struct ascii_driver_ext *ext = this->ext;
525 unsigned short value;
527 assert (this->page_open);
529 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
531 #if !SUPPRESS_WARNINGS
532 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
533 x0, y0, x1, y1, this->width, this->length);
539 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
540 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
541 for (y = y0; y < y1; y++)
545 expand_line (this, y, x1);
546 for (x = x0; x < x1; x++)
547 ext->lines[y].chars[x] = value;
552 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
554 extern struct som_table_class tab_table_class;
556 assert (s->class == &tab_table_class);
557 assert (s->type == SOM_CHART);
561 text_draw (struct outp_driver *this,
564 enum outp_justification justification, int width,
565 const char *string, size_t length)
567 struct ascii_driver_ext *ext = this->ext;
568 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
572 switch (justification)
577 x += (width - length + 1) / 2;
586 if (y >= this->length || x >= this->width)
589 if (x + length > this->width)
590 length = this->width - x;
592 line_len = x + length;
594 expand_line (this, y, line_len);
596 ext->lines[y].chars[x++] = *string++ | attr;
599 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
600 to the maximum width of a line and *HEIGHT to the number of
601 lines, if those arguments are non-null. Actually draws the
602 text if DRAW is true. */
604 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
605 int *width, int *height)
610 const char *cp = ss_data (text->string);
613 height_left = text->v;
615 while (height_left > 0)
621 /* Initially the line is up to text->h characters long. */
622 chars_left = ss_end (text->string) - cp;
625 line_len = MIN (chars_left, text->h);
627 /* A new-line terminates the line prematurely. */
628 end = memchr (cp, '\n', line_len);
632 /* Don't cut off words if it can be avoided. */
633 if (cp + line_len < ss_end (text->string))
635 size_t space_len = line_len;
636 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
639 line_len = space_len;
646 text->x, text->y + (text->v - height_left),
647 text->justification, text->h,
652 if (line_len > max_width)
653 max_width = line_len;
657 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
664 *height = text->v - height_left;
668 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
669 int *width, int *height)
671 delineate (this, t, false, width, height);
675 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
677 assert (this->page_open);
678 delineate (this, t, true, NULL, NULL);
681 /* ascii_close_page () and support routines. */
683 /* Writes the LENGTH characters in S to OUT. */
685 output_line (struct outp_driver *this, const struct line *line,
688 struct ascii_driver_ext *ext = this->ext;
689 const unsigned short *s = line->chars;
692 for (length = line->char_cnt; length-- > 0; s++)
694 ds_put_cstr (out, ext->box[*s & 0xff]);
697 if (*s & ATTR_EMPHASIS)
699 if (ext->emphasis == EMPH_BOLD)
701 ds_put_char (out, *s);
702 ds_put_char (out, '\b');
704 else if (ext->emphasis == EMPH_UNDERLINE)
705 ds_put_cstr (out, "_\b");
707 ds_put_char (out, *s);
712 append_lr_justified (struct string *out, int width,
713 const char *left, const char *right)
715 ds_put_char_multiple (out, ' ', width);
718 size_t length = MIN (strlen (left), width);
719 memcpy (ds_end (out) - width, left, length);
723 size_t length = MIN (strlen (right), width);
724 memcpy (ds_end (out) - length, right, length);
726 ds_put_char (out, '\n');
730 dump_output (struct outp_driver *this, struct string *out)
732 struct ascii_driver_ext *x = this->ext;
733 fwrite (ds_data (out), ds_length (out), 1, x->file);
738 ascii_close_page (struct outp_driver *this)
740 struct ascii_driver_ext *x = this->ext;
747 ds_init_empty (&out);
749 ds_put_char_multiple (&out, '\n', x->top_margin);
754 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
755 r2 = xasprintf ("%s - %s" , version, host_system);
757 append_lr_justified (&out, this->width, outp_title, r1);
758 append_lr_justified (&out, this->width, outp_subtitle, r2);
759 ds_put_char (&out, '\n');
764 dump_output (this, &out);
766 for (line_num = 0; line_num < this->length; line_num++)
769 /* Squeeze multiple blank lines into a single blank line if
771 if (x->squeeze_blank_lines)
773 if (line_num >= x->line_cap)
776 && x->lines[line_num].char_cnt == 0
777 && x->lines[line_num - 1].char_cnt == 0)
781 if (line_num < x->line_cap)
782 output_line (this, &x->lines[line_num], &out);
783 ds_put_char (&out, '\n');
784 dump_output (this, &out);
787 ds_put_char_multiple (&out, '\n', x->bottom_margin);
789 ds_put_char (&out, '\f');
791 dump_output (this, &out);
795 /* Flushes all output to the user and lets the user deal with it.
796 This is applied only to output drivers that are designated as
797 "screen" drivers that the user is interacting with in real
800 ascii_flush (struct outp_driver *this)
802 struct ascii_driver_ext *x = this->ext;
805 if (fn_close (x->file_name, x->file) != 0)
806 error (0, errno, _("ascii: closing output file \"%s\""),
808 pool_detach_file (x->pool, x->file);
814 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
816 struct ascii_driver_ext *x = this->ext;
820 if (x->chart_type == NULL)
823 /* Initialize chart. */
824 chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
825 if (ch->file_name == NULL)
828 /* Mention chart in output.
829 First advance current position. */
830 if (!this->page_open)
831 outp_open_page (this);
835 if (this->cp_y >= this->length)
837 outp_close_page (this);
838 outp_open_page (this);
842 /* Then write the text. */
843 text = xasprintf ("See %s for a chart.", ch->file_name);
845 t.justification = OUTP_LEFT;
846 t.string = ss_cstr (text);
851 ascii_text_draw (this, &t);
858 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
860 struct ascii_driver_ext *x = this->ext;
861 if (x->chart_type != NULL)
862 chart_finalise_separate (ch);
865 const struct outp_class ascii_class =
883 ascii_chart_initialise,