1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009 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 <data/settings.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>
32 #include <output/chart-provider.h>
33 #include <output/chart.h>
34 #include <output/output.h>
41 #define _(msgid) gettext (msgid)
43 /* ASCII driver options: (defaults listed first)
45 output-file="pspp.list"
46 append=no|yes If output-file exists, append to it?
47 chart-files="pspp-#.png" Name used for charts.
48 chart-type=png Format of charts (use "none" to disable).
50 paginate=on|off Formfeeds are desired?
51 tab-width=8 Width of a tab; 0 to not use tabs.
53 headers=on|off Put headers at top of page?
54 emphasis=bold|underline|none Style to use for emphasis.
57 squeeze=off|on Squeeze multiple newlines into exactly one.
62 box[x]="strng" Sets box character X (X in base 4: 0-3333).
63 init="string" Set initialization string.
66 /* Disable messages by failed range checks. */
67 /*#define SUPPRESS_WARNINGS 1 */
69 /* Line styles bit shifts. */
80 /* Character attributes. */
81 #define ATTR_EMPHASIS 0x100 /* Bold-face. */
82 #define ATTR_BOX 0x200 /* Line drawing character. */
87 unsigned short *chars; /* Characters and attributes. */
88 int char_cnt; /* Length. */
89 int char_cap; /* Allocated bytes. */
92 /* How to emphasize text. */
95 EMPH_BOLD, /* Overstrike for bold. */
96 EMPH_UNDERLINE, /* Overstrike for underlining. */
97 EMPH_NONE /* No emphasis. */
100 /* ASCII output driver extension record. */
101 struct ascii_driver_ext
105 /* User parameters. */
106 bool append; /* Append if output-file already exists? */
107 bool headers; /* Print headers at top of page? */
108 bool paginate; /* Insert formfeeds? */
109 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
110 enum emphasis_style emphasis; /* How to emphasize text. */
111 int tab_width; /* Width of a tab; 0 not to use tabs. */
112 const char *chart_type; /* Type of charts to output; NULL for none. */
113 const char *chart_file_name; /* Name of files used for charts. */
115 bool auto_width; /* Use viewwidth as page width? */
116 bool auto_length; /* Use viewlength as page width? */
117 int page_length; /* Page length before subtracting margins. */
118 int top_margin; /* Top margin in lines. */
119 int bottom_margin; /* Bottom margin in lines. */
121 char *box[LNS_COUNT]; /* Line & box drawing characters. */
122 char *init; /* Device initialization string. */
124 /* Internal state. */
125 char *file_name; /* Output file name. */
126 FILE *file; /* Output file. */
127 bool reported_error; /* Reported file open error? */
128 int page_number; /* Current page number. */
129 struct line *lines; /* Page content. */
130 int line_cap; /* Number of lines allocated. */
131 int chart_cnt; /* Number of charts so far. */
134 static void ascii_flush (struct outp_driver *);
135 static int get_default_box_char (size_t idx);
136 static bool update_page_size (struct outp_driver *, bool issue_error);
137 static bool handle_option (void *this, const char *key,
138 const struct string *val);
141 ascii_open_driver (const char *name, int types, struct substring options)
143 struct outp_driver *this;
144 struct ascii_driver_ext *x;
147 this = outp_allocate_driver (&ascii_class, name, types);
149 this->font_height = 1;
150 this->prop_em_width = 1;
151 this->fixed_width = 1;
152 for (i = 0; i < OUTP_L_COUNT; i++)
153 this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
155 this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
159 x->squeeze_blank_lines = false;
160 x->emphasis = EMPH_BOLD;
162 x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
163 x->chart_type = pool_strdup (x->pool, "png");
164 x->auto_width = false;
165 x->auto_length = false;
168 x->bottom_margin = 2;
169 for (i = 0; i < LNS_COUNT; i++)
172 x->file_name = pool_strdup (x->pool, "pspp.list");
174 x->reported_error = false;
180 if (!outp_parse_options (this->name, options, handle_option, this))
183 if (!update_page_size (this, true))
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);
195 outp_register_driver (this);
200 pool_destroy (x->pool);
201 outp_free_driver (this);
206 get_default_box_char (size_t idx)
208 /* Disassemble IDX into components. */
209 unsigned top = (idx >> LNS_TOP) & 3;
210 unsigned left = (idx >> LNS_LEFT) & 3;
211 unsigned bottom = (idx >> LNS_BOTTOM) & 3;
212 unsigned right = (idx >> LNS_RIGHT) & 3;
214 /* Reassemble components into nibbles in the order TLBR.
215 This makes it easy to read the case labels. */
216 unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
222 case 0x0100: case 0x0101: case 0x0001:
225 case 0x1000: case 0x1010: case 0x0010:
228 case 0x0300: case 0x0303: case 0x0003:
229 case 0x0200: case 0x0202: case 0x0002:
233 return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
237 /* Re-calculates the page width and length based on settings,
238 margins, and, if "auto" is set, the size of the user's
239 terminal window or GUI output window. */
241 update_page_size (struct outp_driver *this, bool issue_error)
243 struct ascii_driver_ext *x = this->ext;
244 int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0);
247 this->width = settings_get_viewwidth ();
249 x->page_length = settings_get_viewlength ();
251 this->length = x->page_length - margins;
253 if (this->width < 59 || this->length < 15)
257 _("ascii: page excluding margins and headers "
258 "must be at least 59 characters wide by 15 lines long, but "
259 "as configured is only %d characters by %d lines"),
260 this->width, this->length);
261 if (this->width < 59)
263 if (this->length < 15)
266 x->page_length = this->length + margins;
275 ascii_close_driver (struct outp_driver *this)
277 struct ascii_driver_ext *x = this->ext;
280 pool_detach_file (x->pool, x->file);
281 pool_destroy (x->pool);
286 /* Generic option types. */
296 static const struct outp_option option_tab[] =
298 {"headers", boolean_arg, 0},
299 {"paginate", boolean_arg, 1},
300 {"squeeze", boolean_arg, 2},
301 {"append", boolean_arg, 3},
303 {"emphasis", emphasis_arg, 0},
305 {"length", page_size_arg, 0},
306 {"width", page_size_arg, 1},
308 {"top-margin", nonneg_int_arg, 0},
309 {"bottom-margin", nonneg_int_arg, 1},
310 {"tab-width", nonneg_int_arg, 2},
312 {"output-file", string_arg, 0},
313 {"chart-files", string_arg, 1},
314 {"chart-type", string_arg, 2},
315 {"init", string_arg, 3},
321 handle_option (void *this_, const char *key,
322 const struct string *val)
324 struct outp_driver *this = this_;
325 struct ascii_driver_ext *x = this->ext;
329 value = ds_cstr (val);
330 if (!strncmp (key, "box[", 4))
333 int indx = strtol (&key[4], &tail, 4);
334 if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
336 error (0, 0, _("ascii: bad index value for `box' key: syntax "
337 "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
338 "expressed in base 4"),
342 if (x->box[indx] != NULL)
343 error (0, 0, _("ascii: multiple values for %s"), key);
344 x->box[indx] = pool_strdup (x->pool, value);
348 switch (outp_match_keyword (key, option_tab, &subcat))
351 error (0, 0, _("ascii: unknown parameter `%s'"), key);
358 if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
360 if (!(this->device & OUTP_DEV_SCREEN))
362 /* We only let `screen' devices have `auto'
363 length or width because output to such devices
364 is flushed before each new command. Resizing
365 a device in the middle of output seems like a
367 error (0, 0, _("ascii: only screen devices may have `auto' "
370 else if (subcat == 0)
371 x->auto_length = true;
373 x->auto_width = true;
378 arg = strtol (value, &tail, 0);
379 if (arg < 1 || errno == ERANGE || *tail)
381 error (0, 0, _("ascii: positive integer required as "
389 x->page_length = arg;
401 if (!strcmp (value, "bold"))
402 x->emphasis = EMPH_BOLD;
403 else if (!strcmp (value, "underline"))
404 x->emphasis = EMPH_UNDERLINE;
405 else if (!strcmp (value, "none"))
406 x->emphasis = EMPH_NONE;
409 _("ascii: `emphasis' value must be `bold', "
410 "`underline', or `none'"));
418 arg = strtol (value, &tail, 0);
419 if (arg < 0 || errno == ERANGE || *tail)
422 _("ascii: zero or positive integer required as `%s' value"),
432 x->bottom_margin = arg;
445 if (!strcmp (value, "on") || !strcmp (value, "true")
446 || !strcmp (value, "yes") || atoi (value))
448 else if (!strcmp (value, "off") || !strcmp (value, "false")
449 || !strcmp (value, "no") || !strcmp (value, "0"))
453 error (0, 0, _("ascii: boolean value expected for `%s'"), key);
459 x->headers = setting;
462 x->paginate = setting;
465 x->squeeze_blank_lines = setting;
479 x->file_name = pool_strdup (x->pool, value);
482 if (ds_find_char (val, '#') != SIZE_MAX)
483 x->chart_file_name = pool_strdup (x->pool, value);
485 error (0, 0, _("`chart-files' value must contain `#'"));
488 if (value[0] != '\0')
489 x->chart_type = pool_strdup (x->pool, value);
491 x->chart_type = NULL;
494 x->init = pool_strdup (x->pool, value);
506 ascii_open_page (struct outp_driver *this)
508 struct ascii_driver_ext *x = this->ext;
511 update_page_size (this, false);
515 x->file = fn_open (x->file_name, x->append ? "a" : "w");
518 pool_attach_file (x->pool, x->file);
520 fputs (x->init, x->file);
524 /* Report the error to the user and complete
525 initialization. If we do not finish initialization,
526 then calls to other driver functions will segfault
527 later. It would be better to simply drop the driver
528 entirely, but we do not have a convenient mechanism
530 if (!x->reported_error)
531 error (0, errno, _("ascii: opening output file \"%s\""),
533 x->reported_error = true;
539 if (this->length > x->line_cap)
541 x->lines = pool_nrealloc (x->pool,
542 x->lines, this->length, sizeof *x->lines);
543 for (i = x->line_cap; i < this->length; i++)
545 struct line *line = &x->lines[i];
549 x->line_cap = this->length;
552 for (i = 0; i < this->length; i++)
553 x->lines[i].char_cnt = 0;
556 /* Ensures that at least the first LENGTH characters of line Y in
557 THIS driver identified X have been cleared out. */
559 expand_line (struct outp_driver *this, int y, int length)
561 struct ascii_driver_ext *ext = this->ext;
562 struct line *line = &ext->lines[y];
563 if (line->char_cnt < length)
566 if (line->char_cap < length)
568 line->char_cap = MIN (length * 2, this->width);
569 line->chars = pool_nrealloc (ext->pool,
571 line->char_cap, sizeof *line->chars);
573 for (x = line->char_cnt; x < length; x++)
574 line->chars[x] = ' ';
575 line->char_cnt = length;
580 ascii_line (struct outp_driver *this,
581 int x0, int y0, int x1, int y1,
582 enum outp_line_style top, enum outp_line_style left,
583 enum outp_line_style bottom, enum outp_line_style right)
585 struct ascii_driver_ext *ext = this->ext;
587 unsigned short value;
589 assert (this->page_open);
591 if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
593 #if !SUPPRESS_WARNINGS
594 printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
595 x0, y0, x1, y1, this->width, this->length);
601 value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
602 | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
603 for (y = y0; y < y1; y++)
607 expand_line (this, y, x1);
608 for (x = x0; x < x1; x++)
609 ext->lines[y].chars[x] = value;
614 text_draw (struct outp_driver *this,
617 enum outp_justification justification, int width,
618 const char *string, size_t length)
620 struct ascii_driver_ext *ext = this->ext;
621 unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
625 switch (justification)
630 x += (width - length + 1) / 2;
639 if (y >= this->length || x >= this->width)
642 if (x + length > this->width)
643 length = this->width - x;
645 line_len = x + length;
647 expand_line (this, y, line_len);
649 ext->lines[y].chars[x++] = *string++ | attr;
652 /* Divides the text T->S into lines of width T->H. Sets *WIDTH
653 to the maximum width of a line and *HEIGHT to the number of
654 lines, if those arguments are non-null. Actually draws the
655 text if DRAW is true. */
657 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
658 int *width, int *height)
663 const char *cp = ss_data (text->string);
666 height_left = text->v;
668 while (height_left > 0)
674 /* Initially the line is up to text->h characters long. */
675 chars_left = ss_end (text->string) - cp;
678 line_len = MIN (chars_left, text->h);
680 /* A new-line terminates the line prematurely. */
681 end = memchr (cp, '\n', line_len);
685 /* Don't cut off words if it can be avoided. */
686 if (cp + line_len < ss_end (text->string))
688 size_t space_len = line_len;
689 while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
692 line_len = space_len;
699 text->x, text->y + (text->v - height_left),
700 text->justification, text->h,
705 if (line_len > max_width)
706 max_width = line_len;
710 if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
717 *height = text->v - height_left;
721 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
722 int *width, int *height)
724 delineate (this, t, false, width, height);
728 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
730 assert (this->page_open);
731 delineate (this, t, true, NULL, NULL);
734 /* ascii_close_page () and support routines. */
736 /* Writes the LENGTH characters in S to OUT. */
738 output_line (struct outp_driver *this, const struct line *line,
741 struct ascii_driver_ext *ext = this->ext;
742 const unsigned short *s = line->chars;
745 for (length = line->char_cnt; length-- > 0; s++)
747 ds_put_cstr (out, ext->box[*s & 0xff]);
750 if (*s & ATTR_EMPHASIS)
752 if (ext->emphasis == EMPH_BOLD)
754 ds_put_char (out, *s);
755 ds_put_char (out, '\b');
757 else if (ext->emphasis == EMPH_UNDERLINE)
758 ds_put_cstr (out, "_\b");
760 ds_put_char (out, *s);
765 append_lr_justified (struct string *out, int width,
766 const char *left, const char *right)
768 ds_put_char_multiple (out, ' ', width);
771 size_t length = MIN (strlen (left), width);
772 memcpy (ds_end (out) - width, left, length);
776 size_t length = MIN (strlen (right), width);
777 memcpy (ds_end (out) - length, right, length);
779 ds_put_char (out, '\n');
783 dump_output (struct outp_driver *this, struct string *out)
785 struct ascii_driver_ext *x = this->ext;
786 fwrite (ds_data (out), ds_length (out), 1, x->file);
791 ascii_close_page (struct outp_driver *this)
793 struct ascii_driver_ext *x = this->ext;
800 ds_init_empty (&out);
802 ds_put_char_multiple (&out, '\n', x->top_margin);
807 r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
808 r2 = xasprintf ("%s - %s" , version, host_system);
810 append_lr_justified (&out, this->width, outp_title, r1);
811 append_lr_justified (&out, this->width, outp_subtitle, r2);
812 ds_put_char (&out, '\n');
817 dump_output (this, &out);
819 for (line_num = 0; line_num < this->length; line_num++)
822 /* Squeeze multiple blank lines into a single blank line if
824 if (x->squeeze_blank_lines)
826 if (line_num >= x->line_cap)
829 && x->lines[line_num].char_cnt == 0
830 && x->lines[line_num - 1].char_cnt == 0)
834 if (line_num < x->line_cap)
835 output_line (this, &x->lines[line_num], &out);
836 ds_put_char (&out, '\n');
837 dump_output (this, &out);
840 ds_put_char_multiple (&out, '\n', x->bottom_margin);
842 ds_put_char (&out, '\f');
844 dump_output (this, &out);
848 /* Flushes all output to the user and lets the user deal with it.
849 This is applied only to output drivers that are designated as
850 "screen" drivers that the user is interacting with in real
853 ascii_flush (struct outp_driver *this)
855 struct ascii_driver_ext *x = this->ext;
858 if (fn_close (x->file_name, x->file) != 0)
859 error (0, errno, _("ascii: closing output file \"%s\""),
861 pool_detach_file (x->pool, x->file);
867 ascii_output_chart (struct outp_driver *this, const struct chart *chart)
869 struct ascii_driver_ext *x = this->ext;
870 struct chart_geometry geom;
876 if (x->chart_type == NULL)
879 /* Draw chart in separate file. */
880 if (!chart_create_file (x->chart_type, x->chart_file_name, x->chart_cnt,
881 NULL, &file_name, &lp))
884 chart_geometry_init (lp, &geom, 1000.0, 1000.0);
885 chart_draw (chart, lp, &geom);
886 chart_geometry_free (lp);
889 /* Mention chart in output.
890 First advance current position. */
891 if (!this->page_open)
892 outp_open_page (this);
896 if (this->cp_y >= this->length)
898 outp_close_page (this);
899 outp_open_page (this);
903 /* Then write the text. */
904 text = xasprintf ("See %s for a chart.", file_name);
906 t.justification = OUTP_LEFT;
907 t.string = ss_cstr (text);
912 ascii_text_draw (this, &t);
919 const struct outp_class ascii_class =