15af515f18378e50b04a2e6ee26bc35d2552402c
[pspp-builds.git] / src / output / ascii.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24
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>
35
36 #include "error.h"
37 #include "minmax.h"
38 #include "xalloc.h"
39
40 #include "gettext.h"
41 #define _(msgid) gettext (msgid)
42
43 /* ASCII driver options: (defaults listed first)
44
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).
49
50    paginate=on|off              Formfeeds are desired?
51    tab-width=8                  Width of a tab; 0 to not use tabs.
52
53    headers=on|off               Put headers at top of page?
54    emphasis=bold|underline|none Style to use for emphasis.
55    length=66|auto
56    width=79|auto
57    squeeze=off|on               Squeeze multiple newlines into exactly one.
58
59    top-margin=2
60    bottom-margin=2
61
62    box[x]="strng"               Sets box character X (X in base 4: 0-3333).
63    init="string"                Set initialization string.
64  */
65
66 /* Disable messages by failed range checks. */
67 /*#define SUPPRESS_WARNINGS 1 */
68
69 /* Line styles bit shifts. */
70 enum
71   {
72     LNS_TOP = 0,
73     LNS_LEFT = 2,
74     LNS_BOTTOM = 4,
75     LNS_RIGHT = 6,
76
77     LNS_COUNT = 256
78   };
79
80 /* Character attributes. */
81 #define ATTR_EMPHASIS   0x100   /* Bold-face. */
82 #define ATTR_BOX        0x200   /* Line drawing character. */
83
84 /* A line of text. */
85 struct line
86   {
87     unsigned short *chars;      /* Characters and attributes. */
88     int char_cnt;               /* Length. */
89     int char_cap;               /* Allocated bytes. */
90   };
91
92 /* How to emphasize text. */
93 enum emphasis_style
94   {
95     EMPH_BOLD,                  /* Overstrike for bold. */
96     EMPH_UNDERLINE,             /* Overstrike for underlining. */
97     EMPH_NONE                   /* No emphasis. */
98   };
99
100 /* ASCII output driver extension record. */
101 struct ascii_driver_ext
102   {
103     struct pool *pool;
104
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. */
114
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. */
120
121     char *box[LNS_COUNT];       /* Line & box drawing characters. */
122     char *init;                 /* Device initialization string. */
123
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. */
132   };
133
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);
139
140 static bool
141 ascii_open_driver (const char *name, int types, struct substring options)
142 {
143   struct outp_driver *this;
144   struct ascii_driver_ext *x;
145   int i;
146
147   this = outp_allocate_driver (&ascii_class, name, types);
148   this->width = 79;
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;
154
155   this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
156   x->append = false;
157   x->headers = true;
158   x->paginate = true;
159   x->squeeze_blank_lines = false;
160   x->emphasis = EMPH_BOLD;
161   x->tab_width = 8;
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;
166   x->page_length = 66;
167   x->top_margin = 2;
168   x->bottom_margin = 2;
169   for (i = 0; i < LNS_COUNT; i++)
170     x->box[i] = NULL;
171   x->init = NULL;
172   x->file_name = pool_strdup (x->pool, "pspp.list");
173   x->file = NULL;
174   x->reported_error = false;
175   x->page_number = 0;
176   x->lines = NULL;
177   x->line_cap = 0;
178   x->chart_cnt = 1;
179
180   if (!outp_parse_options (this->name, options, handle_option, this))
181     goto error;
182
183   if (!update_page_size (this, true))
184     goto error;
185
186   for (i = 0; i < LNS_COUNT; i++)
187     if (x->box[i] == NULL)
188       {
189         char s[2];
190         s[0] = get_default_box_char (i);
191         s[1] = '\0';
192         x->box[i] = pool_strdup (x->pool, s);
193       }
194
195   outp_register_driver (this);
196
197   return true;
198
199  error:
200   pool_destroy (x->pool);
201   outp_free_driver (this);
202   return false;
203 }
204
205 static int
206 get_default_box_char (size_t idx)
207 {
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;
213
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);
217   switch (value)
218     {
219     case 0x0000:
220       return ' ';
221
222     case 0x0100: case 0x0101: case 0x0001:
223       return '-';
224
225     case 0x1000: case 0x1010: case 0x0010:
226       return '|';
227
228     case 0x0300: case 0x0303: case 0x0003:
229     case 0x0200: case 0x0202: case 0x0002:
230       return '=';
231
232     default:
233       return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
234     }
235 }
236
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. */
240 static bool
241 update_page_size (struct outp_driver *this, bool issue_error)
242 {
243   struct ascii_driver_ext *x = this->ext;
244   int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0);
245
246   if (x->auto_width)
247     this->width = settings_get_viewwidth ();
248   if (x->auto_length)
249     x->page_length = settings_get_viewlength ();
250
251   this->length = x->page_length - margins;
252
253   if (this->width < 59 || this->length < 15)
254     {
255       if (issue_error)
256         error (0, 0,
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)
262         this->width = 59;
263       if (this->length < 15)
264         {
265           this->length = 15;
266           x->page_length = this->length + margins;
267         }
268       return false;
269     }
270
271   return true;
272 }
273
274 static bool
275 ascii_close_driver (struct outp_driver *this)
276 {
277   struct ascii_driver_ext *x = this->ext;
278
279   ascii_flush (this);
280   pool_detach_file (x->pool, x->file);
281   pool_destroy (x->pool);
282
283   return true;
284 }
285
286 /* Generic option types. */
287 enum
288   {
289     boolean_arg,
290     emphasis_arg,
291     nonneg_int_arg,
292     page_size_arg,
293     string_arg
294   };
295
296 static const struct outp_option option_tab[] =
297   {
298     {"headers", boolean_arg, 0},
299     {"paginate", boolean_arg, 1},
300     {"squeeze", boolean_arg, 2},
301     {"append", boolean_arg, 3},
302
303     {"emphasis", emphasis_arg, 0},
304
305     {"length", page_size_arg, 0},
306     {"width", page_size_arg, 1},
307
308     {"top-margin", nonneg_int_arg, 0},
309     {"bottom-margin", nonneg_int_arg, 1},
310     {"tab-width", nonneg_int_arg, 2},
311
312     {"output-file", string_arg, 0},
313     {"chart-files", string_arg, 1},
314     {"chart-type", string_arg, 2},
315     {"init", string_arg, 3},
316
317     {NULL, 0, 0},
318   };
319
320 static bool
321 handle_option (void *this_, const char *key,
322                const struct string *val)
323 {
324   struct outp_driver *this = this_;
325   struct ascii_driver_ext *x = this->ext;
326   int subcat;
327   const char *value;
328
329   value = ds_cstr (val);
330   if (!strncmp (key, "box[", 4))
331     {
332       char *tail;
333       int indx = strtol (&key[4], &tail, 4);
334       if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
335         {
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"),
339                  LNS_COUNT);
340           return false;
341         }
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);
345       return true;
346     }
347
348   switch (outp_match_keyword (key, option_tab, &subcat))
349     {
350     case -1:
351       error (0, 0, _("ascii: unknown parameter `%s'"), key);
352       break;
353     case page_size_arg:
354       {
355         char *tail;
356         int arg;
357
358         if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
359           {
360             if (!(this->device & OUTP_DEV_SCREEN))
361               {
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
366                    bad idea. */
367                 error (0, 0, _("ascii: only screen devices may have `auto' "
368                                "length or width"));
369               }
370             else if (subcat == 0)
371               x->auto_length = true;
372             else
373               x->auto_width = true;
374           }
375         else
376           {
377             errno = 0;
378             arg = strtol (value, &tail, 0);
379             if (arg < 1 || errno == ERANGE || *tail)
380               {
381                 error (0, 0, _("ascii: positive integer required as "
382                                "`%s' value"),
383                        key);
384                 break;
385               }
386             switch (subcat)
387               {
388               case 0:
389                 x->page_length = arg;
390                 break;
391               case 1:
392                 this->width = arg;
393                 break;
394               default:
395                 NOT_REACHED ();
396               }
397           }
398       }
399       break;
400     case emphasis_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;
407       else
408         error (0, 0,
409                _("ascii: `emphasis' value must be `bold', "
410                  "`underline', or `none'"));
411       break;
412     case nonneg_int_arg:
413       {
414         char *tail;
415         int arg;
416
417         errno = 0;
418         arg = strtol (value, &tail, 0);
419         if (arg < 0 || errno == ERANGE || *tail)
420           {
421             error (0, 0,
422                    _("ascii: zero or positive integer required as `%s' value"),
423                    key);
424             break;
425           }
426         switch (subcat)
427           {
428           case 0:
429             x->top_margin = arg;
430             break;
431           case 1:
432             x->bottom_margin = arg;
433             break;
434           case 2:
435             x->tab_width = arg;
436             break;
437           default:
438             NOT_REACHED ();
439           }
440       }
441       break;
442     case boolean_arg:
443       {
444         bool setting;
445         if (!strcmp (value, "on") || !strcmp (value, "true")
446             || !strcmp (value, "yes") || atoi (value))
447           setting = true;
448         else if (!strcmp (value, "off") || !strcmp (value, "false")
449                  || !strcmp (value, "no") || !strcmp (value, "0"))
450           setting = false;
451         else
452           {
453             error (0, 0, _("ascii: boolean value expected for `%s'"), key);
454             return false;
455           }
456         switch (subcat)
457           {
458           case 0:
459             x->headers = setting;
460             break;
461           case 1:
462             x->paginate = setting;
463             break;
464           case 2:
465             x->squeeze_blank_lines = setting;
466             break;
467           case 3:
468             x->append = setting;
469             break;
470           default:
471             NOT_REACHED ();
472           }
473       }
474       break;
475     case string_arg:
476       switch (subcat)
477         {
478         case 0:
479           x->file_name = pool_strdup (x->pool, value);
480           break;
481         case 1:
482           if (ds_find_char (val, '#') != SIZE_MAX)
483             x->chart_file_name = pool_strdup (x->pool, value);
484           else
485             error (0, 0, _("`chart-files' value must contain `#'"));
486           break;
487         case 2:
488           if (value[0] != '\0')
489             x->chart_type = pool_strdup (x->pool, value);
490           else
491             x->chart_type = NULL;
492           break;
493         case 3:
494           x->init = pool_strdup (x->pool, value);
495           break;
496         }
497       break;
498     default:
499       NOT_REACHED ();
500     }
501
502   return true;
503 }
504
505 static void
506 ascii_open_page (struct outp_driver *this)
507 {
508   struct ascii_driver_ext *x = this->ext;
509   int i;
510
511   update_page_size (this, false);
512
513   if (x->file == NULL)
514     {
515       x->file = fn_open (x->file_name, x->append ? "a" : "w");
516       if (x->file != NULL)
517         {
518           pool_attach_file (x->pool, x->file);
519           if (x->init != NULL)
520             fputs (x->init, x->file);
521         }
522       else
523         {
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
529              for this (yet). */
530           if (!x->reported_error)
531             error (0, errno, _("ascii: opening output file \"%s\""),
532                    x->file_name);
533           x->reported_error = true;
534         }
535     }
536
537   x->page_number++;
538
539   if (this->length > x->line_cap)
540     {
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++)
544         {
545           struct line *line = &x->lines[i];
546           line->chars = NULL;
547           line->char_cap = 0;
548         }
549       x->line_cap = this->length;
550     }
551
552   for (i = 0; i < this->length; i++)
553     x->lines[i].char_cnt = 0;
554 }
555
556 /* Ensures that at least the first LENGTH characters of line Y in
557    THIS driver identified X have been cleared out. */
558 static inline void
559 expand_line (struct outp_driver *this, int y, int length)
560 {
561   struct ascii_driver_ext *ext = this->ext;
562   struct line *line = &ext->lines[y];
563   if (line->char_cnt < length)
564     {
565       int x;
566       if (line->char_cap < length)
567         {
568           line->char_cap = MIN (length * 2, this->width);
569           line->chars = pool_nrealloc (ext->pool,
570                                        line->chars,
571                                        line->char_cap, sizeof *line->chars);
572         }
573       for (x = line->char_cnt; x < length; x++)
574         line->chars[x] = ' ';
575       line->char_cnt = length;
576     }
577 }
578
579 static void
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)
584 {
585   struct ascii_driver_ext *ext = this->ext;
586   int y;
587   unsigned short value;
588
589   assert (this->page_open);
590 #if DEBUGGING
591   if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
592     {
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);
596 #endif
597       return;
598     }
599 #endif
600
601   value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
602            | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
603   for (y = y0; y < y1; y++)
604     {
605       int x;
606
607       expand_line (this, y, x1);
608       for (x = x0; x < x1; x++)
609         ext->lines[y].chars[x] = value;
610     }
611 }
612
613 static void
614 text_draw (struct outp_driver *this,
615            enum outp_font font,
616            int x, int y,
617            enum outp_justification justification, int width,
618            const char *string, size_t length)
619 {
620   struct ascii_driver_ext *ext = this->ext;
621   unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
622
623   int line_len;
624
625   switch (justification)
626     {
627     case OUTP_LEFT:
628       break;
629     case OUTP_CENTER:
630       x += (width - length + 1) / 2;
631       break;
632     case OUTP_RIGHT:
633       x += width - length;
634       break;
635     default:
636       NOT_REACHED ();
637     }
638
639   if (y >= this->length || x >= this->width)
640     return;
641
642   if (x + length > this->width)
643     length = this->width - x;
644
645   line_len = x + length;
646
647   expand_line (this, y, line_len);
648   while (length-- > 0)
649     ext->lines[y].chars[x++] = *string++ | attr;
650 }
651
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. */
656 static void
657 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
658            int *width, int *height)
659 {
660   int max_width;
661   int height_left;
662
663   const char *cp = ss_data (text->string);
664
665   max_width = 0;
666   height_left = text->v;
667
668   while (height_left > 0)
669     {
670       size_t chars_left;
671       size_t line_len;
672       const char *end;
673
674       /* Initially the line is up to text->h characters long. */
675       chars_left = ss_end (text->string) - cp;
676       if (chars_left == 0)
677         break;
678       line_len = MIN (chars_left, text->h);
679
680       /* A new-line terminates the line prematurely. */
681       end = memchr (cp, '\n', line_len);
682       if (end != NULL)
683         line_len = end - cp;
684
685       /* Don't cut off words if it can be avoided. */
686       if (cp + line_len < ss_end (text->string))
687         {
688           size_t space_len = line_len;
689           while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
690             space_len--;
691           if (space_len > 0)
692             line_len = space_len;
693         }
694
695       /* Draw text. */
696       if (draw)
697         text_draw (this,
698                    text->font,
699                    text->x, text->y + (text->v - height_left),
700                    text->justification, text->h,
701                    cp, line_len);
702
703       /* Update. */
704       height_left--;
705       if (line_len > max_width)
706         max_width = line_len;
707
708       /* Next line. */
709       cp += line_len;
710       if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
711         cp++;
712     }
713
714   if (width != NULL)
715     *width = max_width;
716   if (height != NULL)
717     *height = text->v - height_left;
718 }
719
720 static void
721 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
722                     int *width, int *height)
723 {
724   delineate (this, t, false, width, height);
725 }
726
727 static void
728 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
729 {
730   assert (this->page_open);
731   delineate (this, t, true, NULL, NULL);
732 }
733 \f
734 /* ascii_close_page () and support routines. */
735
736 /* Writes the LENGTH characters in S to OUT.  */
737 static void
738 output_line (struct outp_driver *this, const struct line *line,
739              struct string *out)
740 {
741   struct ascii_driver_ext *ext = this->ext;
742   const unsigned short *s = line->chars;
743   size_t length;
744
745   for (length = line->char_cnt; length-- > 0; s++)
746     if (*s & ATTR_BOX)
747       ds_put_cstr (out, ext->box[*s & 0xff]);
748     else
749       {
750         if (*s & ATTR_EMPHASIS)
751           {
752             if (ext->emphasis == EMPH_BOLD)
753               {
754                 ds_put_char (out, *s);
755                 ds_put_char (out, '\b');
756               }
757             else if (ext->emphasis == EMPH_UNDERLINE)
758               ds_put_cstr (out, "_\b");
759           }
760         ds_put_char (out, *s);
761       }
762 }
763
764 static void
765 append_lr_justified (struct string *out, int width,
766                      const char *left, const char *right)
767 {
768   ds_put_char_multiple (out, ' ', width);
769   if (left != NULL)
770     {
771       size_t length = MIN (strlen (left), width);
772       memcpy (ds_end (out) - width, left, length);
773     }
774   if (right != NULL)
775     {
776       size_t length = MIN (strlen (right), width);
777       memcpy (ds_end (out) - length, right, length);
778     }
779   ds_put_char (out, '\n');
780 }
781
782 static void
783 dump_output (struct outp_driver *this, struct string *out)
784 {
785   struct ascii_driver_ext *x = this->ext;
786   fwrite (ds_data (out), ds_length (out), 1, x->file);
787   ds_clear (out);
788 }
789
790 static void
791 ascii_close_page (struct outp_driver *this)
792 {
793   struct ascii_driver_ext *x = this->ext;
794   struct string out;
795   int line_num;
796
797   if (x->file == NULL)
798     return;
799
800   ds_init_empty (&out);
801
802   ds_put_char_multiple (&out, '\n', x->top_margin);
803   if (x->headers)
804     {
805       char *r1, *r2;
806
807       r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
808       r2 = xasprintf ("%s - %s" , version, host_system);
809
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');
813
814       free (r1);
815       free (r2);
816     }
817   dump_output (this, &out);
818
819   for (line_num = 0; line_num < this->length; line_num++)
820     {
821
822       /* Squeeze multiple blank lines into a single blank line if
823          requested. */
824       if (x->squeeze_blank_lines)
825         {
826           if (line_num >= x->line_cap)
827             break;
828           if (line_num > 0
829               && x->lines[line_num].char_cnt == 0
830               && x->lines[line_num - 1].char_cnt == 0)
831             continue;
832         }
833
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);
838     }
839
840   ds_put_char_multiple (&out, '\n', x->bottom_margin);
841   if (x->paginate)
842     ds_put_char (&out, '\f');
843
844   dump_output (this, &out);
845   ds_destroy (&out);
846 }
847
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
851    time. */
852 static void
853 ascii_flush (struct outp_driver *this)
854 {
855   struct ascii_driver_ext *x = this->ext;
856   if (x->file != NULL)
857     {
858       if (fn_close (x->file_name, x->file) != 0)
859         error (0, errno, _("ascii: closing output file \"%s\""),
860                x->file_name);
861       pool_detach_file (x->pool, x->file);
862       x->file = NULL;
863     }
864 }
865
866 static void
867 ascii_output_chart (struct outp_driver *this, const struct chart *chart)
868 {
869   struct ascii_driver_ext *x = this->ext;
870   struct chart_geometry geom;
871   struct outp_text t;
872   char *file_name;
873   plPlotter *lp;
874   char *text;
875
876   if (x->chart_type == NULL)
877     return;
878
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))
882     return;
883   x->chart_cnt++;
884   chart_geometry_init (lp, &geom, 1000.0, 1000.0);
885   chart_draw (chart, lp, &geom);
886   chart_geometry_free (lp);
887   pl_deletepl_r (lp);
888
889   /* Mention chart in output.
890      First advance current position. */
891   if (!this->page_open)
892     outp_open_page (this);
893   else
894     {
895       this->cp_y++;
896       if (this->cp_y >= this->length)
897         {
898           outp_close_page (this);
899           outp_open_page (this);
900         }
901     }
902
903   /* Then write the text. */
904   text = xasprintf ("See %s for a chart.", file_name);
905   t.font = OUTP_FIXED;
906   t.justification = OUTP_LEFT;
907   t.string = ss_cstr (text);
908   t.h = this->width;
909   t.v = 1;
910   t.x = 0;
911   t.y = this->cp_y;
912   ascii_text_draw (this, &t);
913   this->cp_y++;
914
915   free (file_name);
916   free (text);
917 }
918
919 const struct outp_class ascii_class =
920 {
921   "ascii",
922   0,
923
924   ascii_open_driver,
925   ascii_close_driver,
926
927   ascii_open_page,
928   ascii_close_page,
929   ascii_flush,
930
931   ascii_output_chart,
932
933   NULL,                         /* submit */
934
935   ascii_line,
936   ascii_text_metrics,
937   ascii_text_draw,
938 };