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 (void *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 (this->name, 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 (void *this_, const char *key,
321 const struct string *val)
323 struct outp_driver *this = this_;
324 struct ascii_driver_ext *x = this->ext;
328 value = ds_cstr (val);
329 if (!strncmp (key, "box[", 4))
332 int indx = strtol (&key[4], &tail, 4);
333 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
335 error (0, 0, _("ascii: bad index value for `box' key: syntax "
336 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
337 "expressed in base 4"),
341 if (x->box[indx] != NULL)
342 error (0, 0, _("ascii: multiple values for %s"), key);
343 x->box[indx] = pool_strdup (x->pool, value);
347 switch (outp_match_keyword (key, option_tab, &subcat))
350 error (0, 0, _("ascii: unknown parameter `%s'"), key);
357 if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
359 if (!(this->device & OUTP_DEV_SCREEN))
361 /* We only let `screen' devices have `auto'
362 length or width because output to such devices
363 is flushed before each new command. Resizing
364 a device in the middle of output seems like a
366 error (0, 0, _("ascii: only screen devices may have `auto' "
369 else if (subcat == 0)
370 x->auto_length = true;
372 x->auto_width = true;
377 arg = strtol (value, &tail, 0);
378 if (arg < 1 || errno == ERANGE || *tail)
380 error (0, 0, _("ascii: positive integer required as "
388 x->page_length = arg;
400 if (!strcmp (value, "bold"))
401 x->emphasis = EMPH_BOLD;
402 else if (!strcmp (value, "underline"))
403 x->emphasis = EMPH_UNDERLINE;
404 else if (!strcmp (value, "none"))
405 x->emphasis = EMPH_NONE;
408 _("ascii: `emphasis' value must be `bold', "
409 "`underline', or `none'"));
417 arg = strtol (value, &tail, 0);
418 if (arg < 0 || errno == ERANGE || *tail)
421 _("ascii: zero or positive integer required as `%s' value"),
431 x->bottom_margin = arg;
444 if (!strcmp (value, "on") || !strcmp (value, "true")
445 || !strcmp (value, "yes") || atoi (value))
447 else if (!strcmp (value, "off") || !strcmp (value, "false")
448 || !strcmp (value, "no") || !strcmp (value, "0"))
452 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
458 x->headers = setting;
461 x->paginate = setting;
464 x->squeeze_blank_lines = setting;
478 x->file_name = pool_strdup (x->pool, value);
481 if (ds_find_char (val, '#') != SIZE_MAX)
482 x->chart_file_name = pool_strdup (x->pool, value);
484 error (0, 0, _("`chart-files' value must contain `#'"));
487 if (value[0] != '\0')
488 x->chart_type = pool_strdup (x->pool, value);
490 x->chart_type = NULL;
493 x->init = pool_strdup (x->pool, value);
505 ascii_open_page (struct outp_driver *this)
507 struct ascii_driver_ext *x = this->ext;
510 update_page_size (this, false);
514 x->file = fn_open (x->file_name, x->append ? "a" : "w");
517 pool_attach_file (x->pool, x->file);
519 fputs (x->init, x->file);
523 /* Report the error to the user and complete
524 initialization. If we do not finish initialization,
525 then calls to other driver functions will segfault
526 later. It would be better to simply drop the driver
527 entirely, but we do not have a convenient mechanism
529 if (!x->reported_error)
530 error (0, errno, _("ascii: opening output file \"%s\""),
532 x->reported_error = true;
538 if (this->length > x->line_cap)
540 x->lines = pool_nrealloc (x->pool,
541 x->lines, this->length, sizeof *x->lines);
542 for (i = x->line_cap; i < this->length; i++)
544 struct line *line = &x->lines[i];
548 x->line_cap = this->length;
551 for (i = 0; i < this->length; i++)
552 x->lines[i].char_cnt = 0;
555 /* Ensures that at least the first LENGTH characters of line Y in
556 THIS driver identified X have been cleared out. */
558 expand_line (struct outp_driver *this, int y, int length)
560 struct ascii_driver_ext *ext = this->ext;
561 struct line *line = &ext->lines[y];
562 if (line->char_cnt < length)
565 if (line->char_cap < length)
567 line->char_cap = MIN (length * 2, this->width);
568 line->chars = pool_nrealloc (ext->pool,
570 line->char_cap, sizeof *line->chars);
572 for (x = line->char_cnt; x < length; x++)
573 line->chars[x] = ' ';
574 line->char_cnt = length;
579 ascii_line (struct outp_driver *this,
580 int x0, int y0, int x1, int y1,
581 enum outp_line_style top, enum outp_line_style left,
582 enum outp_line_style bottom, enum outp_line_style right)
584 struct ascii_driver_ext *ext = this->ext;
586 unsigned short value;
588 assert (this->page_open);
590 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
592 #if !SUPPRESS_WARNINGS
593 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
594 x0, y0, x1, y1, this->width, this->length);
600 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
601 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
602 for (y = y0; y < y1; y++)
606 expand_line (this, y, x1);
607 for (x = x0; x < x1; x++)
608 ext->lines[y].chars[x] = value;
613 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
615 extern struct som_table_class tab_table_class;
617 assert (s->class == &tab_table_class);
618 assert (s->type == SOM_CHART);
622 text_draw (struct outp_driver *this,
625 enum outp_justification justification, int width,
626 const char *string, size_t length)
628 struct ascii_driver_ext *ext = this->ext;
629 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
633 switch (justification)
638 x += (width - length + 1) / 2;
647 if (y >= this->length || x >= this->width)
650 if (x + length > this->width)
651 length = this->width - x;
653 line_len = x + length;
655 expand_line (this, y, line_len);
657 ext->lines[y].chars[x++] = *string++ | attr;
660 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
661 to the maximum width of a line and *HEIGHT to the number of
662 lines, if those arguments are non-null. Actually draws the
663 text if DRAW is true. */
665 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
666 int *width, int *height)
671 const char *cp = ss_data (text->string);
674 height_left = text->v;
676 while (height_left > 0)
682 /* Initially the line is up to text->h characters long. */
683 chars_left = ss_end (text->string) - cp;
686 line_len = MIN (chars_left, text->h);
688 /* A new-line terminates the line prematurely. */
689 end = memchr (cp, '\n', line_len);
693 /* Don't cut off words if it can be avoided. */
694 if (cp + line_len < ss_end (text->string))
696 size_t space_len = line_len;
697 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
700 line_len = space_len;
707 text->x, text->y + (text->v - height_left),
708 text->justification, text->h,
713 if (line_len > max_width)
714 max_width = line_len;
718 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
725 *height = text->v - height_left;
729 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
730 int *width, int *height)
732 delineate (this, t, false, width, height);
736 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
738 assert (this->page_open);
739 delineate (this, t, true, NULL, NULL);
742 /* ascii_close_page () and support routines. */
744 /* Writes the LENGTH characters in S to OUT. */
746 output_line (struct outp_driver *this, const struct line *line,
749 struct ascii_driver_ext *ext = this->ext;
750 const unsigned short *s = line->chars;
753 for (length = line->char_cnt; length-- > 0; s++)
755 ds_put_cstr (out, ext->box[*s & 0xff]);
758 if (*s & ATTR_EMPHASIS)
760 if (ext->emphasis == EMPH_BOLD)
762 ds_put_char (out, *s);
763 ds_put_char (out, '\b');
765 else if (ext->emphasis == EMPH_UNDERLINE)
766 ds_put_cstr (out, "_\b");
768 ds_put_char (out, *s);
773 append_lr_justified (struct string *out, int width,
774 const char *left, const char *right)
776 ds_put_char_multiple (out, ' ', width);
779 size_t length = MIN (strlen (left), width);
780 memcpy (ds_end (out) - width, left, length);
784 size_t length = MIN (strlen (right), width);
785 memcpy (ds_end (out) - length, right, length);
787 ds_put_char (out, '\n');
791 dump_output (struct outp_driver *this, struct string *out)
793 struct ascii_driver_ext *x = this->ext;
794 fwrite (ds_data (out), ds_length (out), 1, x->file);
799 ascii_close_page (struct outp_driver *this)
801 struct ascii_driver_ext *x = this->ext;
808 ds_init_empty (&out);
810 ds_put_char_multiple (&out, '\n', x->top_margin);
815 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
816 r2 = xasprintf ("%s - %s" , version, host_system);
818 append_lr_justified (&out, this->width, outp_title, r1);
819 append_lr_justified (&out, this->width, outp_subtitle, r2);
820 ds_put_char (&out, '\n');
825 dump_output (this, &out);
827 for (line_num = 0; line_num < this->length; line_num++)
830 /* Squeeze multiple blank lines into a single blank line if
832 if (x->squeeze_blank_lines)
834 if (line_num >= x->line_cap)
837 && x->lines[line_num].char_cnt == 0
838 && x->lines[line_num - 1].char_cnt == 0)
842 if (line_num < x->line_cap)
843 output_line (this, &x->lines[line_num], &out);
844 ds_put_char (&out, '\n');
845 dump_output (this, &out);
848 ds_put_char_multiple (&out, '\n', x->bottom_margin);
850 ds_put_char (&out, '\f');
852 dump_output (this, &out);
856 /* Flushes all output to the user and lets the user deal with it.
857 This is applied only to output drivers that are designated as
858 "screen" drivers that the user is interacting with in real
861 ascii_flush (struct outp_driver *this)
863 struct ascii_driver_ext *x = this->ext;
866 if (fn_close (x->file_name, x->file) != 0)
867 error (0, errno, _("ascii: closing output file \"%s\""),
869 pool_detach_file (x->pool, x->file);
875 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
877 struct ascii_driver_ext *x = this->ext;
881 if (x->chart_type == NULL)
884 /* Initialize chart. */
885 chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
886 if (ch->file_name == NULL)
889 /* Mention chart in output.
890 First advance current position. */
891 if (!this->page_open)
892 outp_open_page (this);
896 if (this->cp_y >= this->length)
898 outp_close_page (this);
899 outp_open_page (this);
903 /* Then write the text. */
904 text = xasprintf ("See %s for a chart.", ch->file_name);
906 t.justification = OUTP_LEFT;
907 t.string = ss_cstr (text);
912 ascii_text_draw (this, &t);
919 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
921 struct ascii_driver_ext *x = this->ext;
922 if (x->chart_type != NULL)
923 chart_finalise_separate (ch);
926 const struct outp_class ascii_class =
944 ascii_chart_initialise,