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).
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 void ascii_flush (struct outp_driver *);
119 static int get_default_box_char (size_t idx);
120 static bool handle_option (struct outp_driver *this, const char *key,
121 const struct string *val);
124 ascii_open_driver (struct outp_driver *this, struct substring options)
126 struct ascii_driver_ext *x;
130 this->font_height = 1;
131 this->prop_em_width = 1;
132 this->fixed_width = 1;
133 for (i = 0; i < OUTP_L_COUNT; i++)
134 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
136 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
139 x->squeeze_blank_lines = false;
140 x->emphasis = EMPH_BOLD;
144 x->bottom_margin = 2;
145 for (i = 0; i < LNS_COUNT; i++)
147 x->file_name = pool_strdup (x->pool, "pspp.list");
153 if (!outp_parse_options (options, handle_option, this))
156 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
160 if (this->width < 59 || this->length < 15)
163 _("ascii: page excluding margins and headers "
164 "must be at least 59 characters wide by 15 lines long, but as "
165 "configured is only %d characters by %d lines"),
166 this->width, this->length);
170 for (i = 0; i < LNS_COUNT; i++)
171 if (x->box[i] == NULL)
174 s[0] = get_default_box_char (i);
176 x->box[i] = pool_strdup (x->pool, s);
182 pool_destroy (x->pool);
187 get_default_box_char (size_t idx)
189 /* Disassemble IDX into components. */
190 unsigned top = (idx >> LNS_TOP) & 3;
191 unsigned left = (idx >> LNS_LEFT) & 3;
192 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
193 unsigned right = (idx >> LNS_RIGHT) & 3;
195 /* Reassemble components into nibbles in the order TLBR.
196 This makes it easy to read the case labels. */
197 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
203 case 0x0100: case 0x0101: case 0x0001:
206 case 0x1000: case 0x1010: case 0x0010:
209 case 0x0300: case 0x0303: case 0x0003:
210 case 0x0200: case 0x0202: case 0x0002:
214 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
219 ascii_close_driver (struct outp_driver *this)
221 struct ascii_driver_ext *x = this->ext;
224 pool_detach_file (x->pool, x->file);
225 pool_destroy (x->pool);
230 /* Generic option types. */
240 static const struct outp_option option_tab[] =
242 {"headers", boolean_arg, 0},
243 {"paginate", boolean_arg, 1},
244 {"squeeze", boolean_arg, 2},
246 {"emphasis", string_arg, 3},
248 {"output-file", output_file_arg, 0},
250 {"length", pos_int_arg, 0},
251 {"width", pos_int_arg, 1},
253 {"top-margin", nonneg_int_arg, 0},
254 {"bottom-margin", nonneg_int_arg, 1},
255 {"tab-width", nonneg_int_arg, 2},
261 handle_option (struct outp_driver *this, const char *key,
262 const struct string *val)
264 struct ascii_driver_ext *x = this->ext;
268 value = ds_cstr (val);
269 if (!strncmp (key, "box[", 4))
272 int indx = strtol (&key[4], &tail, 4);
273 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
275 error (0, 0, _("ascii: bad index value for `box' key: syntax "
276 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
277 "expressed in base 4"),
281 if (x->box[indx] != NULL)
282 error (0, 0, _("ascii: multiple values for %s"), key);
283 x->box[indx] = pool_strdup (x->pool, value);
287 switch (outp_match_keyword (key, option_tab, &subcat))
290 error (0, 0, _("ascii: unknown parameter `%s'"), key);
292 case output_file_arg:
293 x->file_name = pool_strdup (x->pool, value);
301 arg = strtol (value, &tail, 0);
302 if (arg < 1 || errno == ERANGE || *tail)
304 error (0, 0, _("ascii: positive integer required as `%s' value"),
311 x->page_length = arg;
322 if (!strcmp (value, "bold"))
323 x->emphasis = EMPH_BOLD;
324 else if (!strcmp (value, "underline"))
325 x->emphasis = EMPH_UNDERLINE;
326 else if (!strcmp (value, "none"))
327 x->emphasis = EMPH_NONE;
330 _("ascii: `emphasis' value must be `bold', "
331 "`underline', or `none'"));
339 arg = strtol (value, &tail, 0);
340 if (arg < 0 || errno == ERANGE || *tail)
343 _("ascii: zero or positive integer required as `%s' value"),
353 x->bottom_margin = arg;
366 if (!strcmp (value, "on") || !strcmp (value, "true")
367 || !strcmp (value, "yes") || atoi (value))
369 else if (!strcmp (value, "off") || !strcmp (value, "false")
370 || !strcmp (value, "no") || !strcmp (value, "0"))
374 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
380 x->headers = setting;
383 x->paginate = setting;
386 x->squeeze_blank_lines = setting;
401 ascii_open_page (struct outp_driver *this)
403 struct ascii_driver_ext *x = this->ext;
408 x->file = fn_open (x->file_name, "w");
411 error (0, errno, _("ascii: opening output file \"%s\""),
415 pool_attach_file (x->pool, x->file);
420 if (this->length > x->line_cap)
422 x->lines = pool_nrealloc (x->pool,
423 x->lines, this->length, sizeof *x->lines);
424 for (i = x->line_cap; i < this->length; i++)
426 struct line *line = &x->lines[i];
430 x->line_cap = this->length;
433 for (i = 0; i < this->length; i++)
434 x->lines[i].char_cnt = 0;
437 /* Ensures that at least the first LENGTH characters of line Y in
438 THIS driver identified X have been cleared out. */
440 expand_line (struct outp_driver *this, int y, int length)
442 struct ascii_driver_ext *ext = this->ext;
443 struct line *line = &ext->lines[y];
444 if (line->char_cnt < length)
447 if (line->char_cap < length)
449 line->char_cap = MIN (length * 2, this->width);
450 line->chars = pool_nrealloc (ext->pool,
452 line->char_cap, sizeof *line->chars);
454 for (x = line->char_cnt; x < length; x++)
455 line->chars[x] = ' ';
456 line->char_cnt = length;
461 ascii_line (struct outp_driver *this,
462 int x0, int y0, int x1, int y1,
463 enum outp_line_style top, enum outp_line_style left,
464 enum outp_line_style bottom, enum outp_line_style right)
466 struct ascii_driver_ext *ext = this->ext;
468 unsigned short value;
470 assert (this->page_open);
472 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
474 #if !SUPPRESS_WARNINGS
475 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
476 x0, y0, x1, y1, this->width, this->length);
482 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
483 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
484 for (y = y0; y < y1; y++)
488 expand_line (this, y, x1);
489 for (x = x0; x < x1; x++)
490 ext->lines[y].chars[x] = value;
495 text_draw (struct outp_driver *this,
498 enum outp_justification justification, int width,
499 const char *string, size_t length)
501 struct ascii_driver_ext *ext = this->ext;
502 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
506 switch (justification)
511 x += (width - length + 1) / 2;
520 if (y >= this->length || x >= this->width)
523 if (x + length > this->width)
524 length = this->width - x;
526 line_len = x + length;
528 expand_line (this, y, line_len);
530 ext->lines[y].chars[x++] = *string++ | attr;
533 /* Divides the text T->S into lines of width T->H. Sets T->V to the
534 number of lines necessary. Actually draws the text if DRAW is
537 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
538 int *width, int *height)
543 const char *cp = ss_data (text->string);
546 height_left = text->v;
548 while (height_left > 0)
554 /* Initially the line is up to text->h characters long. */
555 chars_left = ss_end (text->string) - cp;
558 line_len = MIN (chars_left, text->h);
560 /* A new-line terminates the line prematurely. */
561 end = memchr (cp, '\n', line_len);
565 /* Don't cut off words if it can be avoided. */
566 if (cp + line_len < ss_end (text->string))
568 size_t space_len = line_len;
569 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
572 line_len = space_len;
579 text->x, text->y + (text->v - height_left),
580 text->justification, text->h,
585 if (line_len > max_width)
586 max_width = line_len;
590 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
597 *height = text->v - height_left;
601 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
602 int *width, int *height)
604 delineate (this, t, false, width, height);
608 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
610 assert (this->page_open);
611 delineate (this, t, true, NULL, NULL);
615 /* ascii_close_page () and support routines. */
617 /* Writes the LENGTH characters in S to OUT. */
619 output_line (struct outp_driver *this, const struct line *line,
622 struct ascii_driver_ext *ext = this->ext;
623 const unsigned short *s = line->chars;
626 for (length = line->char_cnt; length-- > 0; s++)
628 ds_put_cstr (out, ext->box[*s & 0xff]);
631 if (*s & ATTR_EMPHASIS)
633 if (ext->emphasis == EMPH_BOLD)
635 ds_put_char (out, *s);
636 ds_put_char (out, '\b');
638 else if (ext->emphasis == EMPH_UNDERLINE)
639 ds_put_cstr (out, "_\b");
641 ds_put_char (out, *s);
646 append_lr_justified (struct string *out, int width,
647 const char *left, const char *right)
649 ds_put_char_multiple (out, ' ', width);
652 size_t length = MIN (strlen (left), width);
653 memcpy (ds_end (out) - width, left, length);
657 size_t length = MIN (strlen (right), width);
658 memcpy (ds_end (out) - length, right, length);
660 ds_put_char (out, '\n');
664 dump_output (struct outp_driver *this, struct string *out)
666 struct ascii_driver_ext *x = this->ext;
667 fwrite (ds_data (out), ds_length (out), 1, x->file);
672 ascii_close_page (struct outp_driver *this)
674 struct ascii_driver_ext *x = this->ext;
681 ds_init_empty (&out);
683 ds_put_char_multiple (&out, '\n', x->top_margin);
688 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
689 r2 = xasprintf ("%s - %s" , version, host_system);
691 append_lr_justified (&out, this->width, outp_title, r1);
692 append_lr_justified (&out, this->width, outp_subtitle, r2);
693 ds_put_char (&out, '\n');
698 dump_output (this, &out);
700 for (line_num = 0; line_num < this->length; line_num++)
703 /* Squeeze multiple blank lines into a single blank line if
705 if (x->squeeze_blank_lines)
707 if (line_num >= x->line_cap)
710 && x->lines[line_num].char_cnt == 0
711 && x->lines[line_num - 1].char_cnt == 0)
715 if (line_num < x->line_cap)
716 output_line (this, &x->lines[line_num], &out);
717 ds_put_char (&out, '\n');
718 dump_output (this, &out);
721 ds_put_char_multiple (&out, '\n', x->bottom_margin);
723 ds_put_char (&out, '\f');
725 dump_output (this, &out);
729 /* Flushes all output to the user and lets the user deal with it.
730 This is applied only to output drivers that are designated as
731 "screen" drivers that the user is interacting with in real
734 ascii_flush (struct outp_driver *this)
736 struct ascii_driver_ext *x = this->ext;
740 if (fn_close (x->file_name, x->file) != 0)
741 error (0, errno, _("ascii: closing output file \"%s\""),
743 pool_detach_file (x->pool, x->file);
749 ascii_chart_initialise (struct outp_driver *d UNUSED, struct chart *ch)
751 error (0, 0, _("ascii: charts are unsupported by this driver"));
756 ascii_chart_finalise (struct outp_driver *d UNUSED, struct chart *ch UNUSED)
761 const struct outp_class ascii_class =
779 ascii_chart_initialise,