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 <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 (struct outp_driver *this, struct substring options)
142 struct ascii_driver_ext *x;
146 this->font_height = 1;
147 this->prop_em_width = 1;
148 this->fixed_width = 1;
149 for (i = 0; i < OUTP_L_COUNT; i++)
150 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
152 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
156 x->squeeze_blank_lines = false;
157 x->emphasis = EMPH_BOLD;
159 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
160 x->chart_type = pool_strdup (x->pool, "png");
161 x->auto_width = false;
162 x->auto_length = false;
165 x->bottom_margin = 2;
166 for (i = 0; i < LNS_COUNT; i++)
169 x->file_name = pool_strdup (x->pool, "pspp.list");
171 x->reported_error = false;
177 if (!outp_parse_options (options, handle_option, this))
180 if (!update_page_size (this, true))
183 for (i = 0; i < LNS_COUNT; i++)
184 if (x->box[i] == NULL)
187 s[0] = get_default_box_char (i);
189 x->box[i] = pool_strdup (x->pool, s);
195 pool_destroy (x->pool);
200 get_default_box_char (size_t idx)
202 /* Disassemble IDX into components. */
203 unsigned top = (idx >> LNS_TOP) & 3;
204 unsigned left = (idx >> LNS_LEFT) & 3;
205 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
206 unsigned right = (idx >> LNS_RIGHT) & 3;
208 /* Reassemble components into nibbles in the order TLBR.
209 This makes it easy to read the case labels. */
210 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
216 case 0x0100: case 0x0101: case 0x0001:
219 case 0x1000: case 0x1010: case 0x0010:
222 case 0x0300: case 0x0303: case 0x0003:
223 case 0x0200: case 0x0202: case 0x0002:
227 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
231 /* Re-calculates the page width and length based on settings,
232 margins, and, if "auto" is set, the size of the user's
233 terminal window or GUI output window. */
235 update_page_size (struct outp_driver *this, bool issue_error)
237 struct ascii_driver_ext *x = this->ext;
238 int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0);
241 this->width = settings_get_viewwidth ();
243 x->page_length = settings_get_viewlength ();
245 this->length = x->page_length - margins;
247 if (this->width < 59 || this->length < 15)
251 _("ascii: page excluding margins and headers "
252 "must be at least 59 characters wide by 15 lines long, but "
253 "as configured is only %d characters by %d lines"),
254 this->width, this->length);
255 if (this->width < 59)
257 if (this->length < 15)
260 x->page_length = this->length + margins;
269 ascii_close_driver (struct outp_driver *this)
271 struct ascii_driver_ext *x = this->ext;
274 pool_detach_file (x->pool, x->file);
275 pool_destroy (x->pool);
280 /* Generic option types. */
290 static const struct outp_option option_tab[] =
292 {"headers", boolean_arg, 0},
293 {"paginate", boolean_arg, 1},
294 {"squeeze", boolean_arg, 2},
295 {"append", boolean_arg, 3},
297 {"emphasis", emphasis_arg, 0},
299 {"length", page_size_arg, 0},
300 {"width", page_size_arg, 1},
302 {"top-margin", nonneg_int_arg, 0},
303 {"bottom-margin", nonneg_int_arg, 1},
304 {"tab-width", nonneg_int_arg, 2},
306 {"output-file", string_arg, 0},
307 {"chart-files", string_arg, 1},
308 {"chart-type", string_arg, 2},
309 {"init", string_arg, 3},
315 handle_option (struct outp_driver *this, const char *key,
316 const struct string *val)
318 struct ascii_driver_ext *x = this->ext;
322 value = ds_cstr (val);
323 if (!strncmp (key, "box[", 4))
326 int indx = strtol (&key[4], &tail, 4);
327 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
329 error (0, 0, _("ascii: bad index value for `box' key: syntax "
330 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
331 "expressed in base 4"),
335 if (x->box[indx] != NULL)
336 error (0, 0, _("ascii: multiple values for %s"), key);
337 x->box[indx] = pool_strdup (x->pool, value);
341 switch (outp_match_keyword (key, option_tab, &subcat))
344 error (0, 0, _("ascii: unknown parameter `%s'"), key);
351 if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
353 if (!(this->device & OUTP_DEV_SCREEN))
355 /* We only let `screen' devices have `auto'
356 length or width because output to such devices
357 is flushed before each new command. Resizing
358 a device in the middle of output seems like a
360 error (0, 0, _("ascii: only screen devices may have `auto' "
363 else if (subcat == 0)
364 x->auto_length = true;
366 x->auto_width = true;
371 arg = strtol (value, &tail, 0);
372 if (arg < 1 || errno == ERANGE || *tail)
374 error (0, 0, _("ascii: positive integer required as "
382 x->page_length = arg;
394 if (!strcmp (value, "bold"))
395 x->emphasis = EMPH_BOLD;
396 else if (!strcmp (value, "underline"))
397 x->emphasis = EMPH_UNDERLINE;
398 else if (!strcmp (value, "none"))
399 x->emphasis = EMPH_NONE;
402 _("ascii: `emphasis' value must be `bold', "
403 "`underline', or `none'"));
411 arg = strtol (value, &tail, 0);
412 if (arg < 0 || errno == ERANGE || *tail)
415 _("ascii: zero or positive integer required as `%s' value"),
425 x->bottom_margin = arg;
438 if (!strcmp (value, "on") || !strcmp (value, "true")
439 || !strcmp (value, "yes") || atoi (value))
441 else if (!strcmp (value, "off") || !strcmp (value, "false")
442 || !strcmp (value, "no") || !strcmp (value, "0"))
446 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
452 x->headers = setting;
455 x->paginate = setting;
458 x->squeeze_blank_lines = setting;
472 x->file_name = pool_strdup (x->pool, value);
475 if (ds_find_char (val, '#') != SIZE_MAX)
476 x->chart_file_name = pool_strdup (x->pool, value);
478 error (0, 0, _("`chart-files' value must contain `#'"));
481 if (value[0] != '\0')
482 x->chart_type = pool_strdup (x->pool, value);
484 x->chart_type = NULL;
487 x->init = pool_strdup (x->pool, value);
499 ascii_open_page (struct outp_driver *this)
501 struct ascii_driver_ext *x = this->ext;
504 update_page_size (this, false);
508 x->file = fn_open (x->file_name, x->append ? "a" : "w");
511 pool_attach_file (x->pool, x->file);
513 fputs (x->init, x->file);
517 /* Report the error to the user and complete
518 initialization. If we do not finish initialization,
519 then calls to other driver functions will segfault
520 later. It would be better to simply drop the driver
521 entirely, but we do not have a convenient mechanism
523 if (!x->reported_error)
524 error (0, errno, _("ascii: opening output file \"%s\""),
526 x->reported_error = true;
532 if (this->length > x->line_cap)
534 x->lines = pool_nrealloc (x->pool,
535 x->lines, this->length, sizeof *x->lines);
536 for (i = x->line_cap; i < this->length; i++)
538 struct line *line = &x->lines[i];
542 x->line_cap = this->length;
545 for (i = 0; i < this->length; i++)
546 x->lines[i].char_cnt = 0;
549 /* Ensures that at least the first LENGTH characters of line Y in
550 THIS driver identified X have been cleared out. */
552 expand_line (struct outp_driver *this, int y, int length)
554 struct ascii_driver_ext *ext = this->ext;
555 struct line *line = &ext->lines[y];
556 if (line->char_cnt < length)
559 if (line->char_cap < length)
561 line->char_cap = MIN (length * 2, this->width);
562 line->chars = pool_nrealloc (ext->pool,
564 line->char_cap, sizeof *line->chars);
566 for (x = line->char_cnt; x < length; x++)
567 line->chars[x] = ' ';
568 line->char_cnt = length;
573 ascii_line (struct outp_driver *this,
574 int x0, int y0, int x1, int y1,
575 enum outp_line_style top, enum outp_line_style left,
576 enum outp_line_style bottom, enum outp_line_style right)
578 struct ascii_driver_ext *ext = this->ext;
580 unsigned short value;
582 assert (this->page_open);
584 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
586 #if !SUPPRESS_WARNINGS
587 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
588 x0, y0, x1, y1, this->width, this->length);
594 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
595 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
596 for (y = y0; y < y1; y++)
600 expand_line (this, y, x1);
601 for (x = x0; x < x1; x++)
602 ext->lines[y].chars[x] = value;
607 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
609 extern struct som_table_class tab_table_class;
611 assert (s->class == &tab_table_class);
612 assert (s->type == SOM_CHART);
616 text_draw (struct outp_driver *this,
619 enum outp_justification justification, int width,
620 const char *string, size_t length)
622 struct ascii_driver_ext *ext = this->ext;
623 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
627 switch (justification)
632 x += (width - length + 1) / 2;
641 if (y >= this->length || x >= this->width)
644 if (x + length > this->width)
645 length = this->width - x;
647 line_len = x + length;
649 expand_line (this, y, line_len);
651 ext->lines[y].chars[x++] = *string++ | attr;
654 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
655 to the maximum width of a line and *HEIGHT to the number of
656 lines, if those arguments are non-null. Actually draws the
657 text if DRAW is true. */
659 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
660 int *width, int *height)
665 const char *cp = ss_data (text->string);
668 height_left = text->v;
670 while (height_left > 0)
676 /* Initially the line is up to text->h characters long. */
677 chars_left = ss_end (text->string) - cp;
680 line_len = MIN (chars_left, text->h);
682 /* A new-line terminates the line prematurely. */
683 end = memchr (cp, '\n', line_len);
687 /* Don't cut off words if it can be avoided. */
688 if (cp + line_len < ss_end (text->string))
690 size_t space_len = line_len;
691 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
694 line_len = space_len;
701 text->x, text->y + (text->v - height_left),
702 text->justification, text->h,
707 if (line_len > max_width)
708 max_width = line_len;
712 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
719 *height = text->v - height_left;
723 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
724 int *width, int *height)
726 delineate (this, t, false, width, height);
730 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
732 assert (this->page_open);
733 delineate (this, t, true, NULL, NULL);
736 /* ascii_close_page () and support routines. */
738 /* Writes the LENGTH characters in S to OUT. */
740 output_line (struct outp_driver *this, const struct line *line,
743 struct ascii_driver_ext *ext = this->ext;
744 const unsigned short *s = line->chars;
747 for (length = line->char_cnt; length-- > 0; s++)
749 ds_put_cstr (out, ext->box[*s & 0xff]);
752 if (*s & ATTR_EMPHASIS)
754 if (ext->emphasis == EMPH_BOLD)
756 ds_put_char (out, *s);
757 ds_put_char (out, '\b');
759 else if (ext->emphasis == EMPH_UNDERLINE)
760 ds_put_cstr (out, "_\b");
762 ds_put_char (out, *s);
767 append_lr_justified (struct string *out, int width,
768 const char *left, const char *right)
770 ds_put_char_multiple (out, ' ', width);
773 size_t length = MIN (strlen (left), width);
774 memcpy (ds_end (out) - width, left, length);
778 size_t length = MIN (strlen (right), width);
779 memcpy (ds_end (out) - length, right, length);
781 ds_put_char (out, '\n');
785 dump_output (struct outp_driver *this, struct string *out)
787 struct ascii_driver_ext *x = this->ext;
788 fwrite (ds_data (out), ds_length (out), 1, x->file);
793 ascii_close_page (struct outp_driver *this)
795 struct ascii_driver_ext *x = this->ext;
802 ds_init_empty (&out);
804 ds_put_char_multiple (&out, '\n', x->top_margin);
809 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
810 r2 = xasprintf ("%s - %s" , version, host_system);
812 append_lr_justified (&out, this->width, outp_title, r1);
813 append_lr_justified (&out, this->width, outp_subtitle, r2);
814 ds_put_char (&out, '\n');
819 dump_output (this, &out);
821 for (line_num = 0; line_num < this->length; line_num++)
824 /* Squeeze multiple blank lines into a single blank line if
826 if (x->squeeze_blank_lines)
828 if (line_num >= x->line_cap)
831 && x->lines[line_num].char_cnt == 0
832 && x->lines[line_num - 1].char_cnt == 0)
836 if (line_num < x->line_cap)
837 output_line (this, &x->lines[line_num], &out);
838 ds_put_char (&out, '\n');
839 dump_output (this, &out);
842 ds_put_char_multiple (&out, '\n', x->bottom_margin);
844 ds_put_char (&out, '\f');
846 dump_output (this, &out);
850 /* Flushes all output to the user and lets the user deal with it.
851 This is applied only to output drivers that are designated as
852 "screen" drivers that the user is interacting with in real
855 ascii_flush (struct outp_driver *this)
857 struct ascii_driver_ext *x = this->ext;
860 if (fn_close (x->file_name, x->file) != 0)
861 error (0, errno, _("ascii: closing output file \"%s\""),
863 pool_detach_file (x->pool, x->file);
869 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
871 struct ascii_driver_ext *x = this->ext;
875 if (x->chart_type == NULL)
878 /* Initialize chart. */
879 chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
880 if (ch->file_name == NULL)
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.", ch->file_name);
900 t.justification = OUTP_LEFT;
901 t.string = ss_cstr (text);
906 ascii_text_draw (this, &t);
913 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
915 struct ascii_driver_ext *x = this->ext;
916 if (x->chart_type != NULL)
917 chart_finalise_separate (ch);
920 const struct outp_class ascii_class =
938 ascii_chart_initialise,