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>
40 #define _(msgid) gettext (msgid)
42 /* ASCII driver options: (defaults listed first)
44 output-file="pspp.list"
45 append=no|yes If output-file exists, append to it?
46 chart-files="pspp-#.png" Name used for charts.
47 chart-type=png Format of charts (use "none" to disable).
49 paginate=on|off Formfeeds are desired?
50 tab-width=8 Width of a tab; 0 to not use tabs.
52 headers=on|off Put headers at top of page?
53 emphasis=bold|underline|none Style to use for emphasis.
56 squeeze=off|on Squeeze multiple newlines into exactly one.
61 box[x]="strng" Sets box character X (X in base 4: 0-3333).
62 init="string" Set initialization string.
65 /* Disable messages by failed range checks. */
66 /*#define SUPPRESS_WARNINGS 1 */
68 /* Line styles bit shifts. */
79 /* Character attributes. */
80 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
81 #define ATTR_BOX 0x200 /* Line drawing character. */
86 unsigned short *chars; /* Characters and attributes. */
87 int char_cnt; /* Length. */
88 int char_cap; /* Allocated bytes. */
91 /* How to emphasize text. */
94 EMPH_BOLD, /* Overstrike for bold. */
95 EMPH_UNDERLINE, /* Overstrike for underlining. */
96 EMPH_NONE /* No emphasis. */
99 /* ASCII output driver extension record. */
100 struct ascii_driver_ext
104 /* User parameters. */
105 bool append; /* Append if output-file already exists? */
106 bool headers; /* Print headers at top of page? */
107 bool paginate; /* Insert formfeeds? */
108 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
109 enum emphasis_style emphasis; /* How to emphasize text. */
110 int tab_width; /* Width of a tab; 0 not to use tabs. */
111 const char *chart_type; /* Type of charts to output; NULL for none. */
112 const char *chart_file_name; /* Name of files used for charts. */
114 bool auto_width; /* Use viewwidth as page width? */
115 bool auto_length; /* Use viewlength as page width? */
116 int page_length; /* Page length before subtracting margins. */
117 int top_margin; /* Top margin in lines. */
118 int bottom_margin; /* Bottom margin in lines. */
120 char *box[LNS_COUNT]; /* Line & box drawing characters. */
121 char *init; /* Device initialization string. */
123 /* Internal state. */
124 char *file_name; /* Output file name. */
125 FILE *file; /* Output file. */
126 bool reported_error; /* Reported file open error? */
127 int page_number; /* Current page number. */
128 struct line *lines; /* Page content. */
129 int line_cap; /* Number of lines allocated. */
130 int chart_cnt; /* Number of charts so far. */
133 static void ascii_flush (struct outp_driver *);
134 static int get_default_box_char (size_t idx);
135 static bool update_page_size (struct outp_driver *, bool issue_error);
136 static bool handle_option (struct outp_driver *this, const char *key,
137 const struct string *val);
140 ascii_open_driver (const char *name, int types, struct substring options)
142 struct outp_driver *this;
143 struct ascii_driver_ext *x;
146 this = outp_allocate_driver (&ascii_class, name, types);
148 this->font_height = 1;
149 this->prop_em_width = 1;
150 this->fixed_width = 1;
151 for (i = 0; i < OUTP_L_COUNT; i++)
152 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
154 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
158 x->squeeze_blank_lines = false;
159 x->emphasis = EMPH_BOLD;
161 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
162 x->chart_type = pool_strdup (x->pool, "png");
163 x->auto_width = false;
164 x->auto_length = false;
167 x->bottom_margin = 2;
168 for (i = 0; i < LNS_COUNT; i++)
171 x->file_name = pool_strdup (x->pool, "pspp.list");
173 x->reported_error = false;
179 if (!outp_parse_options (options, handle_option, this))
182 if (!update_page_size (this, true))
185 for (i = 0; i < LNS_COUNT; i++)
186 if (x->box[i] == NULL)
189 s[0] = get_default_box_char (i);
191 x->box[i] = pool_strdup (x->pool, s);
194 outp_register_driver (this);
199 pool_destroy (x->pool);
200 outp_free_driver (this);
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 ? '#' : '+';
236 /* Re-calculates the page width and length based on settings,
237 margins, and, if "auto" is set, the size of the user's
238 terminal window or GUI output window. */
240 update_page_size (struct outp_driver *this, bool issue_error)
242 struct ascii_driver_ext *x = this->ext;
243 int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0);
246 this->width = settings_get_viewwidth ();
248 x->page_length = settings_get_viewlength ();
250 this->length = x->page_length - margins;
252 if (this->width < 59 || this->length < 15)
256 _("ascii: page excluding margins and headers "
257 "must be at least 59 characters wide by 15 lines long, but "
258 "as configured is only %d characters by %d lines"),
259 this->width, this->length);
260 if (this->width < 59)
262 if (this->length < 15)
265 x->page_length = this->length + margins;
274 ascii_close_driver (struct outp_driver *this)
276 struct ascii_driver_ext *x = this->ext;
279 pool_detach_file (x->pool, x->file);
280 pool_destroy (x->pool);
285 /* Generic option types. */
295 static const struct outp_option option_tab[] =
297 {"headers", boolean_arg, 0},
298 {"paginate", boolean_arg, 1},
299 {"squeeze", boolean_arg, 2},
300 {"append", boolean_arg, 3},
302 {"emphasis", emphasis_arg, 0},
304 {"length", page_size_arg, 0},
305 {"width", page_size_arg, 1},
307 {"top-margin", nonneg_int_arg, 0},
308 {"bottom-margin", nonneg_int_arg, 1},
309 {"tab-width", nonneg_int_arg, 2},
311 {"output-file", string_arg, 0},
312 {"chart-files", string_arg, 1},
313 {"chart-type", string_arg, 2},
314 {"init", string_arg, 3},
320 handle_option (struct outp_driver *this, const char *key,
321 const struct string *val)
323 struct ascii_driver_ext *x = this->ext;
327 value = ds_cstr (val);
328 if (!strncmp (key, "box[", 4))
331 int indx = strtol (&key[4], &tail, 4);
332 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
334 error (0, 0, _("ascii: bad index value for `box' key: syntax "
335 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
336 "expressed in base 4"),
340 if (x->box[indx] != NULL)
341 error (0, 0, _("ascii: multiple values for %s"), key);
342 x->box[indx] = pool_strdup (x->pool, value);
346 switch (outp_match_keyword (key, option_tab, &subcat))
349 error (0, 0, _("ascii: unknown parameter `%s'"), key);
356 if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
358 if (!(this->device & OUTP_DEV_SCREEN))
360 /* We only let `screen' devices have `auto'
361 length or width because output to such devices
362 is flushed before each new command. Resizing
363 a device in the middle of output seems like a
365 error (0, 0, _("ascii: only screen devices may have `auto' "
368 else if (subcat == 0)
369 x->auto_length = true;
371 x->auto_width = true;
376 arg = strtol (value, &tail, 0);
377 if (arg < 1 || errno == ERANGE || *tail)
379 error (0, 0, _("ascii: positive integer required as "
387 x->page_length = arg;
399 if (!strcmp (value, "bold"))
400 x->emphasis = EMPH_BOLD;
401 else if (!strcmp (value, "underline"))
402 x->emphasis = EMPH_UNDERLINE;
403 else if (!strcmp (value, "none"))
404 x->emphasis = EMPH_NONE;
407 _("ascii: `emphasis' value must be `bold', "
408 "`underline', or `none'"));
416 arg = strtol (value, &tail, 0);
417 if (arg < 0 || errno == ERANGE || *tail)
420 _("ascii: zero or positive integer required as `%s' value"),
430 x->bottom_margin = arg;
443 if (!strcmp (value, "on") || !strcmp (value, "true")
444 || !strcmp (value, "yes") || atoi (value))
446 else if (!strcmp (value, "off") || !strcmp (value, "false")
447 || !strcmp (value, "no") || !strcmp (value, "0"))
451 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
457 x->headers = setting;
460 x->paginate = setting;
463 x->squeeze_blank_lines = setting;
477 x->file_name = pool_strdup (x->pool, value);
480 if (ds_find_char (val, '#') != SIZE_MAX)
481 x->chart_file_name = pool_strdup (x->pool, value);
483 error (0, 0, _("`chart-files' value must contain `#'"));
486 if (value[0] != '\0')
487 x->chart_type = pool_strdup (x->pool, value);
489 x->chart_type = NULL;
492 x->init = pool_strdup (x->pool, value);
504 ascii_open_page (struct outp_driver *this)
506 struct ascii_driver_ext *x = this->ext;
509 update_page_size (this, false);
513 x->file = fn_open (x->file_name, x->append ? "a" : "w");
516 pool_attach_file (x->pool, x->file);
518 fputs (x->init, x->file);
522 /* Report the error to the user and complete
523 initialization. If we do not finish initialization,
524 then calls to other driver functions will segfault
525 later. It would be better to simply drop the driver
526 entirely, but we do not have a convenient mechanism
528 if (!x->reported_error)
529 error (0, errno, _("ascii: opening output file \"%s\""),
531 x->reported_error = true;
537 if (this->length > x->line_cap)
539 x->lines = pool_nrealloc (x->pool,
540 x->lines, this->length, sizeof *x->lines);
541 for (i = x->line_cap; i < this->length; i++)
543 struct line *line = &x->lines[i];
547 x->line_cap = this->length;
550 for (i = 0; i < this->length; i++)
551 x->lines[i].char_cnt = 0;
554 /* Ensures that at least the first LENGTH characters of line Y in
555 THIS driver identified X have been cleared out. */
557 expand_line (struct outp_driver *this, int y, int length)
559 struct ascii_driver_ext *ext = this->ext;
560 struct line *line = &ext->lines[y];
561 if (line->char_cnt < length)
564 if (line->char_cap < length)
566 line->char_cap = MIN (length * 2, this->width);
567 line->chars = pool_nrealloc (ext->pool,
569 line->char_cap, sizeof *line->chars);
571 for (x = line->char_cnt; x < length; x++)
572 line->chars[x] = ' ';
573 line->char_cnt = length;
578 ascii_line (struct outp_driver *this,
579 int x0, int y0, int x1, int y1,
580 enum outp_line_style top, enum outp_line_style left,
581 enum outp_line_style bottom, enum outp_line_style right)
583 struct ascii_driver_ext *ext = this->ext;
585 unsigned short value;
587 assert (this->page_open);
589 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
591 #if !SUPPRESS_WARNINGS
592 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
593 x0, y0, x1, y1, this->width, this->length);
599 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
600 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
601 for (y = y0; y < y1; y++)
605 expand_line (this, y, x1);
606 for (x = x0; x < x1; x++)
607 ext->lines[y].chars[x] = value;
612 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
614 extern struct som_table_class tab_table_class;
616 assert (s->class == &tab_table_class);
617 assert (s->type == SOM_CHART);
621 text_draw (struct outp_driver *this,
624 enum outp_justification justification, int width,
625 const char *string, size_t length)
627 struct ascii_driver_ext *ext = this->ext;
628 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
632 switch (justification)
637 x += (width - length + 1) / 2;
646 if (y >= this->length || x >= this->width)
649 if (x + length > this->width)
650 length = this->width - x;
652 line_len = x + length;
654 expand_line (this, y, line_len);
656 ext->lines[y].chars[x++] = *string++ | attr;
659 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
660 to the maximum width of a line and *HEIGHT to the number of
661 lines, if those arguments are non-null. Actually draws the
662 text if DRAW is true. */
664 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
665 int *width, int *height)
670 const char *cp = ss_data (text->string);
673 height_left = text->v;
675 while (height_left > 0)
681 /* Initially the line is up to text->h characters long. */
682 chars_left = ss_end (text->string) - cp;
685 line_len = MIN (chars_left, text->h);
687 /* A new-line terminates the line prematurely. */
688 end = memchr (cp, '\n', line_len);
692 /* Don't cut off words if it can be avoided. */
693 if (cp + line_len < ss_end (text->string))
695 size_t space_len = line_len;
696 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
699 line_len = space_len;
706 text->x, text->y + (text->v - height_left),
707 text->justification, text->h,
712 if (line_len > max_width)
713 max_width = line_len;
717 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
724 *height = text->v - height_left;
728 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
729 int *width, int *height)
731 delineate (this, t, false, width, height);
735 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
737 assert (this->page_open);
738 delineate (this, t, true, NULL, NULL);
741 /* ascii_close_page () and support routines. */
743 /* Writes the LENGTH characters in S to OUT. */
745 output_line (struct outp_driver *this, const struct line *line,
748 struct ascii_driver_ext *ext = this->ext;
749 const unsigned short *s = line->chars;
752 for (length = line->char_cnt; length-- > 0; s++)
754 ds_put_cstr (out, ext->box[*s & 0xff]);
757 if (*s & ATTR_EMPHASIS)
759 if (ext->emphasis == EMPH_BOLD)
761 ds_put_char (out, *s);
762 ds_put_char (out, '\b');
764 else if (ext->emphasis == EMPH_UNDERLINE)
765 ds_put_cstr (out, "_\b");
767 ds_put_char (out, *s);
772 append_lr_justified (struct string *out, int width,
773 const char *left, const char *right)
775 ds_put_char_multiple (out, ' ', width);
778 size_t length = MIN (strlen (left), width);
779 memcpy (ds_end (out) - width, left, length);
783 size_t length = MIN (strlen (right), width);
784 memcpy (ds_end (out) - length, right, length);
786 ds_put_char (out, '\n');
790 dump_output (struct outp_driver *this, struct string *out)
792 struct ascii_driver_ext *x = this->ext;
793 fwrite (ds_data (out), ds_length (out), 1, x->file);
798 ascii_close_page (struct outp_driver *this)
800 struct ascii_driver_ext *x = this->ext;
807 ds_init_empty (&out);
809 ds_put_char_multiple (&out, '\n', x->top_margin);
814 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
815 r2 = xasprintf ("%s - %s" , version, host_system);
817 append_lr_justified (&out, this->width, outp_title, r1);
818 append_lr_justified (&out, this->width, outp_subtitle, r2);
819 ds_put_char (&out, '\n');
824 dump_output (this, &out);
826 for (line_num = 0; line_num < this->length; line_num++)
829 /* Squeeze multiple blank lines into a single blank line if
831 if (x->squeeze_blank_lines)
833 if (line_num >= x->line_cap)
836 && x->lines[line_num].char_cnt == 0
837 && x->lines[line_num - 1].char_cnt == 0)
841 if (line_num < x->line_cap)
842 output_line (this, &x->lines[line_num], &out);
843 ds_put_char (&out, '\n');
844 dump_output (this, &out);
847 ds_put_char_multiple (&out, '\n', x->bottom_margin);
849 ds_put_char (&out, '\f');
851 dump_output (this, &out);
855 /* Flushes all output to the user and lets the user deal with it.
856 This is applied only to output drivers that are designated as
857 "screen" drivers that the user is interacting with in real
860 ascii_flush (struct outp_driver *this)
862 struct ascii_driver_ext *x = this->ext;
865 if (fn_close (x->file_name, x->file) != 0)
866 error (0, errno, _("ascii: closing output file \"%s\""),
868 pool_detach_file (x->pool, x->file);
874 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
876 struct ascii_driver_ext *x = this->ext;
880 if (x->chart_type == NULL)
883 /* Initialize chart. */
884 chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
885 if (ch->file_name == NULL)
888 /* Mention chart in output.
889 First advance current position. */
890 if (!this->page_open)
891 outp_open_page (this);
895 if (this->cp_y >= this->length)
897 outp_close_page (this);
898 outp_open_page (this);
902 /* Then write the text. */
903 text = xasprintf ("See %s for a chart.", ch->file_name);
905 t.justification = OUTP_LEFT;
906 t.string = ss_cstr (text);
911 ascii_text_draw (this, &t);
918 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
920 struct ascii_driver_ext *x = this->ext;
921 if (x->chart_type != NULL)
922 chart_finalise_separate (ch);
925 const struct outp_class ascii_class =
943 ascii_chart_initialise,