1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3 Written by Ben Pfaff <blp@gnu.org>.
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 #include <data/file-name.h>
28 #include <libpspp/alloc.h>
29 #include <libpspp/assertion.h>
30 #include <libpspp/compiler.h>
31 #include <libpspp/pool.h>
32 #include <libpspp/start-date.h>
33 #include <libpspp/version.h>
41 #define _(msgid) gettext (msgid)
43 /* ASCII driver options: (defaults listed first)
45 output-file="pspp.list"
46 paginate=on|off Formfeeds are desired?
47 tab-width=8 Width of a tab; 0 to not use tabs.
49 headers=on|off Put headers at top of page?
50 emphasis=bold|underline|none Style to use for emphasis.
53 squeeze=off|on Squeeze multiple newlines into exactly one.
58 box[x]="strng" Sets box character X (X in base 4: 0-3333).
61 /* Disable messages by failed range checks. */
62 /*#define SUPPRESS_WARNINGS 1 */
64 /* Line styles bit shifts. */
75 /* Character attributes. */
76 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
77 #define ATTR_BOX 0x200 /* Line drawing character. */
82 unsigned short *chars; /* Characters and attributes. */
83 int char_cnt; /* Length. */
84 int char_cap; /* Allocated bytes. */
87 /* How to emphasize text. */
90 EMPH_BOLD, /* Overstrike for bold. */
91 EMPH_UNDERLINE, /* Overstrike for underlining. */
92 EMPH_NONE /* No emphasis. */
95 /* ASCII output driver extension record. */
96 struct ascii_driver_ext
100 /* User parameters. */
101 bool headers; /* Print headers at top of page? */
102 bool paginate; /* Insert formfeeds? */
103 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
104 enum emphasis_style emphasis; /* How to emphasize text. */
105 int tab_width; /* Width of a tab; 0 not to use tabs. */
107 int page_length; /* Page length before subtracting margins. */
108 int top_margin; /* Top margin in lines. */
109 int bottom_margin; /* Bottom margin in lines. */
111 char *box[LNS_COUNT]; /* Line & box drawing characters. */
113 /* Internal state. */
114 char *file_name; /* Output file name. */
115 FILE *file; /* Output file. */
116 int page_number; /* Current page number. */
117 struct line *lines; /* Page content. */
118 int line_cap; /* Number of lines allocated. */
121 static int get_default_box_char (size_t idx);
122 static bool handle_option (struct outp_driver *this, const char *key,
123 const struct string *val);
126 ascii_open_driver (struct outp_driver *this, struct substring options)
128 struct ascii_driver_ext *x;
132 this->font_height = 1;
133 this->prop_em_width = 1;
134 this->fixed_width = 1;
135 for (i = 0; i < OUTP_L_COUNT; i++)
136 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
138 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
141 x->squeeze_blank_lines = false;
142 x->emphasis = EMPH_BOLD;
146 x->bottom_margin = 2;
147 for (i = 0; i < LNS_COUNT; i++)
149 x->file_name = pool_strdup (x->pool, "pspp.list");
155 if (!outp_parse_options (options, handle_option, this))
158 x->file = pool_fopen (x->pool, x->file_name, "w");
161 error (0, errno, _("ascii: opening output file \"%s\""), x->file_name);
165 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
169 if (this->width < 59 || this->length < 15)
172 _("ascii: page excluding margins and headers "
173 "must be at least 59 characters wide by 15 lines long, but as "
174 "configured is only %d characters by %d lines"),
175 this->width, this->length);
179 for (i = 0; i < LNS_COUNT; i++)
180 if (x->box[i] == NULL)
183 s[0] = get_default_box_char (i);
185 x->box[i] = pool_strdup (x->pool, s);
191 pool_destroy (x->pool);
196 get_default_box_char (size_t idx)
198 /* Disassemble IDX into components. */
199 unsigned top = (idx >> LNS_TOP) & 3;
200 unsigned left = (idx >> LNS_LEFT) & 3;
201 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
202 unsigned right = (idx >> LNS_RIGHT) & 3;
204 /* Reassemble components into nibbles in the order TLBR.
205 This makes it easy to read the case labels. */
206 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
212 case 0x0100: case 0x0101: case 0x0001:
215 case 0x1000: case 0x1010: case 0x0010:
218 case 0x0300: case 0x0303: case 0x0003:
219 case 0x0200: case 0x0202: case 0x0002:
223 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
228 ascii_close_driver (struct outp_driver *this)
230 struct ascii_driver_ext *x = this->ext;
232 if (fn_close (x->file_name, x->file) != 0)
233 error (0, errno, _("ascii: closing output file \"%s\""), x->file_name);
234 pool_detach_file (x->pool, x->file);
235 pool_destroy (x->pool);
240 /* Generic option types. */
250 static const struct outp_option option_tab[] =
252 {"headers", boolean_arg, 0},
253 {"paginate", boolean_arg, 1},
254 {"squeeze", boolean_arg, 2},
256 {"emphasis", string_arg, 3},
258 {"output-file", output_file_arg, 0},
260 {"length", pos_int_arg, 0},
261 {"width", pos_int_arg, 1},
263 {"top-margin", nonneg_int_arg, 0},
264 {"bottom-margin", nonneg_int_arg, 1},
265 {"tab-width", nonneg_int_arg, 2},
271 handle_option (struct outp_driver *this, const char *key,
272 const struct string *val)
274 struct ascii_driver_ext *x = this->ext;
278 value = ds_cstr (val);
279 if (!strncmp (key, "box[", 4))
282 int indx = strtol (&key[4], &tail, 4);
283 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
285 error (0, 0, _("ascii: bad index value for `box' key: syntax "
286 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
287 "expressed in base 4"),
291 if (x->box[indx] != NULL)
292 error (0, 0, _("ascii: multiple values for %s"), key);
293 x->box[indx] = pool_strdup (x->pool, value);
297 switch (outp_match_keyword (key, option_tab, &subcat))
300 error (0, 0, _("ascii: unknown parameter `%s'"), key);
302 case output_file_arg:
303 x->file_name = pool_strdup (x->pool, value);
311 arg = strtol (value, &tail, 0);
312 if (arg < 1 || errno == ERANGE || *tail)
314 error (0, 0, _("ascii: positive integer required as `%s' value"),
321 x->page_length = arg;
332 if (!strcmp (value, "bold"))
333 x->emphasis = EMPH_BOLD;
334 else if (!strcmp (value, "underline"))
335 x->emphasis = EMPH_UNDERLINE;
336 else if (!strcmp (value, "none"))
337 x->emphasis = EMPH_NONE;
340 _("ascii: `emphasis' value must be `bold', "
341 "`underline', or `none'"));
349 arg = strtol (value, &tail, 0);
350 if (arg < 0 || errno == ERANGE || *tail)
353 _("ascii: zero or positive integer required as `%s' value"),
363 x->bottom_margin = arg;
376 if (!strcmp (value, "on") || !strcmp (value, "true")
377 || !strcmp (value, "yes") || atoi (value))
379 else if (!strcmp (value, "off") || !strcmp (value, "false")
380 || !strcmp (value, "no") || !strcmp (value, "0"))
384 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
390 x->headers = setting;
393 x->paginate = setting;
396 x->squeeze_blank_lines = setting;
411 ascii_open_page (struct outp_driver *this)
413 struct ascii_driver_ext *x = this->ext;
418 if (this->length > x->line_cap)
420 x->lines = pool_nrealloc (x->pool,
421 x->lines, this->length, sizeof *x->lines);
422 for (i = x->line_cap; i < this->length; i++)
424 struct line *line = &x->lines[i];
428 x->line_cap = this->length;
431 for (i = 0; i < this->length; i++)
432 x->lines[i].char_cnt = 0;
435 /* Ensures that at least the first LENGTH characters of line Y in
436 THIS driver identified X have been cleared out. */
438 expand_line (struct outp_driver *this, int y, int length)
440 struct ascii_driver_ext *ext = this->ext;
441 struct line *line = &ext->lines[y];
442 if (line->char_cnt < length)
445 if (line->char_cap < length)
447 line->char_cap = MIN (length * 2, this->width);
448 line->chars = pool_nrealloc (ext->pool,
450 line->char_cap, sizeof *line->chars);
452 for (x = line->char_cnt; x < length; x++)
453 line->chars[x] = ' ';
454 line->char_cnt = length;
459 ascii_line (struct outp_driver *this,
460 int x0, int y0, int x1, int y1,
461 enum outp_line_style top, enum outp_line_style left,
462 enum outp_line_style bottom, enum outp_line_style right)
464 struct ascii_driver_ext *ext = this->ext;
466 unsigned short value;
468 assert (this->page_open);
470 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
472 #if !SUPPRESS_WARNINGS
473 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
474 x0, y0, x1, y1, this->width, this->length);
480 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
481 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
482 for (y = y0; y < y1; y++)
486 expand_line (this, y, x1);
487 for (x = x0; x < x1; x++)
488 ext->lines[y].chars[x] = value;
493 text_draw (struct outp_driver *this,
496 enum outp_justification justification, int width,
497 const char *string, size_t length)
499 struct ascii_driver_ext *ext = this->ext;
500 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
504 switch (justification)
509 x += (width - length + 1) / 2;
518 if (y >= this->length || x >= this->width)
521 if (x + length > this->width)
522 length = this->width - x;
524 line_len = x + length;
526 expand_line (this, y, line_len);
528 ext->lines[y].chars[x++] = *string++ | attr;
531 /* Divides the text T->S into lines of width T->H. Sets T->V to the
532 number of lines necessary. Actually draws the text if DRAW is
535 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
536 int *width, int *height)
541 const char *cp = ss_data (text->string);
544 height_left = text->v;
546 while (height_left > 0)
552 /* Initially the line is up to text->h characters long. */
553 chars_left = ss_end (text->string) - cp;
556 line_len = MIN (chars_left, text->h);
558 /* A new-line terminates the line prematurely. */
559 end = memchr (cp, '\n', line_len);
563 /* Don't cut off words if it can be avoided. */
564 if (cp + line_len < ss_end (text->string))
566 size_t space_len = line_len;
567 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
570 line_len = space_len;
577 text->x, text->y + (text->v - height_left),
578 text->justification, text->h,
583 if (line_len > max_width)
584 max_width = line_len;
588 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
595 *height = text->v - height_left;
599 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
600 int *width, int *height)
602 delineate (this, t, false, width, height);
606 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
608 assert (this->page_open);
609 delineate (this, t, true, NULL, NULL);
613 /* ascii_close_page () and support routines. */
615 /* Writes the LENGTH characters in S to OUT. */
617 output_line (struct outp_driver *this, const struct line *line,
620 struct ascii_driver_ext *ext = this->ext;
621 const unsigned short *s = line->chars;
624 for (length = line->char_cnt; length-- > 0; s++)
626 ds_put_cstr (out, ext->box[*s & 0xff]);
629 if (*s & ATTR_EMPHASIS)
631 if (ext->emphasis == EMPH_BOLD)
633 ds_put_char (out, *s);
634 ds_put_char (out, '\b');
636 else if (ext->emphasis == EMPH_UNDERLINE)
637 ds_put_cstr (out, "_\b");
639 ds_put_char (out, *s);
644 append_lr_justified (struct string *out, int width,
645 const char *left, const char *right)
647 ds_put_char_multiple (out, ' ', width);
650 size_t length = MIN (strlen (left), width);
651 memcpy (ds_end (out) - width, left, length);
655 size_t length = MIN (strlen (right), width);
656 memcpy (ds_end (out) - length, right, length);
658 ds_put_char (out, '\n');
662 dump_output (struct outp_driver *this, struct string *out)
664 struct ascii_driver_ext *x = this->ext;
665 fwrite (ds_data (out), ds_length (out), 1, x->file);
670 ascii_close_page (struct outp_driver *this)
672 struct ascii_driver_ext *x = this->ext;
676 ds_init_empty (&out);
678 ds_put_char_multiple (&out, '\n', x->top_margin);
683 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
684 r2 = xasprintf ("%s - %s" , version, host_system);
686 append_lr_justified (&out, this->width, outp_title, r1);
687 append_lr_justified (&out, this->width, outp_subtitle, r2);
688 ds_put_char (&out, '\n');
693 dump_output (this, &out);
695 for (line_num = 0; line_num < this->length; line_num++)
698 /* Squeeze multiple blank lines into a single blank line if
700 if (x->squeeze_blank_lines)
702 if (line_num >= x->line_cap)
705 && x->lines[line_num].char_cnt == 0
706 && x->lines[line_num - 1].char_cnt == 0)
710 if (line_num < x->line_cap)
711 output_line (this, &x->lines[line_num], &out);
712 ds_put_char (&out, '\n');
713 dump_output (this, &out);
716 ds_put_char_multiple (&out, '\n', x->bottom_margin);
718 ds_put_char (&out, '\f');
720 dump_output (this, &out);
725 ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch)
727 error (0, 0, _("ascii: charts are unsupported by this driver"));
732 ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED)
737 const struct outp_class ascii_class =
754 ascii_chart_initialise,