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/>. */
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).
56 init="string" Set initialization string.
59 /* Disable messages by failed range checks. */
60 /*#define SUPPRESS_WARNINGS 1 */
62 /* Line styles bit shifts. */
73 /* Character attributes. */
74 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
75 #define ATTR_BOX 0x200 /* Line drawing character. */
80 unsigned short *chars; /* Characters and attributes. */
81 int char_cnt; /* Length. */
82 int char_cap; /* Allocated bytes. */
85 /* How to emphasize text. */
88 EMPH_BOLD, /* Overstrike for bold. */
89 EMPH_UNDERLINE, /* Overstrike for underlining. */
90 EMPH_NONE /* No emphasis. */
93 /* ASCII output driver extension record. */
94 struct ascii_driver_ext
98 /* User parameters. */
99 bool headers; /* Print headers at top of page? */
100 bool paginate; /* Insert formfeeds? */
101 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
102 enum emphasis_style emphasis; /* How to emphasize text. */
103 int tab_width; /* Width of a tab; 0 not to use tabs. */
105 int page_length; /* Page length before subtracting margins. */
106 int top_margin; /* Top margin in lines. */
107 int bottom_margin; /* Bottom margin in lines. */
109 char *box[LNS_COUNT]; /* Line & box drawing characters. */
110 char *init; /* Device initialization string. */
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 void ascii_flush (struct outp_driver *);
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++)
150 x->file_name = pool_strdup (x->pool, "pspp.list");
156 if (!outp_parse_options (options, handle_option, this))
159 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
163 if (this->width < 59 || this->length < 15)
166 _("ascii: page excluding margins and headers "
167 "must be at least 59 characters wide by 15 lines long, but as "
168 "configured is only %d characters by %d lines"),
169 this->width, this->length);
173 for (i = 0; i < LNS_COUNT; i++)
174 if (x->box[i] == NULL)
177 s[0] = get_default_box_char (i);
179 x->box[i] = pool_strdup (x->pool, s);
185 pool_destroy (x->pool);
190 get_default_box_char (size_t idx)
192 /* Disassemble IDX into components. */
193 unsigned top = (idx >> LNS_TOP) & 3;
194 unsigned left = (idx >> LNS_LEFT) & 3;
195 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
196 unsigned right = (idx >> LNS_RIGHT) & 3;
198 /* Reassemble components into nibbles in the order TLBR.
199 This makes it easy to read the case labels. */
200 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
206 case 0x0100: case 0x0101: case 0x0001:
209 case 0x1000: case 0x1010: case 0x0010:
212 case 0x0300: case 0x0303: case 0x0003:
213 case 0x0200: case 0x0202: case 0x0002:
217 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
222 ascii_close_driver (struct outp_driver *this)
224 struct ascii_driver_ext *x = this->ext;
227 pool_detach_file (x->pool, x->file);
228 pool_destroy (x->pool);
233 /* Generic option types. */
244 static const struct outp_option option_tab[] =
246 {"headers", boolean_arg, 0},
247 {"paginate", boolean_arg, 1},
248 {"squeeze", boolean_arg, 2},
250 {"emphasis", emphasis_arg, 0},
252 {"output-file", output_file_arg, 0},
254 {"length", pos_int_arg, 0},
255 {"width", pos_int_arg, 1},
257 {"top-margin", nonneg_int_arg, 0},
258 {"bottom-margin", nonneg_int_arg, 1},
259 {"tab-width", nonneg_int_arg, 2},
261 {"init", string_arg, 0},
267 handle_option (struct outp_driver *this, const char *key,
268 const struct string *val)
270 struct ascii_driver_ext *x = this->ext;
274 value = ds_cstr (val);
275 if (!strncmp (key, "box[", 4))
278 int indx = strtol (&key[4], &tail, 4);
279 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
281 error (0, 0, _("ascii: bad index value for `box' key: syntax "
282 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
283 "expressed in base 4"),
287 if (x->box[indx] != NULL)
288 error (0, 0, _("ascii: multiple values for %s"), key);
289 x->box[indx] = pool_strdup (x->pool, value);
293 switch (outp_match_keyword (key, option_tab, &subcat))
296 error (0, 0, _("ascii: unknown parameter `%s'"), key);
298 case output_file_arg:
299 x->file_name = pool_strdup (x->pool, value);
307 arg = strtol (value, &tail, 0);
308 if (arg < 1 || errno == ERANGE || *tail)
310 error (0, 0, _("ascii: positive integer required as `%s' value"),
317 x->page_length = arg;
328 if (!strcmp (value, "bold"))
329 x->emphasis = EMPH_BOLD;
330 else if (!strcmp (value, "underline"))
331 x->emphasis = EMPH_UNDERLINE;
332 else if (!strcmp (value, "none"))
333 x->emphasis = EMPH_NONE;
336 _("ascii: `emphasis' value must be `bold', "
337 "`underline', or `none'"));
345 arg = strtol (value, &tail, 0);
346 if (arg < 0 || errno == ERANGE || *tail)
349 _("ascii: zero or positive integer required as `%s' value"),
359 x->bottom_margin = arg;
372 if (!strcmp (value, "on") || !strcmp (value, "true")
373 || !strcmp (value, "yes") || atoi (value))
375 else if (!strcmp (value, "off") || !strcmp (value, "false")
376 || !strcmp (value, "no") || !strcmp (value, "0"))
380 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
386 x->headers = setting;
389 x->paginate = setting;
392 x->squeeze_blank_lines = setting;
401 x->init = pool_strdup (x->pool, value);
411 ascii_open_page (struct outp_driver *this)
413 struct ascii_driver_ext *x = this->ext;
418 x->file = fn_open (x->file_name, "w");
421 error (0, errno, _("ascii: opening output file \"%s\""),
425 pool_attach_file (x->pool, x->file);
428 fputs (x->init, x->file);
433 if (this->length > x->line_cap)
435 x->lines = pool_nrealloc (x->pool,
436 x->lines, this->length, sizeof *x->lines);
437 for (i = x->line_cap; i < this->length; i++)
439 struct line *line = &x->lines[i];
443 x->line_cap = this->length;
446 for (i = 0; i < this->length; i++)
447 x->lines[i].char_cnt = 0;
450 /* Ensures that at least the first LENGTH characters of line Y in
451 THIS driver identified X have been cleared out. */
453 expand_line (struct outp_driver *this, int y, int length)
455 struct ascii_driver_ext *ext = this->ext;
456 struct line *line = &ext->lines[y];
457 if (line->char_cnt < length)
460 if (line->char_cap < length)
462 line->char_cap = MIN (length * 2, this->width);
463 line->chars = pool_nrealloc (ext->pool,
465 line->char_cap, sizeof *line->chars);
467 for (x = line->char_cnt; x < length; x++)
468 line->chars[x] = ' ';
469 line->char_cnt = length;
474 ascii_line (struct outp_driver *this,
475 int x0, int y0, int x1, int y1,
476 enum outp_line_style top, enum outp_line_style left,
477 enum outp_line_style bottom, enum outp_line_style right)
479 struct ascii_driver_ext *ext = this->ext;
481 unsigned short value;
483 assert (this->page_open);
485 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
487 #if !SUPPRESS_WARNINGS
488 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
489 x0, y0, x1, y1, this->width, this->length);
495 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
496 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
497 for (y = y0; y < y1; y++)
501 expand_line (this, y, x1);
502 for (x = x0; x < x1; x++)
503 ext->lines[y].chars[x] = value;
508 text_draw (struct outp_driver *this,
511 enum outp_justification justification, int width,
512 const char *string, size_t length)
514 struct ascii_driver_ext *ext = this->ext;
515 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
519 switch (justification)
524 x += (width - length + 1) / 2;
533 if (y >= this->length || x >= this->width)
536 if (x + length > this->width)
537 length = this->width - x;
539 line_len = x + length;
541 expand_line (this, y, line_len);
543 ext->lines[y].chars[x++] = *string++ | attr;
546 /* Divides the text T->S into lines of width T->H. Sets T->V to the
547 number of lines necessary. Actually draws the text if DRAW is
550 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
551 int *width, int *height)
556 const char *cp = ss_data (text->string);
559 height_left = text->v;
561 while (height_left > 0)
567 /* Initially the line is up to text->h characters long. */
568 chars_left = ss_end (text->string) - cp;
571 line_len = MIN (chars_left, text->h);
573 /* A new-line terminates the line prematurely. */
574 end = memchr (cp, '\n', line_len);
578 /* Don't cut off words if it can be avoided. */
579 if (cp + line_len < ss_end (text->string))
581 size_t space_len = line_len;
582 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
585 line_len = space_len;
592 text->x, text->y + (text->v - height_left),
593 text->justification, text->h,
598 if (line_len > max_width)
599 max_width = line_len;
603 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
610 *height = text->v - height_left;
614 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
615 int *width, int *height)
617 delineate (this, t, false, width, height);
621 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
623 assert (this->page_open);
624 delineate (this, t, true, NULL, NULL);
628 /* ascii_close_page () and support routines. */
630 /* Writes the LENGTH characters in S to OUT. */
632 output_line (struct outp_driver *this, const struct line *line,
635 struct ascii_driver_ext *ext = this->ext;
636 const unsigned short *s = line->chars;
639 for (length = line->char_cnt; length-- > 0; s++)
641 ds_put_cstr (out, ext->box[*s & 0xff]);
644 if (*s & ATTR_EMPHASIS)
646 if (ext->emphasis == EMPH_BOLD)
648 ds_put_char (out, *s);
649 ds_put_char (out, '\b');
651 else if (ext->emphasis == EMPH_UNDERLINE)
652 ds_put_cstr (out, "_\b");
654 ds_put_char (out, *s);
659 append_lr_justified (struct string *out, int width,
660 const char *left, const char *right)
662 ds_put_char_multiple (out, ' ', width);
665 size_t length = MIN (strlen (left), width);
666 memcpy (ds_end (out) - width, left, length);
670 size_t length = MIN (strlen (right), width);
671 memcpy (ds_end (out) - length, right, length);
673 ds_put_char (out, '\n');
677 dump_output (struct outp_driver *this, struct string *out)
679 struct ascii_driver_ext *x = this->ext;
680 fwrite (ds_data (out), ds_length (out), 1, x->file);
685 ascii_close_page (struct outp_driver *this)
687 struct ascii_driver_ext *x = this->ext;
694 ds_init_empty (&out);
696 ds_put_char_multiple (&out, '\n', x->top_margin);
701 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
702 r2 = xasprintf ("%s - %s" , version, host_system);
704 append_lr_justified (&out, this->width, outp_title, r1);
705 append_lr_justified (&out, this->width, outp_subtitle, r2);
706 ds_put_char (&out, '\n');
711 dump_output (this, &out);
713 for (line_num = 0; line_num < this->length; line_num++)
716 /* Squeeze multiple blank lines into a single blank line if
718 if (x->squeeze_blank_lines)
720 if (line_num >= x->line_cap)
723 && x->lines[line_num].char_cnt == 0
724 && x->lines[line_num - 1].char_cnt == 0)
728 if (line_num < x->line_cap)
729 output_line (this, &x->lines[line_num], &out);
730 ds_put_char (&out, '\n');
731 dump_output (this, &out);
734 ds_put_char_multiple (&out, '\n', x->bottom_margin);
736 ds_put_char (&out, '\f');
738 dump_output (this, &out);
742 /* Flushes all output to the user and lets the user deal with it.
743 This is applied only to output drivers that are designated as
744 "screen" drivers that the user is interacting with in real
747 ascii_flush (struct outp_driver *this)
749 struct ascii_driver_ext *x = this->ext;
753 if (fn_close (x->file_name, x->file) != 0)
754 error (0, errno, _("ascii: closing output file \"%s\""),
756 pool_detach_file (x->pool, x->file);
762 ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch)
764 error (0, 0, _("ascii: charts are unsupported by this driver"));
769 ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED)
774 const struct outp_class ascii_class =
792 ascii_chart_initialise,