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/>. */
25 #include <data/file-name.h>
26 #include <libpspp/alloc.h>
27 #include <libpspp/assertion.h>
28 #include <libpspp/compiler.h>
29 #include <libpspp/pool.h>
30 #include <libpspp/start-date.h>
31 #include <libpspp/version.h>
39 #define _(msgid) gettext (msgid)
41 /* ASCII driver options: (defaults listed first)
43 output-file="pspp.list"
44 chart-files="pspp-#.png" Name used for charts.
45 chart-type=png Format of charts (use "none" to disable).
47 paginate=on|off Formfeeds are desired?
48 tab-width=8 Width of a tab; 0 to not use tabs.
50 headers=on|off Put headers at top of page?
51 emphasis=bold|underline|none Style to use for emphasis.
54 squeeze=off|on Squeeze multiple newlines into exactly one.
59 box[x]="strng" Sets box character X (X in base 4: 0-3333).
60 init="string" Set initialization string.
63 /* Disable messages by failed range checks. */
64 /*#define SUPPRESS_WARNINGS 1 */
66 /* Line styles bit shifts. */
77 /* Character attributes. */
78 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
79 #define ATTR_BOX 0x200 /* Line drawing character. */
84 unsigned short *chars; /* Characters and attributes. */
85 int char_cnt; /* Length. */
86 int char_cap; /* Allocated bytes. */
89 /* How to emphasize text. */
92 EMPH_BOLD, /* Overstrike for bold. */
93 EMPH_UNDERLINE, /* Overstrike for underlining. */
94 EMPH_NONE /* No emphasis. */
97 /* ASCII output driver extension record. */
98 struct ascii_driver_ext
102 /* User parameters. */
103 bool headers; /* Print headers at top of page? */
104 bool paginate; /* Insert formfeeds? */
105 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
106 enum emphasis_style emphasis; /* How to emphasize text. */
107 int tab_width; /* Width of a tab; 0 not to use tabs. */
108 const char *chart_type; /* Type of charts to output; NULL for none. */
109 const char *chart_file_name; /* Name of files used for charts. */
111 int page_length; /* Page length before subtracting margins. */
112 int top_margin; /* Top margin in lines. */
113 int bottom_margin; /* Bottom margin in lines. */
115 char *box[LNS_COUNT]; /* Line & box drawing characters. */
116 char *init; /* Device initialization string. */
118 /* Internal state. */
119 char *file_name; /* Output file name. */
120 FILE *file; /* Output file. */
121 int page_number; /* Current page number. */
122 struct line *lines; /* Page content. */
123 int line_cap; /* Number of lines allocated. */
124 int chart_cnt; /* Number of charts so far. */
127 static void ascii_flush (struct outp_driver *);
128 static int get_default_box_char (size_t idx);
129 static bool handle_option (struct outp_driver *this, const char *key,
130 const struct string *val);
133 ascii_open_driver (struct outp_driver *this, struct substring options)
135 struct ascii_driver_ext *x;
139 this->font_height = 1;
140 this->prop_em_width = 1;
141 this->fixed_width = 1;
142 for (i = 0; i < OUTP_L_COUNT; i++)
143 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
145 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
148 x->squeeze_blank_lines = false;
149 x->emphasis = EMPH_BOLD;
151 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
152 x->chart_type = pool_strdup (x->pool, "png");
155 x->bottom_margin = 2;
156 for (i = 0; i < LNS_COUNT; i++)
159 x->file_name = pool_strdup (x->pool, "pspp.list");
166 if (!outp_parse_options (options, handle_option, this))
169 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
173 if (this->width < 59 || this->length < 15)
176 _("ascii: page excluding margins and headers "
177 "must be at least 59 characters wide by 15 lines long, but as "
178 "configured is only %d characters by %d lines"),
179 this->width, this->length);
183 for (i = 0; i < LNS_COUNT; i++)
184 if (x->box[i] == NULL)
187 s[0] = get_default_box_char (i);
189 x->box[i] = pool_strdup (x->pool, s);
195 pool_destroy (x->pool);
200 get_default_box_char (size_t idx)
202 /* Disassemble IDX into components. */
203 unsigned top = (idx >> LNS_TOP) & 3;
204 unsigned left = (idx >> LNS_LEFT) & 3;
205 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
206 unsigned right = (idx >> LNS_RIGHT) & 3;
208 /* Reassemble components into nibbles in the order TLBR.
209 This makes it easy to read the case labels. */
210 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
216 case 0x0100: case 0x0101: case 0x0001:
219 case 0x1000: case 0x1010: case 0x0010:
222 case 0x0300: case 0x0303: case 0x0003:
223 case 0x0200: case 0x0202: case 0x0002:
227 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
232 ascii_close_driver (struct outp_driver *this)
234 struct ascii_driver_ext *x = this->ext;
237 pool_detach_file (x->pool, x->file);
238 pool_destroy (x->pool);
243 /* Generic option types. */
253 static const struct outp_option option_tab[] =
255 {"headers", boolean_arg, 0},
256 {"paginate", boolean_arg, 1},
257 {"squeeze", boolean_arg, 2},
259 {"emphasis", emphasis_arg, 0},
261 {"length", pos_int_arg, 0},
262 {"width", pos_int_arg, 1},
264 {"top-margin", nonneg_int_arg, 0},
265 {"bottom-margin", nonneg_int_arg, 1},
266 {"tab-width", nonneg_int_arg, 2},
268 {"output-file", string_arg, 0},
269 {"chart-files", string_arg, 1},
270 {"chart-type", string_arg, 2},
271 {"init", string_arg, 3},
277 handle_option (struct outp_driver *this, const char *key,
278 const struct string *val)
280 struct ascii_driver_ext *x = this->ext;
284 value = ds_cstr (val);
285 if (!strncmp (key, "box[", 4))
288 int indx = strtol (&key[4], &tail, 4);
289 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
291 error (0, 0, _("ascii: bad index value for `box' key: syntax "
292 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
293 "expressed in base 4"),
297 if (x->box[indx] != NULL)
298 error (0, 0, _("ascii: multiple values for %s"), key);
299 x->box[indx] = pool_strdup (x->pool, value);
303 switch (outp_match_keyword (key, option_tab, &subcat))
306 error (0, 0, _("ascii: unknown parameter `%s'"), key);
314 arg = strtol (value, &tail, 0);
315 if (arg < 1 || errno == ERANGE || *tail)
317 error (0, 0, _("ascii: positive integer required as `%s' value"),
324 x->page_length = arg;
335 if (!strcmp (value, "bold"))
336 x->emphasis = EMPH_BOLD;
337 else if (!strcmp (value, "underline"))
338 x->emphasis = EMPH_UNDERLINE;
339 else if (!strcmp (value, "none"))
340 x->emphasis = EMPH_NONE;
343 _("ascii: `emphasis' value must be `bold', "
344 "`underline', or `none'"));
352 arg = strtol (value, &tail, 0);
353 if (arg < 0 || errno == ERANGE || *tail)
356 _("ascii: zero or positive integer required as `%s' value"),
366 x->bottom_margin = arg;
379 if (!strcmp (value, "on") || !strcmp (value, "true")
380 || !strcmp (value, "yes") || atoi (value))
382 else if (!strcmp (value, "off") || !strcmp (value, "false")
383 || !strcmp (value, "no") || !strcmp (value, "0"))
387 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
393 x->headers = setting;
396 x->paginate = setting;
399 x->squeeze_blank_lines = setting;
410 x->file_name = pool_strdup (x->pool, value);
413 if (ds_find_char (val, '#') != SIZE_MAX)
414 x->chart_file_name = pool_strdup (x->pool, value);
416 error (0, 0, _("`chart-files' value must contain `#'"));
419 if (value[0] != '\0')
420 x->chart_type = pool_strdup (x->pool, value);
422 x->chart_type = NULL;
425 x->init = pool_strdup (x->pool, value);
437 ascii_open_page (struct outp_driver *this)
439 struct ascii_driver_ext *x = this->ext;
444 x->file = fn_open (x->file_name, "w");
447 error (0, errno, _("ascii: opening output file \"%s\""),
451 pool_attach_file (x->pool, x->file);
454 fputs (x->init, x->file);
459 if (this->length > x->line_cap)
461 x->lines = pool_nrealloc (x->pool,
462 x->lines, this->length, sizeof *x->lines);
463 for (i = x->line_cap; i < this->length; i++)
465 struct line *line = &x->lines[i];
469 x->line_cap = this->length;
472 for (i = 0; i < this->length; i++)
473 x->lines[i].char_cnt = 0;
476 /* Ensures that at least the first LENGTH characters of line Y in
477 THIS driver identified X have been cleared out. */
479 expand_line (struct outp_driver *this, int y, int length)
481 struct ascii_driver_ext *ext = this->ext;
482 struct line *line = &ext->lines[y];
483 if (line->char_cnt < length)
486 if (line->char_cap < length)
488 line->char_cap = MIN (length * 2, this->width);
489 line->chars = pool_nrealloc (ext->pool,
491 line->char_cap, sizeof *line->chars);
493 for (x = line->char_cnt; x < length; x++)
494 line->chars[x] = ' ';
495 line->char_cnt = length;
500 ascii_line (struct outp_driver *this,
501 int x0, int y0, int x1, int y1,
502 enum outp_line_style top, enum outp_line_style left,
503 enum outp_line_style bottom, enum outp_line_style right)
505 struct ascii_driver_ext *ext = this->ext;
507 unsigned short value;
509 assert (this->page_open);
511 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
513 #if !SUPPRESS_WARNINGS
514 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
515 x0, y0, x1, y1, this->width, this->length);
521 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
522 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
523 for (y = y0; y < y1; y++)
527 expand_line (this, y, x1);
528 for (x = x0; x < x1; x++)
529 ext->lines[y].chars[x] = value;
534 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
536 extern struct som_table_class tab_table_class;
538 assert (s->class == &tab_table_class);
539 assert (s->type == SOM_CHART);
543 text_draw (struct outp_driver *this,
546 enum outp_justification justification, int width,
547 const char *string, size_t length)
549 struct ascii_driver_ext *ext = this->ext;
550 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
554 switch (justification)
559 x += (width - length + 1) / 2;
568 if (y >= this->length || x >= this->width)
571 if (x + length > this->width)
572 length = this->width - x;
574 line_len = x + length;
576 expand_line (this, y, line_len);
578 ext->lines[y].chars[x++] = *string++ | attr;
581 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
582 to the maximum width of a line and *HEIGHT to the number of
583 lines, if those arguments are non-null. Actually draws the
584 text if DRAW is true. */
586 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
587 int *width, int *height)
592 const char *cp = ss_data (text->string);
595 height_left = text->v;
597 while (height_left > 0)
603 /* Initially the line is up to text->h characters long. */
604 chars_left = ss_end (text->string) - cp;
607 line_len = MIN (chars_left, text->h);
609 /* A new-line terminates the line prematurely. */
610 end = memchr (cp, '\n', line_len);
614 /* Don't cut off words if it can be avoided. */
615 if (cp + line_len < ss_end (text->string))
617 size_t space_len = line_len;
618 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
621 line_len = space_len;
628 text->x, text->y + (text->v - height_left),
629 text->justification, text->h,
634 if (line_len > max_width)
635 max_width = line_len;
639 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
646 *height = text->v - height_left;
650 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
651 int *width, int *height)
653 delineate (this, t, false, width, height);
657 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
659 assert (this->page_open);
660 delineate (this, t, true, NULL, NULL);
663 /* ascii_close_page () and support routines. */
665 /* Writes the LENGTH characters in S to OUT. */
667 output_line (struct outp_driver *this, const struct line *line,
670 struct ascii_driver_ext *ext = this->ext;
671 const unsigned short *s = line->chars;
674 for (length = line->char_cnt; length-- > 0; s++)
676 ds_put_cstr (out, ext->box[*s & 0xff]);
679 if (*s & ATTR_EMPHASIS)
681 if (ext->emphasis == EMPH_BOLD)
683 ds_put_char (out, *s);
684 ds_put_char (out, '\b');
686 else if (ext->emphasis == EMPH_UNDERLINE)
687 ds_put_cstr (out, "_\b");
689 ds_put_char (out, *s);
694 append_lr_justified (struct string *out, int width,
695 const char *left, const char *right)
697 ds_put_char_multiple (out, ' ', width);
700 size_t length = MIN (strlen (left), width);
701 memcpy (ds_end (out) - width, left, length);
705 size_t length = MIN (strlen (right), width);
706 memcpy (ds_end (out) - length, right, length);
708 ds_put_char (out, '\n');
712 dump_output (struct outp_driver *this, struct string *out)
714 struct ascii_driver_ext *x = this->ext;
715 fwrite (ds_data (out), ds_length (out), 1, x->file);
720 ascii_close_page (struct outp_driver *this)
722 struct ascii_driver_ext *x = this->ext;
729 ds_init_empty (&out);
731 ds_put_char_multiple (&out, '\n', x->top_margin);
736 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
737 r2 = xasprintf ("%s - %s" , version, host_system);
739 append_lr_justified (&out, this->width, outp_title, r1);
740 append_lr_justified (&out, this->width, outp_subtitle, r2);
741 ds_put_char (&out, '\n');
746 dump_output (this, &out);
748 for (line_num = 0; line_num < this->length; line_num++)
751 /* Squeeze multiple blank lines into a single blank line if
753 if (x->squeeze_blank_lines)
755 if (line_num >= x->line_cap)
758 && x->lines[line_num].char_cnt == 0
759 && x->lines[line_num - 1].char_cnt == 0)
763 if (line_num < x->line_cap)
764 output_line (this, &x->lines[line_num], &out);
765 ds_put_char (&out, '\n');
766 dump_output (this, &out);
769 ds_put_char_multiple (&out, '\n', x->bottom_margin);
771 ds_put_char (&out, '\f');
773 dump_output (this, &out);
777 /* Flushes all output to the user and lets the user deal with it.
778 This is applied only to output drivers that are designated as
779 "screen" drivers that the user is interacting with in real
782 ascii_flush (struct outp_driver *this)
784 struct ascii_driver_ext *x = this->ext;
788 if (fn_close (x->file_name, x->file) != 0)
789 error (0, errno, _("ascii: closing output file \"%s\""),
791 pool_detach_file (x->pool, x->file);
797 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
799 struct ascii_driver_ext *x = this->ext;
803 if (x->chart_type == NULL)
806 /* Initialize chart. */
807 chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
808 if (ch->file_name == NULL)
811 /* Mention chart in output.
812 First advance current position. */
813 if (!this->page_open)
814 outp_open_page (this);
818 if (this->cp_y >= this->length)
820 outp_close_page (this);
821 outp_open_page (this);
825 /* Then write the text. */
826 text = xasprintf ("See %s for a chart.", ch->file_name);
828 t.justification = OUTP_LEFT;
829 t.string = ss_cstr (text);
834 ascii_text_draw (this, &t);
841 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
843 struct ascii_driver_ext *x = this->ext;
844 if (x->chart_type != NULL)
845 chart_finalise_separate (ch);
848 const struct outp_class ascii_class =
866 ascii_chart_initialise,