output: Make allocating and freeing drivers independent of configuration.
[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
33 #include "chart.h"
34 #include "error.h"
35 #include "minmax.h"
36 #include "output.h"
37 #include "xalloc.h"
38
39 #include "gettext.h"
40 #define _(msgid) gettext (msgid)
41
42 /* ASCII driver options: (defaults listed first)
43
44    output-file="pspp.list"
45    append=no|yes                If output-file exists, append to it?
46    chart-files="pspp-#.png"     Name used for charts.
47    chart-type=png               Format of charts (use "none" to disable).
48
49    paginate=on|off              Formfeeds are desired?
50    tab-width=8                  Width of a tab; 0 to not use tabs.
51
52    headers=on|off               Put headers at top of page?
53    emphasis=bold|underline|none Style to use for emphasis.
54    length=66|auto
55    width=79|auto
56    squeeze=off|on               Squeeze multiple newlines into exactly one.
57
58    top-margin=2
59    bottom-margin=2
60
61    box[x]="strng"               Sets box character X (X in base 4: 0-3333).
62    init="string"                Set initialization string.
63  */
64
65 /* Disable messages by failed range checks. */
66 /*#define SUPPRESS_WARNINGS 1 */
67
68 /* Line styles bit shifts. */
69 enum
70   {
71     LNS_TOP = 0,
72     LNS_LEFT = 2,
73     LNS_BOTTOM = 4,
74     LNS_RIGHT = 6,
75
76     LNS_COUNT = 256
77   };
78
79 /* Character attributes. */
80 #define ATTR_EMPHASIS   0x100   /* Bold-face. */
81 #define ATTR_BOX        0x200   /* Line drawing character. */
82
83 /* A line of text. */
84 struct line
85   {
86     unsigned short *chars;      /* Characters and attributes. */
87     int char_cnt;               /* Length. */
88     int char_cap;               /* Allocated bytes. */
89   };
90
91 /* How to emphasize text. */
92 enum emphasis_style
93   {
94     EMPH_BOLD,                  /* Overstrike for bold. */
95     EMPH_UNDERLINE,             /* Overstrike for underlining. */
96     EMPH_NONE                   /* No emphasis. */
97   };
98
99 /* ASCII output driver extension record. */
100 struct ascii_driver_ext
101   {
102     struct pool *pool;
103
104     /* User parameters. */
105     bool append;                /* Append if output-file already exists? */
106     bool headers;               /* Print headers at top of page? */
107     bool paginate;              /* Insert formfeeds? */
108     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
109     enum emphasis_style emphasis; /* How to emphasize text. */
110     int tab_width;              /* Width of a tab; 0 not to use tabs. */
111     const char *chart_type;     /* Type of charts to output; NULL for none. */
112     const char *chart_file_name; /* Name of files used for charts. */
113
114     bool auto_width;            /* Use viewwidth as page width? */
115     bool auto_length;           /* Use viewlength as page width? */
116     int page_length;            /* Page length before subtracting margins. */
117     int top_margin;             /* Top margin in lines. */
118     int bottom_margin;          /* Bottom margin in lines. */
119
120     char *box[LNS_COUNT];       /* Line & box drawing characters. */
121     char *init;                 /* Device initialization string. */
122
123     /* Internal state. */
124     char *file_name;            /* Output file name. */
125     FILE *file;                 /* Output file. */
126     bool reported_error;        /* Reported file open error? */
127     int page_number;            /* Current page number. */
128     struct line *lines;         /* Page content. */
129     int line_cap;               /* Number of lines allocated. */
130     int chart_cnt;              /* Number of charts so far. */
131   };
132
133 static void ascii_flush (struct outp_driver *);
134 static int get_default_box_char (size_t idx);
135 static bool update_page_size (struct outp_driver *, bool issue_error);
136 static bool handle_option (struct outp_driver *this, const char *key,
137                            const struct string *val);
138
139 static bool
140 ascii_open_driver (const char *name, int types, struct substring options)
141 {
142   struct outp_driver *this;
143   struct ascii_driver_ext *x;
144   int i;
145
146   this = outp_allocate_driver (&ascii_class, name, types);
147   this->width = 79;
148   this->font_height = 1;
149   this->prop_em_width = 1;
150   this->fixed_width = 1;
151   for (i = 0; i < OUTP_L_COUNT; i++)
152     this->horiz_line_width[i] = this->vert_line_width[i] = i != OUTP_L_NONE;
153
154   this->ext = x = pool_create_container (struct ascii_driver_ext, pool);
155   x->append = false;
156   x->headers = true;
157   x->paginate = true;
158   x->squeeze_blank_lines = false;
159   x->emphasis = EMPH_BOLD;
160   x->tab_width = 8;
161   x->chart_file_name = pool_strdup (x->pool, "pspp-#.png");
162   x->chart_type = pool_strdup (x->pool, "png");
163   x->auto_width = false;
164   x->auto_length = false;
165   x->page_length = 66;
166   x->top_margin = 2;
167   x->bottom_margin = 2;
168   for (i = 0; i < LNS_COUNT; i++)
169     x->box[i] = NULL;
170   x->init = NULL;
171   x->file_name = pool_strdup (x->pool, "pspp.list");
172   x->file = NULL;
173   x->reported_error = false;
174   x->page_number = 0;
175   x->lines = NULL;
176   x->line_cap = 0;
177   x->chart_cnt = 0;
178
179   if (!outp_parse_options (options, handle_option, this))
180     goto error;
181
182   if (!update_page_size (this, true))
183     goto error;
184
185   for (i = 0; i < LNS_COUNT; i++)
186     if (x->box[i] == NULL)
187       {
188         char s[2];
189         s[0] = get_default_box_char (i);
190         s[1] = '\0';
191         x->box[i] = pool_strdup (x->pool, s);
192       }
193
194   outp_register_driver (this);
195
196   return true;
197
198  error:
199   pool_destroy (x->pool);
200   outp_free_driver (this);
201   return false;
202 }
203
204 static int
205 get_default_box_char (size_t idx)
206 {
207   /* Disassemble IDX into components. */
208   unsigned top = (idx >> LNS_TOP) & 3;
209   unsigned left = (idx >> LNS_LEFT) & 3;
210   unsigned bottom = (idx >> LNS_BOTTOM) & 3;
211   unsigned right = (idx >> LNS_RIGHT) & 3;
212
213   /* Reassemble components into nibbles in the order TLBR.
214      This makes it easy to read the case labels. */
215   unsigned value = (top << 12) | (left << 8) | (bottom << 4) | (right << 0);
216   switch (value)
217     {
218     case 0x0000:
219       return ' ';
220
221     case 0x0100: case 0x0101: case 0x0001:
222       return '-';
223
224     case 0x1000: case 0x1010: case 0x0010:
225       return '|';
226
227     case 0x0300: case 0x0303: case 0x0003:
228     case 0x0200: case 0x0202: case 0x0002:
229       return '=';
230
231     default:
232       return left > 1 || top > 1 || right > 1 || bottom > 1 ? '#' : '+';
233     }
234 }
235
236 /* Re-calculates the page width and length based on settings,
237    margins, and, if "auto" is set, the size of the user's
238    terminal window or GUI output window. */
239 static bool
240 update_page_size (struct outp_driver *this, bool issue_error)
241 {
242   struct ascii_driver_ext *x = this->ext;
243   int margins = x->top_margin + x->bottom_margin + 1 + (x->headers ? 3 : 0);
244
245   if (x->auto_width)
246     this->width = settings_get_viewwidth ();
247   if (x->auto_length)
248     x->page_length = settings_get_viewlength ();
249
250   this->length = x->page_length - margins;
251
252   if (this->width < 59 || this->length < 15)
253     {
254       if (issue_error)
255         error (0, 0,
256                _("ascii: page excluding margins and headers "
257                  "must be at least 59 characters wide by 15 lines long, but "
258                  "as configured is only %d characters by %d lines"),
259              this->width, this->length);
260       if (this->width < 59)
261         this->width = 59;
262       if (this->length < 15)
263         {
264           this->length = 15;
265           x->page_length = this->length + margins;
266         }
267       return false;
268     }
269
270   return true;
271 }
272
273 static bool
274 ascii_close_driver (struct outp_driver *this)
275 {
276   struct ascii_driver_ext *x = this->ext;
277
278   ascii_flush (this);
279   pool_detach_file (x->pool, x->file);
280   pool_destroy (x->pool);
281
282   return true;
283 }
284
285 /* Generic option types. */
286 enum
287   {
288     boolean_arg,
289     emphasis_arg,
290     nonneg_int_arg,
291     page_size_arg,
292     string_arg
293   };
294
295 static const struct outp_option option_tab[] =
296   {
297     {"headers", boolean_arg, 0},
298     {"paginate", boolean_arg, 1},
299     {"squeeze", boolean_arg, 2},
300     {"append", boolean_arg, 3},
301
302     {"emphasis", emphasis_arg, 0},
303
304     {"length", page_size_arg, 0},
305     {"width", page_size_arg, 1},
306
307     {"top-margin", nonneg_int_arg, 0},
308     {"bottom-margin", nonneg_int_arg, 1},
309     {"tab-width", nonneg_int_arg, 2},
310
311     {"output-file", string_arg, 0},
312     {"chart-files", string_arg, 1},
313     {"chart-type", string_arg, 2},
314     {"init", string_arg, 3},
315
316     {NULL, 0, 0},
317   };
318
319 static bool
320 handle_option (struct outp_driver *this, const char *key,
321                const struct string *val)
322 {
323   struct ascii_driver_ext *x = this->ext;
324   int subcat;
325   const char *value;
326
327   value = ds_cstr (val);
328   if (!strncmp (key, "box[", 4))
329     {
330       char *tail;
331       int indx = strtol (&key[4], &tail, 4);
332       if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
333         {
334           error (0, 0, _("ascii: bad index value for `box' key: syntax "
335                          "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
336                          "expressed in base 4"),
337                  LNS_COUNT);
338           return false;
339         }
340       if (x->box[indx] != NULL)
341         error (0, 0, _("ascii: multiple values for %s"), key);
342       x->box[indx] = pool_strdup (x->pool, value);
343       return true;
344     }
345
346   switch (outp_match_keyword (key, option_tab, &subcat))
347     {
348     case -1:
349       error (0, 0, _("ascii: unknown parameter `%s'"), key);
350       break;
351     case page_size_arg:
352       {
353         char *tail;
354         int arg;
355
356         if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
357           {
358             if (!(this->device & OUTP_DEV_SCREEN))
359               {
360                 /* We only let `screen' devices have `auto'
361                    length or width because output to such devices
362                    is flushed before each new command.  Resizing
363                    a device in the middle of output seems like a
364                    bad idea. */
365                 error (0, 0, _("ascii: only screen devices may have `auto' "
366                                "length or width"));
367               }
368             else if (subcat == 0)
369               x->auto_length = true;
370             else
371               x->auto_width = true;
372           }
373         else
374           {
375             errno = 0;
376             arg = strtol (value, &tail, 0);
377             if (arg < 1 || errno == ERANGE || *tail)
378               {
379                 error (0, 0, _("ascii: positive integer required as "
380                                "`%s' value"),
381                        key);
382                 break;
383               }
384             switch (subcat)
385               {
386               case 0:
387                 x->page_length = arg;
388                 break;
389               case 1:
390                 this->width = arg;
391                 break;
392               default:
393                 NOT_REACHED ();
394               }
395           }
396       }
397       break;
398     case emphasis_arg:
399       if (!strcmp (value, "bold"))
400         x->emphasis = EMPH_BOLD;
401       else if (!strcmp (value, "underline"))
402         x->emphasis = EMPH_UNDERLINE;
403       else if (!strcmp (value, "none"))
404         x->emphasis = EMPH_NONE;
405       else
406         error (0, 0,
407                _("ascii: `emphasis' value must be `bold', "
408                  "`underline', or `none'"));
409       break;
410     case nonneg_int_arg:
411       {
412         char *tail;
413         int arg;
414
415         errno = 0;
416         arg = strtol (value, &tail, 0);
417         if (arg < 0 || errno == ERANGE || *tail)
418           {
419             error (0, 0,
420                    _("ascii: zero or positive integer required as `%s' value"),
421                    key);
422             break;
423           }
424         switch (subcat)
425           {
426           case 0:
427             x->top_margin = arg;
428             break;
429           case 1:
430             x->bottom_margin = arg;
431             break;
432           case 2:
433             x->tab_width = arg;
434             break;
435           default:
436             NOT_REACHED ();
437           }
438       }
439       break;
440     case boolean_arg:
441       {
442         bool setting;
443         if (!strcmp (value, "on") || !strcmp (value, "true")
444             || !strcmp (value, "yes") || atoi (value))
445           setting = true;
446         else if (!strcmp (value, "off") || !strcmp (value, "false")
447                  || !strcmp (value, "no") || !strcmp (value, "0"))
448           setting = false;
449         else
450           {
451             error (0, 0, _("ascii: boolean value expected for `%s'"), key);
452             return false;
453           }
454         switch (subcat)
455           {
456           case 0:
457             x->headers = setting;
458             break;
459           case 1:
460             x->paginate = setting;
461             break;
462           case 2:
463             x->squeeze_blank_lines = setting;
464             break;
465           case 3:
466             x->append = setting;
467             break;
468           default:
469             NOT_REACHED ();
470           }
471       }
472       break;
473     case string_arg:
474       switch (subcat)
475         {
476         case 0:
477           x->file_name = pool_strdup (x->pool, value);
478           break;
479         case 1:
480           if (ds_find_char (val, '#') != SIZE_MAX)
481             x->chart_file_name = pool_strdup (x->pool, value);
482           else
483             error (0, 0, _("`chart-files' value must contain `#'"));
484           break;
485         case 2:
486           if (value[0] != '\0')
487             x->chart_type = pool_strdup (x->pool, value);
488           else
489             x->chart_type = NULL;
490           break;
491         case 3:
492           x->init = pool_strdup (x->pool, value);
493           break;
494         }
495       break;
496     default:
497       NOT_REACHED ();
498     }
499
500   return true;
501 }
502
503 static void
504 ascii_open_page (struct outp_driver *this)
505 {
506   struct ascii_driver_ext *x = this->ext;
507   int i;
508
509   update_page_size (this, false);
510
511   if (x->file == NULL)
512     {
513       x->file = fn_open (x->file_name, x->append ? "a" : "w");
514       if (x->file != NULL)
515         {
516           pool_attach_file (x->pool, x->file);
517           if (x->init != NULL)
518             fputs (x->init, x->file);
519         }
520       else
521         {
522           /* Report the error to the user and complete
523              initialization.  If we do not finish initialization,
524              then calls to other driver functions will segfault
525              later.  It would be better to simply drop the driver
526              entirely, but we do not have a convenient mechanism
527              for this (yet). */
528           if (!x->reported_error)
529             error (0, errno, _("ascii: opening output file \"%s\""),
530                    x->file_name);
531           x->reported_error = true;
532         }
533     }
534
535   x->page_number++;
536
537   if (this->length > x->line_cap)
538     {
539       x->lines = pool_nrealloc (x->pool,
540                                 x->lines, this->length, sizeof *x->lines);
541       for (i = x->line_cap; i < this->length; i++)
542         {
543           struct line *line = &x->lines[i];
544           line->chars = NULL;
545           line->char_cap = 0;
546         }
547       x->line_cap = this->length;
548     }
549
550   for (i = 0; i < this->length; i++)
551     x->lines[i].char_cnt = 0;
552 }
553
554 /* Ensures that at least the first LENGTH characters of line Y in
555    THIS driver identified X have been cleared out. */
556 static inline void
557 expand_line (struct outp_driver *this, int y, int length)
558 {
559   struct ascii_driver_ext *ext = this->ext;
560   struct line *line = &ext->lines[y];
561   if (line->char_cnt < length)
562     {
563       int x;
564       if (line->char_cap < length)
565         {
566           line->char_cap = MIN (length * 2, this->width);
567           line->chars = pool_nrealloc (ext->pool,
568                                        line->chars,
569                                        line->char_cap, sizeof *line->chars);
570         }
571       for (x = line->char_cnt; x < length; x++)
572         line->chars[x] = ' ';
573       line->char_cnt = length;
574     }
575 }
576
577 static void
578 ascii_line (struct outp_driver *this,
579             int x0, int y0, int x1, int y1,
580             enum outp_line_style top, enum outp_line_style left,
581             enum outp_line_style bottom, enum outp_line_style right)
582 {
583   struct ascii_driver_ext *ext = this->ext;
584   int y;
585   unsigned short value;
586
587   assert (this->page_open);
588 #if DEBUGGING
589   if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
590     {
591 #if !SUPPRESS_WARNINGS
592       printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
593               x0, y0, x1, y1, this->width, this->length);
594 #endif
595       return;
596     }
597 #endif
598
599   value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
600            | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
601   for (y = y0; y < y1; y++)
602     {
603       int x;
604
605       expand_line (this, y, x1);
606       for (x = x0; x < x1; x++)
607         ext->lines[y].chars[x] = value;
608     }
609 }
610
611 static void
612 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
613 {
614   extern struct som_table_class tab_table_class;
615
616   assert (s->class == &tab_table_class);
617   assert (s->type == SOM_CHART);
618 }
619
620 static void
621 text_draw (struct outp_driver *this,
622            enum outp_font font,
623            int x, int y,
624            enum outp_justification justification, int width,
625            const char *string, size_t length)
626 {
627   struct ascii_driver_ext *ext = this->ext;
628   unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
629
630   int line_len;
631
632   switch (justification)
633     {
634     case OUTP_LEFT:
635       break;
636     case OUTP_CENTER:
637       x += (width - length + 1) / 2;
638       break;
639     case OUTP_RIGHT:
640       x += width - length;
641       break;
642     default:
643       NOT_REACHED ();
644     }
645
646   if (y >= this->length || x >= this->width)
647     return;
648
649   if (x + length > this->width)
650     length = this->width - x;
651
652   line_len = x + length;
653
654   expand_line (this, y, line_len);
655   while (length-- > 0)
656     ext->lines[y].chars[x++] = *string++ | attr;
657 }
658
659 /* Divides the text T->S into lines of width T->H.  Sets *WIDTH
660    to the maximum width of a line and *HEIGHT to the number of
661    lines, if those arguments are non-null.  Actually draws the
662    text if DRAW is true. */
663 static void
664 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
665            int *width, int *height)
666 {
667   int max_width;
668   int height_left;
669
670   const char *cp = ss_data (text->string);
671
672   max_width = 0;
673   height_left = text->v;
674
675   while (height_left > 0)
676     {
677       size_t chars_left;
678       size_t line_len;
679       const char *end;
680
681       /* Initially the line is up to text->h characters long. */
682       chars_left = ss_end (text->string) - cp;
683       if (chars_left == 0)
684         break;
685       line_len = MIN (chars_left, text->h);
686
687       /* A new-line terminates the line prematurely. */
688       end = memchr (cp, '\n', line_len);
689       if (end != NULL)
690         line_len = end - cp;
691
692       /* Don't cut off words if it can be avoided. */
693       if (cp + line_len < ss_end (text->string))
694         {
695           size_t space_len = line_len;
696           while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
697             space_len--;
698           if (space_len > 0)
699             line_len = space_len;
700         }
701
702       /* Draw text. */
703       if (draw)
704         text_draw (this,
705                    text->font,
706                    text->x, text->y + (text->v - height_left),
707                    text->justification, text->h,
708                    cp, line_len);
709
710       /* Update. */
711       height_left--;
712       if (line_len > max_width)
713         max_width = line_len;
714
715       /* Next line. */
716       cp += line_len;
717       if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
718         cp++;
719     }
720
721   if (width != NULL)
722     *width = max_width;
723   if (height != NULL)
724     *height = text->v - height_left;
725 }
726
727 static void
728 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
729                     int *width, int *height)
730 {
731   delineate (this, t, false, width, height);
732 }
733
734 static void
735 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
736 {
737   assert (this->page_open);
738   delineate (this, t, true, NULL, NULL);
739 }
740 \f
741 /* ascii_close_page () and support routines. */
742
743 /* Writes the LENGTH characters in S to OUT.  */
744 static void
745 output_line (struct outp_driver *this, const struct line *line,
746              struct string *out)
747 {
748   struct ascii_driver_ext *ext = this->ext;
749   const unsigned short *s = line->chars;
750   size_t length;
751
752   for (length = line->char_cnt; length-- > 0; s++)
753     if (*s & ATTR_BOX)
754       ds_put_cstr (out, ext->box[*s & 0xff]);
755     else
756       {
757         if (*s & ATTR_EMPHASIS)
758           {
759             if (ext->emphasis == EMPH_BOLD)
760               {
761                 ds_put_char (out, *s);
762                 ds_put_char (out, '\b');
763               }
764             else if (ext->emphasis == EMPH_UNDERLINE)
765               ds_put_cstr (out, "_\b");
766           }
767         ds_put_char (out, *s);
768       }
769 }
770
771 static void
772 append_lr_justified (struct string *out, int width,
773                      const char *left, const char *right)
774 {
775   ds_put_char_multiple (out, ' ', width);
776   if (left != NULL)
777     {
778       size_t length = MIN (strlen (left), width);
779       memcpy (ds_end (out) - width, left, length);
780     }
781   if (right != NULL)
782     {
783       size_t length = MIN (strlen (right), width);
784       memcpy (ds_end (out) - length, right, length);
785     }
786   ds_put_char (out, '\n');
787 }
788
789 static void
790 dump_output (struct outp_driver *this, struct string *out)
791 {
792   struct ascii_driver_ext *x = this->ext;
793   fwrite (ds_data (out), ds_length (out), 1, x->file);
794   ds_clear (out);
795 }
796
797 static void
798 ascii_close_page (struct outp_driver *this)
799 {
800   struct ascii_driver_ext *x = this->ext;
801   struct string out;
802   int line_num;
803
804   if (x->file == NULL)
805     return;
806
807   ds_init_empty (&out);
808
809   ds_put_char_multiple (&out, '\n', x->top_margin);
810   if (x->headers)
811     {
812       char *r1, *r2;
813
814       r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
815       r2 = xasprintf ("%s - %s" , version, host_system);
816
817       append_lr_justified (&out, this->width, outp_title, r1);
818       append_lr_justified (&out, this->width, outp_subtitle, r2);
819       ds_put_char (&out, '\n');
820
821       free (r1);
822       free (r2);
823     }
824   dump_output (this, &out);
825
826   for (line_num = 0; line_num < this->length; line_num++)
827     {
828
829       /* Squeeze multiple blank lines into a single blank line if
830          requested. */
831       if (x->squeeze_blank_lines)
832         {
833           if (line_num >= x->line_cap)
834             break;
835           if (line_num > 0
836               && x->lines[line_num].char_cnt == 0
837               && x->lines[line_num - 1].char_cnt == 0)
838             continue;
839         }
840
841       if (line_num < x->line_cap)
842         output_line (this, &x->lines[line_num], &out);
843       ds_put_char (&out, '\n');
844       dump_output (this, &out);
845     }
846
847   ds_put_char_multiple (&out, '\n', x->bottom_margin);
848   if (x->paginate)
849     ds_put_char (&out, '\f');
850
851   dump_output (this, &out);
852   ds_destroy (&out);
853 }
854
855 /* Flushes all output to the user and lets the user deal with it.
856    This is applied only to output drivers that are designated as
857    "screen" drivers that the user is interacting with in real
858    time. */
859 static void
860 ascii_flush (struct outp_driver *this)
861 {
862   struct ascii_driver_ext *x = this->ext;
863   if (x->file != NULL)
864     {
865       if (fn_close (x->file_name, x->file) != 0)
866         error (0, errno, _("ascii: closing output file \"%s\""),
867                x->file_name);
868       pool_detach_file (x->pool, x->file);
869       x->file = NULL;
870     }
871 }
872
873 static void
874 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
875 {
876   struct ascii_driver_ext *x = this->ext;
877   struct outp_text t;
878   char *text;
879
880   if (x->chart_type == NULL)
881     return;
882
883   /* Initialize chart. */
884   chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
885   if (ch->file_name == NULL)
886     return;
887
888   /* Mention chart in output.
889      First advance current position. */
890   if (!this->page_open)
891     outp_open_page (this);
892   else
893     {
894       this->cp_y++;
895       if (this->cp_y >= this->length)
896         {
897           outp_close_page (this);
898           outp_open_page (this);
899         }
900     }
901
902   /* Then write the text. */
903   text = xasprintf ("See %s for a chart.", ch->file_name);
904   t.font = OUTP_FIXED;
905   t.justification = OUTP_LEFT;
906   t.string = ss_cstr (text);
907   t.h = this->width;
908   t.v = 1;
909   t.x = 0;
910   t.y = this->cp_y;
911   ascii_text_draw (this, &t);
912   this->cp_y++;
913
914   free (text);
915 }
916
917 static void
918 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
919 {
920   struct ascii_driver_ext *x = this->ext;
921   if (x->chart_type != NULL)
922     chart_finalise_separate (ch);
923 }
924
925 const struct outp_class ascii_class =
926 {
927   "ascii",
928   0,
929
930   ascii_open_driver,
931   ascii_close_driver,
932
933   ascii_open_page,
934   ascii_close_page,
935   ascii_flush,
936
937   ascii_submit,
938
939   ascii_line,
940   ascii_text_metrics,
941   ascii_text_draw,
942
943   ascii_chart_initialise,
944   ascii_chart_finalise
945 };