1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000 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/>. */
24 #include <data/file-name.h>
25 #include <libpspp/alloc.h>
26 #include <libpspp/assertion.h>
27 #include <libpspp/compiler.h>
28 #include <libpspp/pool.h>
29 #include <libpspp/start-date.h>
30 #include <libpspp/version.h>
38 #define _(msgid) gettext (msgid)
40 /* ASCII driver options: (defaults listed first)
42 output-file="pspp.list"
43 paginate=on|off Formfeeds are desired?
44 tab-width=8 Width of a tab; 0 to not use tabs.
46 headers=on|off Put headers at top of page?
47 emphasis=bold|underline|none Style to use for emphasis.
50 squeeze=off|on Squeeze multiple newlines into exactly one.
55 box[x]="strng" Sets box character X (X in base 4: 0-3333).
58 /* Disable messages by failed range checks. */
59 /*#define SUPPRESS_WARNINGS 1 */
61 /* Line styles bit shifts. */
72 /* Character attributes. */
73 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
74 #define ATTR_BOX 0x200 /* Line drawing character. */
79 unsigned short *chars; /* Characters and attributes. */
80 int char_cnt; /* Length. */
81 int char_cap; /* Allocated bytes. */
84 /* How to emphasize text. */
87 EMPH_BOLD, /* Overstrike for bold. */
88 EMPH_UNDERLINE, /* Overstrike for underlining. */
89 EMPH_NONE /* No emphasis. */
92 /* ASCII output driver extension record. */
93 struct ascii_driver_ext
97 /* User parameters. */
98 bool headers; /* Print headers at top of page? */
99 bool paginate; /* Insert formfeeds? */
100 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
101 enum emphasis_style emphasis; /* How to emphasize text. */
102 int tab_width; /* Width of a tab; 0 not to use tabs. */
104 int page_length; /* Page length before subtracting margins. */
105 int top_margin; /* Top margin in lines. */
106 int bottom_margin; /* Bottom margin in lines. */
108 char *box[LNS_COUNT]; /* Line & box drawing characters. */
110 /* Internal state. */
111 char *file_name; /* Output file name. */
112 FILE *file; /* Output file. */
113 int page_number; /* Current page number. */
114 struct line *lines; /* Page content. */
115 int line_cap; /* Number of lines allocated. */
118 static int get_default_box_char (size_t idx);
119 static bool handle_option (struct outp_driver *this, const char *key,
120 const struct string *val);
123 ascii_open_driver (struct outp_driver *this, struct substring options)
125 struct ascii_driver_ext *x;
129 this->font_height = 1;
130 this->prop_em_width = 1;
131 this->fixed_width = 1;
132 for (i = 0; i < OUTP_L_COUNT; i++)
133 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
135 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
138 x->squeeze_blank_lines = false;
139 x->emphasis = EMPH_BOLD;
143 x->bottom_margin = 2;
144 for (i = 0; i < LNS_COUNT; i++)
146 x->file_name = pool_strdup (x->pool, "pspp.list");
152 if (!outp_parse_options (options, handle_option, this))
155 x->file = pool_fopen (x->pool, x->file_name, "w");
158 error (0, errno, _("ascii: opening output file \"%s\""), x->file_name);
162 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
166 if (this->width < 59 || this->length < 15)
169 _("ascii: page excluding margins and headers "
170 "must be at least 59 characters wide by 15 lines long, but as "
171 "configured is only %d characters by %d lines"),
172 this->width, this->length);
176 for (i = 0; i < LNS_COUNT; i++)
177 if (x->box[i] == NULL)
180 s[0] = get_default_box_char (i);
182 x->box[i] = pool_strdup (x->pool, s);
188 pool_destroy (x->pool);
193 get_default_box_char (size_t idx)
195 /* Disassemble IDX into components. */
196 unsigned top = (idx >> LNS_TOP) & 3;
197 unsigned left = (idx >> LNS_LEFT) & 3;
198 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
199 unsigned right = (idx >> LNS_RIGHT) & 3;
201 /* Reassemble components into nibbles in the order TLBR.
202 This makes it easy to read the case labels. */
203 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
209 case 0x0100: case 0x0101: case 0x0001:
212 case 0x1000: case 0x1010: case 0x0010:
215 case 0x0300: case 0x0303: case 0x0003:
216 case 0x0200: case 0x0202: case 0x0002:
220 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
225 ascii_close_driver (struct outp_driver *this)
227 struct ascii_driver_ext *x = this->ext;
229 if (fn_close (x->file_name, x->file) != 0)
230 error (0, errno, _("ascii: closing output file \"%s\""), x->file_name);
231 pool_detach_file (x->pool, x->file);
232 pool_destroy (x->pool);
237 /* Generic option types. */
247 static const struct outp_option option_tab[] =
249 {"headers", boolean_arg, 0},
250 {"paginate", boolean_arg, 1},
251 {"squeeze", boolean_arg, 2},
253 {"emphasis", string_arg, 3},
255 {"output-file", output_file_arg, 0},
257 {"length", pos_int_arg, 0},
258 {"width", pos_int_arg, 1},
260 {"top-margin", nonneg_int_arg, 0},
261 {"bottom-margin", nonneg_int_arg, 1},
262 {"tab-width", nonneg_int_arg, 2},
268 handle_option (struct outp_driver *this, const char *key,
269 const struct string *val)
271 struct ascii_driver_ext *x = this->ext;
275 value = ds_cstr (val);
276 if (!strncmp (key, "box[", 4))
279 int indx = strtol (&key[4], &tail, 4);
280 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
282 error (0, 0, _("ascii: bad index value for `box' key: syntax "
283 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
284 "expressed in base 4"),
288 if (x->box[indx] != NULL)
289 error (0, 0, _("ascii: multiple values for %s"), key);
290 x->box[indx] = pool_strdup (x->pool, value);
294 switch (outp_match_keyword (key, option_tab, &subcat))
297 error (0, 0, _("ascii: unknown parameter `%s'"), key);
299 case output_file_arg:
300 x->file_name = pool_strdup (x->pool, value);
308 arg = strtol (value, &tail, 0);
309 if (arg < 1 || errno == ERANGE || *tail)
311 error (0, 0, _("ascii: positive integer required as `%s' value"),
318 x->page_length = arg;
329 if (!strcmp (value, "bold"))
330 x->emphasis = EMPH_BOLD;
331 else if (!strcmp (value, "underline"))
332 x->emphasis = EMPH_UNDERLINE;
333 else if (!strcmp (value, "none"))
334 x->emphasis = EMPH_NONE;
337 _("ascii: `emphasis' value must be `bold', "
338 "`underline', or `none'"));
346 arg = strtol (value, &tail, 0);
347 if (arg < 0 || errno == ERANGE || *tail)
350 _("ascii: zero or positive integer required as `%s' value"),
360 x->bottom_margin = arg;
373 if (!strcmp (value, "on") || !strcmp (value, "true")
374 || !strcmp (value, "yes") || atoi (value))
376 else if (!strcmp (value, "off") || !strcmp (value, "false")
377 || !strcmp (value, "no") || !strcmp (value, "0"))
381 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
387 x->headers = setting;
390 x->paginate = setting;
393 x->squeeze_blank_lines = setting;
408 ascii_open_page (struct outp_driver *this)
410 struct ascii_driver_ext *x = this->ext;
415 if (this->length > x->line_cap)
417 x->lines = pool_nrealloc (x->pool,
418 x->lines, this->length, sizeof *x->lines);
419 for (i = x->line_cap; i < this->length; i++)
421 struct line *line = &x->lines[i];
425 x->line_cap = this->length;
428 for (i = 0; i < this->length; i++)
429 x->lines[i].char_cnt = 0;
432 /* Ensures that at least the first LENGTH characters of line Y in
433 THIS driver identified X have been cleared out. */
435 expand_line (struct outp_driver *this, int y, int length)
437 struct ascii_driver_ext *ext = this->ext;
438 struct line *line = &ext->lines[y];
439 if (line->char_cnt < length)
442 if (line->char_cap < length)
444 line->char_cap = MIN (length * 2, this->width);
445 line->chars = pool_nrealloc (ext->pool,
447 line->char_cap, sizeof *line->chars);
449 for (x = line->char_cnt; x < length; x++)
450 line->chars[x] = ' ';
451 line->char_cnt = length;
456 ascii_line (struct outp_driver *this,
457 int x0, int y0, int x1, int y1,
458 enum outp_line_style top, enum outp_line_style left,
459 enum outp_line_style bottom, enum outp_line_style right)
461 struct ascii_driver_ext *ext = this->ext;
463 unsigned short value;
465 assert (this->page_open);
467 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
469 #if !SUPPRESS_WARNINGS
470 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
471 x0, y0, x1, y1, this->width, this->length);
477 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
478 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
479 for (y = y0; y < y1; y++)
483 expand_line (this, y, x1);
484 for (x = x0; x < x1; x++)
485 ext->lines[y].chars[x] = value;
490 text_draw (struct outp_driver *this,
493 enum outp_justification justification, int width,
494 const char *string, size_t length)
496 struct ascii_driver_ext *ext = this->ext;
497 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
501 switch (justification)
506 x += (width - length + 1) / 2;
515 if (y >= this->length || x >= this->width)
518 if (x + length > this->width)
519 length = this->width - x;
521 line_len = x + length;
523 expand_line (this, y, line_len);
525 ext->lines[y].chars[x++] = *string++ | attr;
528 /* Divides the text T->S into lines of width T->H. Sets T->V to the
529 number of lines necessary. Actually draws the text if DRAW is
532 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
533 int *width, int *height)
538 const char *cp = ss_data (text->string);
541 height_left = text->v;
543 while (height_left > 0)
549 /* Initially the line is up to text->h characters long. */
550 chars_left = ss_end (text->string) - cp;
553 line_len = MIN (chars_left, text->h);
555 /* A new-line terminates the line prematurely. */
556 end = memchr (cp, '\n', line_len);
560 /* Don't cut off words if it can be avoided. */
561 if (cp + line_len < ss_end (text->string))
563 size_t space_len = line_len;
564 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
567 line_len = space_len;
574 text->x, text->y + (text->v - height_left),
575 text->justification, text->h,
580 if (line_len > max_width)
581 max_width = line_len;
585 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
592 *height = text->v - height_left;
596 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
597 int *width, int *height)
599 delineate (this, t, false, width, height);
603 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
605 assert (this->page_open);
606 delineate (this, t, true, NULL, NULL);
610 /* ascii_close_page () and support routines. */
612 /* Writes the LENGTH characters in S to OUT. */
614 output_line (struct outp_driver *this, const struct line *line,
617 struct ascii_driver_ext *ext = this->ext;
618 const unsigned short *s = line->chars;
621 for (length = line->char_cnt; length-- > 0; s++)
623 ds_put_cstr (out, ext->box[*s & 0xff]);
626 if (*s & ATTR_EMPHASIS)
628 if (ext->emphasis == EMPH_BOLD)
630 ds_put_char (out, *s);
631 ds_put_char (out, '\b');
633 else if (ext->emphasis == EMPH_UNDERLINE)
634 ds_put_cstr (out, "_\b");
636 ds_put_char (out, *s);
641 append_lr_justified (struct string *out, int width,
642 const char *left, const char *right)
644 ds_put_char_multiple (out, ' ', width);
647 size_t length = MIN (strlen (left), width);
648 memcpy (ds_end (out) - width, left, length);
652 size_t length = MIN (strlen (right), width);
653 memcpy (ds_end (out) - length, right, length);
655 ds_put_char (out, '\n');
659 dump_output (struct outp_driver *this, struct string *out)
661 struct ascii_driver_ext *x = this->ext;
662 fwrite (ds_data (out), ds_length (out), 1, x->file);
667 ascii_close_page (struct outp_driver *this)
669 struct ascii_driver_ext *x = this->ext;
673 ds_init_empty (&out);
675 ds_put_char_multiple (&out, '\n', x->top_margin);
680 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
681 r2 = xasprintf ("%s - %s" , version, host_system);
683 append_lr_justified (&out, this->width, outp_title, r1);
684 append_lr_justified (&out, this->width, outp_subtitle, r2);
685 ds_put_char (&out, '\n');
690 dump_output (this, &out);
692 for (line_num = 0; line_num < this->length; line_num++)
695 /* Squeeze multiple blank lines into a single blank line if
697 if (x->squeeze_blank_lines)
699 if (line_num >= x->line_cap)
702 && x->lines[line_num].char_cnt == 0
703 && x->lines[line_num - 1].char_cnt == 0)
707 if (line_num < x->line_cap)
708 output_line (this, &x->lines[line_num], &out);
709 ds_put_char (&out, '\n');
710 dump_output (this, &out);
713 ds_put_char_multiple (&out, '\n', x->bottom_margin);
715 ds_put_char (&out, '\f');
717 dump_output (this, &out);
722 ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch)
724 error (0, 0, _("ascii: charts are unsupported by this driver"));
729 ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED)
734 const struct outp_class ascii_class =
751 ascii_chart_initialise,