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 append=no|yes If output-file exists, append to it?
45 chart-files="pspp-#.png" Name used for charts.
46 chart-type=png Format of charts (use "none" to disable).
48 paginate=on|off Formfeeds are desired?
49 tab-width=8 Width of a tab; 0 to not use tabs.
51 headers=on|off Put headers at top of page?
52 emphasis=bold|underline|none Style to use for emphasis.
55 squeeze=off|on Squeeze multiple newlines into exactly one.
60 box[x]="strng" Sets box character X (X in base 4: 0-3333).
61 init="string" Set initialization string.
64 /* Disable messages by failed range checks. */
65 /*#define SUPPRESS_WARNINGS 1 */
67 /* Line styles bit shifts. */
78 /* Character attributes. */
79 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
80 #define ATTR_BOX 0x200 /* Line drawing character. */
85 unsigned short *chars; /* Characters and attributes. */
86 int char_cnt; /* Length. */
87 int char_cap; /* Allocated bytes. */
90 /* How to emphasize text. */
93 EMPH_BOLD, /* Overstrike for bold. */
94 EMPH_UNDERLINE, /* Overstrike for underlining. */
95 EMPH_NONE /* No emphasis. */
98 /* ASCII output driver extension record. */
99 struct ascii_driver_ext
103 /* User parameters. */
104 bool append; /* Append if output-file already exists? */
105 bool headers; /* Print headers at top of page? */
106 bool paginate; /* Insert formfeeds? */
107 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
108 enum emphasis_style emphasis; /* How to emphasize text. */
109 int tab_width; /* Width of a tab; 0 not to use tabs. */
110 const char *chart_type; /* Type of charts to output; NULL for none. */
111 const char *chart_file_name; /* Name of files used for charts. */
113 int page_length; /* Page length before subtracting margins. */
114 int top_margin; /* Top margin in lines. */
115 int bottom_margin; /* Bottom margin in lines. */
117 char *box[LNS_COUNT]; /* Line & box drawing characters. */
118 char *init; /* Device initialization string. */
120 /* Internal state. */
121 char *file_name; /* Output file name. */
122 FILE *file; /* Output file. */
123 int page_number; /* Current page number. */
124 struct line *lines; /* Page content. */
125 int line_cap; /* Number of lines allocated. */
126 int chart_cnt; /* Number of charts so far. */
129 static void ascii_flush (struct outp_driver *);
130 static int get_default_box_char (size_t idx);
131 static bool handle_option (struct outp_driver *this, const char *key,
132 const struct string *val);
135 ascii_open_driver (struct outp_driver *this, struct substring options)
137 struct ascii_driver_ext *x;
141 this->font_height = 1;
142 this->prop_em_width = 1;
143 this->fixed_width = 1;
144 for (i = 0; i < OUTP_L_COUNT; i++)
145 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
147 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
151 x->squeeze_blank_lines = false;
152 x->emphasis = EMPH_BOLD;
154 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
155 x->chart_type = pool_strdup (x->pool, "png");
158 x->bottom_margin = 2;
159 for (i = 0; i < LNS_COUNT; i++)
162 x->file_name = pool_strdup (x->pool, "pspp.list");
169 if (!outp_parse_options (options, handle_option, this))
172 this->length = x->page_length - x->top_margin - x->bottom_margin - 1;
176 if (this->width < 59 || this->length < 15)
179 _("ascii: page excluding margins and headers "
180 "must be at least 59 characters wide by 15 lines long, but as "
181 "configured is only %d characters by %d lines"),
182 this->width, this->length);
186 for (i = 0; i < LNS_COUNT; i++)
187 if (x->box[i] == NULL)
190 s[0] = get_default_box_char (i);
192 x->box[i] = pool_strdup (x->pool, s);
198 pool_destroy (x->pool);
203 get_default_box_char (size_t idx)
205 /* Disassemble IDX into components. */
206 unsigned top = (idx >> LNS_TOP) & 3;
207 unsigned left = (idx >> LNS_LEFT) & 3;
208 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
209 unsigned right = (idx >> LNS_RIGHT) & 3;
211 /* Reassemble components into nibbles in the order TLBR.
212 This makes it easy to read the case labels. */
213 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
219 case 0x0100: case 0x0101: case 0x0001:
222 case 0x1000: case 0x1010: case 0x0010:
225 case 0x0300: case 0x0303: case 0x0003:
226 case 0x0200: case 0x0202: case 0x0002:
230 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
235 ascii_close_driver (struct outp_driver *this)
237 struct ascii_driver_ext *x = this->ext;
240 pool_detach_file (x->pool, x->file);
241 pool_destroy (x->pool);
246 /* Generic option types. */
256 static const struct outp_option option_tab[] =
258 {"headers", boolean_arg, 0},
259 {"paginate", boolean_arg, 1},
260 {"squeeze", boolean_arg, 2},
261 {"append", boolean_arg, 3},
263 {"emphasis", emphasis_arg, 0},
265 {"length", pos_int_arg, 0},
266 {"width", pos_int_arg, 1},
268 {"top-margin", nonneg_int_arg, 0},
269 {"bottom-margin", nonneg_int_arg, 1},
270 {"tab-width", nonneg_int_arg, 2},
272 {"output-file", string_arg, 0},
273 {"chart-files", string_arg, 1},
274 {"chart-type", string_arg, 2},
275 {"init", string_arg, 3},
281 handle_option (struct outp_driver *this, const char *key,
282 const struct string *val)
284 struct ascii_driver_ext *x = this->ext;
288 value = ds_cstr (val);
289 if (!strncmp (key, "box[", 4))
292 int indx = strtol (&key[4], &tail, 4);
293 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
295 error (0, 0, _("ascii: bad index value for `box' key: syntax "
296 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
297 "expressed in base 4"),
301 if (x->box[indx] != NULL)
302 error (0, 0, _("ascii: multiple values for %s"), key);
303 x->box[indx] = pool_strdup (x->pool, value);
307 switch (outp_match_keyword (key, option_tab, &subcat))
310 error (0, 0, _("ascii: unknown parameter `%s'"), key);
318 arg = strtol (value, &tail, 0);
319 if (arg < 1 || errno == ERANGE || *tail)
321 error (0, 0, _("ascii: positive integer required as `%s' value"),
328 x->page_length = arg;
339 if (!strcmp (value, "bold"))
340 x->emphasis = EMPH_BOLD;
341 else if (!strcmp (value, "underline"))
342 x->emphasis = EMPH_UNDERLINE;
343 else if (!strcmp (value, "none"))
344 x->emphasis = EMPH_NONE;
347 _("ascii: `emphasis' value must be `bold', "
348 "`underline', or `none'"));
356 arg = strtol (value, &tail, 0);
357 if (arg < 0 || errno == ERANGE || *tail)
360 _("ascii: zero or positive integer required as `%s' value"),
370 x->bottom_margin = arg;
383 if (!strcmp (value, "on") || !strcmp (value, "true")
384 || !strcmp (value, "yes") || atoi (value))
386 else if (!strcmp (value, "off") || !strcmp (value, "false")
387 || !strcmp (value, "no") || !strcmp (value, "0"))
391 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
397 x->headers = setting;
400 x->paginate = setting;
403 x->squeeze_blank_lines = setting;
417 x->file_name = pool_strdup (x->pool, value);
420 if (ds_find_char (val, '#') != SIZE_MAX)
421 x->chart_file_name = pool_strdup (x->pool, value);
423 error (0, 0, _("`chart-files' value must contain `#'"));
426 if (value[0] != '\0')
427 x->chart_type = pool_strdup (x->pool, value);
429 x->chart_type = NULL;
432 x->init = pool_strdup (x->pool, value);
444 ascii_open_page (struct outp_driver *this)
446 struct ascii_driver_ext *x = this->ext;
451 x->file = fn_open (x->file_name, x->append ? "a" : "w");
454 error (0, errno, _("ascii: opening output file \"%s\""),
458 pool_attach_file (x->pool, x->file);
461 fputs (x->init, x->file);
466 if (this->length > x->line_cap)
468 x->lines = pool_nrealloc (x->pool,
469 x->lines, this->length, sizeof *x->lines);
470 for (i = x->line_cap; i < this->length; i++)
472 struct line *line = &x->lines[i];
476 x->line_cap = this->length;
479 for (i = 0; i < this->length; i++)
480 x->lines[i].char_cnt = 0;
483 /* Ensures that at least the first LENGTH characters of line Y in
484 THIS driver identified X have been cleared out. */
486 expand_line (struct outp_driver *this, int y, int length)
488 struct ascii_driver_ext *ext = this->ext;
489 struct line *line = &ext->lines[y];
490 if (line->char_cnt < length)
493 if (line->char_cap < length)
495 line->char_cap = MIN (length * 2, this->width);
496 line->chars = pool_nrealloc (ext->pool,
498 line->char_cap, sizeof *line->chars);
500 for (x = line->char_cnt; x < length; x++)
501 line->chars[x] = ' ';
502 line->char_cnt = length;
507 ascii_line (struct outp_driver *this,
508 int x0, int y0, int x1, int y1,
509 enum outp_line_style top, enum outp_line_style left,
510 enum outp_line_style bottom, enum outp_line_style right)
512 struct ascii_driver_ext *ext = this->ext;
514 unsigned short value;
516 assert (this->page_open);
518 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
520 #if !SUPPRESS_WARNINGS
521 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
522 x0, y0, x1, y1, this->width, this->length);
528 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
529 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
530 for (y = y0; y < y1; y++)
534 expand_line (this, y, x1);
535 for (x = x0; x < x1; x++)
536 ext->lines[y].chars[x] = value;
541 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
543 extern struct som_table_class tab_table_class;
545 assert (s->class == &tab_table_class);
546 assert (s->type == SOM_CHART);
550 text_draw (struct outp_driver *this,
553 enum outp_justification justification, int width,
554 const char *string, size_t length)
556 struct ascii_driver_ext *ext = this->ext;
557 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
561 switch (justification)
566 x += (width - length + 1) / 2;
575 if (y >= this->length || x >= this->width)
578 if (x + length > this->width)
579 length = this->width - x;
581 line_len = x + length;
583 expand_line (this, y, line_len);
585 ext->lines[y].chars[x++] = *string++ | attr;
588 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
589 to the maximum width of a line and *HEIGHT to the number of
590 lines, if those arguments are non-null. Actually draws the
591 text if DRAW is true. */
593 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
594 int *width, int *height)
599 const char *cp = ss_data (text->string);
602 height_left = text->v;
604 while (height_left > 0)
610 /* Initially the line is up to text->h characters long. */
611 chars_left = ss_end (text->string) - cp;
614 line_len = MIN (chars_left, text->h);
616 /* A new-line terminates the line prematurely. */
617 end = memchr (cp, '\n', line_len);
621 /* Don't cut off words if it can be avoided. */
622 if (cp + line_len < ss_end (text->string))
624 size_t space_len = line_len;
625 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
628 line_len = space_len;
635 text->x, text->y + (text->v - height_left),
636 text->justification, text->h,
641 if (line_len > max_width)
642 max_width = line_len;
646 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
653 *height = text->v - height_left;
657 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
658 int *width, int *height)
660 delineate (this, t, false, width, height);
664 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
666 assert (this->page_open);
667 delineate (this, t, true, NULL, NULL);
670 /* ascii_close_page () and support routines. */
672 /* Writes the LENGTH characters in S to OUT. */
674 output_line (struct outp_driver *this, const struct line *line,
677 struct ascii_driver_ext *ext = this->ext;
678 const unsigned short *s = line->chars;
681 for (length = line->char_cnt; length-- > 0; s++)
683 ds_put_cstr (out, ext->box[*s & 0xff]);
686 if (*s & ATTR_EMPHASIS)
688 if (ext->emphasis == EMPH_BOLD)
690 ds_put_char (out, *s);
691 ds_put_char (out, '\b');
693 else if (ext->emphasis == EMPH_UNDERLINE)
694 ds_put_cstr (out, "_\b");
696 ds_put_char (out, *s);
701 append_lr_justified (struct string *out, int width,
702 const char *left, const char *right)
704 ds_put_char_multiple (out, ' ', width);
707 size_t length = MIN (strlen (left), width);
708 memcpy (ds_end (out) - width, left, length);
712 size_t length = MIN (strlen (right), width);
713 memcpy (ds_end (out) - length, right, length);
715 ds_put_char (out, '\n');
719 dump_output (struct outp_driver *this, struct string *out)
721 struct ascii_driver_ext *x = this->ext;
722 fwrite (ds_data (out), ds_length (out), 1, x->file);
727 ascii_close_page (struct outp_driver *this)
729 struct ascii_driver_ext *x = this->ext;
736 ds_init_empty (&out);
738 ds_put_char_multiple (&out, '\n', x->top_margin);
743 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
744 r2 = xasprintf ("%s - %s" , version, host_system);
746 append_lr_justified (&out, this->width, outp_title, r1);
747 append_lr_justified (&out, this->width, outp_subtitle, r2);
748 ds_put_char (&out, '\n');
753 dump_output (this, &out);
755 for (line_num = 0; line_num < this->length; line_num++)
758 /* Squeeze multiple blank lines into a single blank line if
760 if (x->squeeze_blank_lines)
762 if (line_num >= x->line_cap)
765 && x->lines[line_num].char_cnt == 0
766 && x->lines[line_num - 1].char_cnt == 0)
770 if (line_num < x->line_cap)
771 output_line (this, &x->lines[line_num], &out);
772 ds_put_char (&out, '\n');
773 dump_output (this, &out);
776 ds_put_char_multiple (&out, '\n', x->bottom_margin);
778 ds_put_char (&out, '\f');
780 dump_output (this, &out);
784 /* Flushes all output to the user and lets the user deal with it.
785 This is applied only to output drivers that are designated as
786 "screen" drivers that the user is interacting with in real
789 ascii_flush (struct outp_driver *this)
791 struct ascii_driver_ext *x = this->ext;
794 if (fn_close (x->file_name, x->file) != 0)
795 error (0, errno, _("ascii: closing output file \"%s\""),
797 pool_detach_file (x->pool, x->file);
803 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
805 struct ascii_driver_ext *x = this->ext;
809 if (x->chart_type == NULL)
812 /* Initialize chart. */
813 chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
814 if (ch->file_name == NULL)
817 /* Mention chart in output.
818 First advance current position. */
819 if (!this->page_open)
820 outp_open_page (this);
824 if (this->cp_y >= this->length)
826 outp_close_page (this);
827 outp_open_page (this);
831 /* Then write the text. */
832 text = xasprintf ("See %s for a chart.", ch->file_name);
834 t.justification = OUTP_LEFT;
835 t.string = ss_cstr (text);
840 ascii_text_draw (this, &t);
847 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
849 struct ascii_driver_ext *x = this->ext;
850 if (x->chart_type != NULL)
851 chart_finalise_separate (ch);
854 const struct outp_class ascii_class =
872 ascii_chart_initialise,