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/compiler.h>
30 #include <libpspp/pool.h>
31 #include <libpspp/start-date.h>
32 #include <libpspp/version.h>
40 #define _(msgid) gettext (msgid)
42 /* ASCII driver options: (defaults listed first)
44 output-file="pspp.list"
45 paginate=on|off Formfeeds are desired?
46 tab-width=8 Width of a tab; 0 to not use tabs.
48 headers=on|off Put headers at top of page?
49 emphasis=bold|underline|none Style to use for emphasis.
52 squeeze=off|on Squeeze multiple newlines into exactly one.
57 box[x]="strng" Sets box character X (X in base 4: 0-3333).
60 /* Disable messages by failed range checks. */
61 /*#define SUPPRESS_WARNINGS 1 */
63 /* Line styles bit shifts. */
74 /* Character attributes. */
75 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
76 #define ATTR_BOX 0x200 /* Line drawing character. */
81 unsigned short *chars; /* Characters and attributes. */
82 int char_cnt; /* Length. */
83 int char_cap; /* Allocated bytes. */
86 /* How to emphasize text. */
89 EMPH_BOLD, /* Overstrike for bold. */
90 EMPH_UNDERLINE, /* Overstrike for underlining. */
91 EMPH_NONE /* No emphasis. */
94 /* ASCII output driver extension record. */
95 struct ascii_driver_ext
99 /* User parameters. */
100 bool headers; /* Print headers at top of page? */
101 bool paginate; /* Insert formfeeds? */
102 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
103 enum emphasis_style emphasis; /* How to emphasize text. */
104 int tab_width; /* Width of a tab; 0 not to use tabs. */
106 int page_length; /* Page length before subtracting margins. */
107 int top_margin; /* Top margin in lines. */
108 int bottom_margin; /* Bottom margin in lines. */
110 char *box[LNS_COUNT]; /* Line & box drawing characters. */
112 /* Internal state. */
113 char *file_name; /* Output file name. */
114 FILE *file; /* Output file. */
115 int page_number; /* Current page number. */
116 struct line *lines; /* Page content. */
117 int line_cap; /* Number of lines allocated. */
120 static int get_default_box_char (size_t idx);
121 static bool handle_option (struct outp_driver *this, const char *key,
122 const struct string *val);
125 ascii_open_driver (struct outp_driver *this, struct substring options)
127 struct ascii_driver_ext *x;
131 this->font_height = 1;
132 this->prop_em_width = 1;
133 this->fixed_width = 1;
134 for (i = 0; i < OUTP_L_COUNT; i++)
135 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
137 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
140 x->squeeze_blank_lines = false;
141 x->emphasis = EMPH_BOLD;
145 x->bottom_margin = 2;
146 for (i = 0; i < LNS_COUNT; i++)
148 x->file_name = pool_strdup (x->pool, "pspp.list");
154 if (!outp_parse_options (options, handle_option, this))
157 x->file = pool_fopen (x->pool, x->file_name, "w");
160 error (0, errno, _("ascii: opening output file \"%s\""), x->file_name);
164 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
168 if (this->width < 59 || this->length < 15)
171 _("ascii: page excluding margins and headers "
172 "must be at least 59 characters wide by 15 lines long, but as "
173 "configured is only %d characters by %d lines"),
174 this->width, this->length);
178 for (i = 0; i < LNS_COUNT; i++)
179 if (x->box[i] == NULL)
182 s[0] = get_default_box_char (i);
184 x->box[i] = pool_strdup (x->pool, s);
190 pool_destroy (x->pool);
195 get_default_box_char (size_t idx)
197 /* Disassemble IDX into components. */
198 unsigned top = (idx >> LNS_TOP) & 3;
199 unsigned left = (idx >> LNS_LEFT) & 3;
200 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
201 unsigned right = (idx >> LNS_RIGHT) & 3;
203 /* Reassemble components into nibbles in the order TLBR.
204 This makes it easy to read the case labels. */
205 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
211 case 0x0100: case 0x0101: case 0x0001:
214 case 0x1000: case 0x1010: case 0x0010:
217 case 0x0300: case 0x0303: case 0x0003:
218 case 0x0200: case 0x0202: case 0x0002:
222 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
227 ascii_close_driver (struct outp_driver *this)
229 struct ascii_driver_ext *x = this->ext;
231 if (fn_close (x->file_name, x->file) != 0)
232 error (0, errno, _("ascii: closing output file \"%s\""), x->file_name);
233 pool_detach_file (x->pool, x->file);
234 pool_destroy (x->pool);
239 /* Generic option types. */
249 static struct outp_option option_tab[] =
251 {"headers", boolean_arg, 0},
252 {"paginate", boolean_arg, 1},
253 {"squeeze", boolean_arg, 2},
255 {"emphasis", string_arg, 3},
257 {"output-file", output_file_arg, 0},
259 {"length", pos_int_arg, 0},
260 {"width", pos_int_arg, 1},
262 {"top-margin", nonneg_int_arg, 0},
263 {"bottom-margin", nonneg_int_arg, 1},
264 {"tab-width", nonneg_int_arg, 2},
270 handle_option (struct outp_driver *this, const char *key,
271 const struct string *val)
273 struct ascii_driver_ext *x = this->ext;
277 value = ds_cstr (val);
278 if (!strncmp (key, "box[", 4))
281 int indx = strtol (&key[4], &tail, 4);
282 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
284 error (0, 0, _("ascii: bad index value for `box' key: syntax "
285 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
286 "expressed in base 4"),
290 if (x->box[indx] != NULL)
291 error (0, 0, _("ascii: multiple values for %s"), key);
292 x->box[indx] = pool_strdup (x->pool, value);
296 switch (outp_match_keyword (key, option_tab, &subcat))
299 error (0, 0, _("ascii: unknown parameter `%s'"), key);
301 case output_file_arg:
302 x->file_name = pool_strdup (x->pool, value);
310 arg = strtol (value, &tail, 0);
311 if (arg < 1 || errno == ERANGE || *tail)
313 error (0, 0, _("ascii: positive integer required as `%s' value"),
320 x->page_length = arg;
331 if (!strcmp (value, "bold"))
332 x->emphasis = EMPH_BOLD;
333 else if (!strcmp (value, "underline"))
334 x->emphasis = EMPH_UNDERLINE;
335 else if (!strcmp (value, "none"))
336 x->emphasis = EMPH_NONE;
339 _("ascii: `emphasis' value must be `bold', "
340 "`underline', or `none'"));
348 arg = strtol (value, &tail, 0);
349 if (arg < 0 || errno == ERANGE || *tail)
352 _("ascii: zero or positive integer required as `%s' value"),
362 x->bottom_margin = arg;
375 if (!strcmp (value, "on") || !strcmp (value, "true")
376 || !strcmp (value, "yes") || atoi (value))
378 else if (!strcmp (value, "off") || !strcmp (value, "false")
379 || !strcmp (value, "no") || !strcmp (value, "0"))
383 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
389 x->headers = setting;
392 x->paginate = setting;
395 x->squeeze_blank_lines = setting;
410 ascii_open_page (struct outp_driver *this)
412 struct ascii_driver_ext *x = this->ext;
417 if (this->length > x->line_cap)
419 x->lines = pool_nrealloc (x->pool,
420 x->lines, this->length, sizeof *x->lines);
421 for (i = x->line_cap; i < this->length; i++)
423 struct line *line = &x->lines[i];
427 x->line_cap = this->length;
430 for (i = 0; i < this->length; i++)
431 x->lines[i].char_cnt = 0;
434 /* Ensures that at least the first LENGTH characters of line Y in
435 THIS driver identified X have been cleared out. */
437 expand_line (struct outp_driver *this, int y, int length)
439 struct ascii_driver_ext *ext = this->ext;
440 struct line *line = &ext->lines[y];
441 if (line->char_cnt < length)
444 if (line->char_cap < length)
446 line->char_cap = MIN (length * 2, this->width);
447 line->chars = pool_nrealloc (ext->pool,
449 line->char_cap, sizeof *line->chars);
451 for (x = line->char_cnt; x < length; x++)
452 line->chars[x] = ' ';
453 line->char_cnt = length;
458 ascii_line (struct outp_driver *this,
459 int x0, int y0, int x1, int y1,
460 enum outp_line_style top, enum outp_line_style left,
461 enum outp_line_style bottom, enum outp_line_style right)
463 struct ascii_driver_ext *ext = this->ext;
465 unsigned short value;
467 assert (this->page_open);
469 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
471 #if !SUPPRESS_WARNINGS
472 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
473 x0, y0, x1, y1, this->width, this->length);
479 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
480 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
481 for (y = y0; y < y1; y++)
485 expand_line (this, y, x1);
486 for (x = x0; x < x1; x++)
487 ext->lines[y].chars[x] = value;
492 text_draw (struct outp_driver *this,
495 enum outp_justification justification, int width,
496 const char *string, size_t length)
498 struct ascii_driver_ext *ext = this->ext;
499 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
503 switch (justification)
508 x += (width - length + 1) / 2;
517 if (y >= this->length || x >= this->width)
520 if (x + length > this->width)
521 length = this->width - x;
523 line_len = x + length;
525 expand_line (this, y, line_len);
527 ext->lines[y].chars[x++] = *string++ | attr;
530 /* Divides the text T->S into lines of width T->H. Sets T->V to the
531 number of lines necessary. Actually draws the text if DRAW is
534 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
535 int *width, int *height)
540 const char *cp = ss_data (text->string);
543 height_left = text->v;
545 while (height_left > 0)
551 /* Initially the line is up to text->h characters long. */
552 chars_left = ss_end (text->string) - cp;
555 line_len = MIN (chars_left, text->h);
557 /* A new-line terminates the line prematurely. */
558 end = memchr (cp, '\n', line_len);
562 /* Don't cut off words if it can be avoided. */
563 if (cp + line_len < ss_end (text->string))
565 size_t space_len = line_len;
566 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
569 line_len = space_len;
576 text->x, text->y + (text->v - height_left),
577 text->justification, text->h,
582 if (line_len > max_width)
583 max_width = line_len;
587 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
594 *height = text->v - height_left;
598 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
599 int *width, int *height)
601 delineate (this, t, false, width, height);
605 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
607 assert (this->page_open);
608 delineate (this, t, true, NULL, NULL);
612 /* ascii_close_page () and support routines. */
614 /* Writes the LENGTH characters in S to OUT. */
616 output_line (struct outp_driver *this, const struct line *line,
619 struct ascii_driver_ext *ext = this->ext;
620 const unsigned short *s = line->chars;
623 for (length = line->char_cnt; length-- > 0; s++)
625 ds_put_cstr (out, ext->box[*s & 0xff]);
628 if (*s & ATTR_EMPHASIS)
630 if (ext->emphasis == EMPH_BOLD)
632 ds_put_char (out, *s);
633 ds_put_char (out, '\b');
635 else if (ext->emphasis == EMPH_UNDERLINE)
636 ds_put_cstr (out, "_\b");
638 ds_put_char (out, *s);
643 append_lr_justified (struct string *out, int width,
644 const char *left, const char *right)
646 ds_put_char_multiple (out, ' ', width);
649 size_t length = MIN (strlen (left), width);
650 memcpy (ds_end (out) - width, left, length);
654 size_t length = MIN (strlen (right), width);
655 memcpy (ds_end (out) - length, right, length);
657 ds_put_char (out, '\n');
661 dump_output (struct outp_driver *this, struct string *out)
663 struct ascii_driver_ext *x = this->ext;
664 fwrite (ds_data (out), ds_length (out), 1, x->file);
669 ascii_close_page (struct outp_driver *this)
671 struct ascii_driver_ext *x = this->ext;
675 ds_init_empty (&out);
677 ds_put_char_multiple (&out, '\n', x->top_margin);
682 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
683 r2 = xasprintf ("%s - %s" , version, host_system);
685 append_lr_justified (&out, this->width, outp_title, r1);
686 append_lr_justified (&out, this->width, outp_subtitle, r2);
687 ds_put_char (&out, '\n');
692 dump_output (this, &out);
694 for (line_num = 0; line_num < this->length; line_num++)
697 /* Squeeze multiple blank lines into a single blank line if
699 if (x->squeeze_blank_lines)
701 if (line_num >= x->line_cap)
704 && x->lines[line_num].char_cnt == 0
705 && x->lines[line_num - 1].char_cnt == 0)
709 if (line_num < x->line_cap)
710 output_line (this, &x->lines[line_num], &out);
711 ds_put_char (&out, '\n');
712 dump_output (this, &out);
715 ds_put_char_multiple (&out, '\n', x->bottom_margin);
717 ds_put_char (&out, '\f');
719 dump_output (this, &out);
724 ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch)
726 error (0, 0, _("ascii: charts are unsupported by this driver"));
731 ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED)
736 struct outp_class ascii_class =
753 ascii_chart_initialise,