5a33a6611bfdd8d6f4c7fc4a602ff1a7c92e8717
[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 (void *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 (this->name, 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 (void *this_, const char *key,
321                const struct string *val)
322 {
323   struct outp_driver *this = this_;
324   struct ascii_driver_ext *x = this->ext;
325   int subcat;
326   const char *value;
327
328   value = ds_cstr (val);
329   if (!strncmp (key, "box[", 4))
330     {
331       char *tail;
332       int indx = strtol (&key[4], &tail, 4);
333       if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
334         {
335           error (0, 0, _("ascii: bad index value for `box' key: syntax "
336                          "is box[INDEX], 0 <= INDEX < %d decimal, with INDEX "
337                          "expressed in base 4"),
338                  LNS_COUNT);
339           return false;
340         }
341       if (x->box[indx] != NULL)
342         error (0, 0, _("ascii: multiple values for %s"), key);
343       x->box[indx] = pool_strdup (x->pool, value);
344       return true;
345     }
346
347   switch (outp_match_keyword (key, option_tab, &subcat))
348     {
349     case -1:
350       error (0, 0, _("ascii: unknown parameter `%s'"), key);
351       break;
352     case page_size_arg:
353       {
354         char *tail;
355         int arg;
356
357         if (ss_equals_case (ds_ss (val), ss_cstr ("auto")))
358           {
359             if (!(this->device & OUTP_DEV_SCREEN))
360               {
361                 /* We only let `screen' devices have `auto'
362                    length or width because output to such devices
363                    is flushed before each new command.  Resizing
364                    a device in the middle of output seems like a
365                    bad idea. */
366                 error (0, 0, _("ascii: only screen devices may have `auto' "
367                                "length or width"));
368               }
369             else if (subcat == 0)
370               x->auto_length = true;
371             else
372               x->auto_width = true;
373           }
374         else
375           {
376             errno = 0;
377             arg = strtol (value, &tail, 0);
378             if (arg < 1 || errno == ERANGE || *tail)
379               {
380                 error (0, 0, _("ascii: positive integer required as "
381                                "`%s' value"),
382                        key);
383                 break;
384               }
385             switch (subcat)
386               {
387               case 0:
388                 x->page_length = arg;
389                 break;
390               case 1:
391                 this->width = arg;
392                 break;
393               default:
394                 NOT_REACHED ();
395               }
396           }
397       }
398       break;
399     case emphasis_arg:
400       if (!strcmp (value, "bold"))
401         x->emphasis = EMPH_BOLD;
402       else if (!strcmp (value, "underline"))
403         x->emphasis = EMPH_UNDERLINE;
404       else if (!strcmp (value, "none"))
405         x->emphasis = EMPH_NONE;
406       else
407         error (0, 0,
408                _("ascii: `emphasis' value must be `bold', "
409                  "`underline', or `none'"));
410       break;
411     case nonneg_int_arg:
412       {
413         char *tail;
414         int arg;
415
416         errno = 0;
417         arg = strtol (value, &tail, 0);
418         if (arg < 0 || errno == ERANGE || *tail)
419           {
420             error (0, 0,
421                    _("ascii: zero or positive integer required as `%s' value"),
422                    key);
423             break;
424           }
425         switch (subcat)
426           {
427           case 0:
428             x->top_margin = arg;
429             break;
430           case 1:
431             x->bottom_margin = arg;
432             break;
433           case 2:
434             x->tab_width = arg;
435             break;
436           default:
437             NOT_REACHED ();
438           }
439       }
440       break;
441     case boolean_arg:
442       {
443         bool setting;
444         if (!strcmp (value, "on") || !strcmp (value, "true")
445             || !strcmp (value, "yes") || atoi (value))
446           setting = true;
447         else if (!strcmp (value, "off") || !strcmp (value, "false")
448                  || !strcmp (value, "no") || !strcmp (value, "0"))
449           setting = false;
450         else
451           {
452             error (0, 0, _("ascii: boolean value expected for `%s'"), key);
453             return false;
454           }
455         switch (subcat)
456           {
457           case 0:
458             x->headers = setting;
459             break;
460           case 1:
461             x->paginate = setting;
462             break;
463           case 2:
464             x->squeeze_blank_lines = setting;
465             break;
466           case 3:
467             x->append = setting;
468             break;
469           default:
470             NOT_REACHED ();
471           }
472       }
473       break;
474     case string_arg:
475       switch (subcat)
476         {
477         case 0:
478           x->file_name = pool_strdup (x->pool, value);
479           break;
480         case 1:
481           if (ds_find_char (val, '#') != SIZE_MAX)
482             x->chart_file_name = pool_strdup (x->pool, value);
483           else
484             error (0, 0, _("`chart-files' value must contain `#'"));
485           break;
486         case 2:
487           if (value[0] != '\0')
488             x->chart_type = pool_strdup (x->pool, value);
489           else
490             x->chart_type = NULL;
491           break;
492         case 3:
493           x->init = pool_strdup (x->pool, value);
494           break;
495         }
496       break;
497     default:
498       NOT_REACHED ();
499     }
500
501   return true;
502 }
503
504 static void
505 ascii_open_page (struct outp_driver *this)
506 {
507   struct ascii_driver_ext *x = this->ext;
508   int i;
509
510   update_page_size (this, false);
511
512   if (x->file == NULL)
513     {
514       x->file = fn_open (x->file_name, x->append ? "a" : "w");
515       if (x->file != NULL)
516         {
517           pool_attach_file (x->pool, x->file);
518           if (x->init != NULL)
519             fputs (x->init, x->file);
520         }
521       else
522         {
523           /* Report the error to the user and complete
524              initialization.  If we do not finish initialization,
525              then calls to other driver functions will segfault
526              later.  It would be better to simply drop the driver
527              entirely, but we do not have a convenient mechanism
528              for this (yet). */
529           if (!x->reported_error)
530             error (0, errno, _("ascii: opening output file \"%s\""),
531                    x->file_name);
532           x->reported_error = true;
533         }
534     }
535
536   x->page_number++;
537
538   if (this->length > x->line_cap)
539     {
540       x->lines = pool_nrealloc (x->pool,
541                                 x->lines, this->length, sizeof *x->lines);
542       for (i = x->line_cap; i < this->length; i++)
543         {
544           struct line *line = &x->lines[i];
545           line->chars = NULL;
546           line->char_cap = 0;
547         }
548       x->line_cap = this->length;
549     }
550
551   for (i = 0; i < this->length; i++)
552     x->lines[i].char_cnt = 0;
553 }
554
555 /* Ensures that at least the first LENGTH characters of line Y in
556    THIS driver identified X have been cleared out. */
557 static inline void
558 expand_line (struct outp_driver *this, int y, int length)
559 {
560   struct ascii_driver_ext *ext = this->ext;
561   struct line *line = &ext->lines[y];
562   if (line->char_cnt < length)
563     {
564       int x;
565       if (line->char_cap < length)
566         {
567           line->char_cap = MIN (length * 2, this->width);
568           line->chars = pool_nrealloc (ext->pool,
569                                        line->chars,
570                                        line->char_cap, sizeof *line->chars);
571         }
572       for (x = line->char_cnt; x < length; x++)
573         line->chars[x] = ' ';
574       line->char_cnt = length;
575     }
576 }
577
578 static void
579 ascii_line (struct outp_driver *this,
580             int x0, int y0, int x1, int y1,
581             enum outp_line_style top, enum outp_line_style left,
582             enum outp_line_style bottom, enum outp_line_style right)
583 {
584   struct ascii_driver_ext *ext = this->ext;
585   int y;
586   unsigned short value;
587
588   assert (this->page_open);
589 #if DEBUGGING
590   if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
591     {
592 #if !SUPPRESS_WARNINGS
593       printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
594               x0, y0, x1, y1, this->width, this->length);
595 #endif
596       return;
597     }
598 #endif
599
600   value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
601            | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
602   for (y = y0; y < y1; y++)
603     {
604       int x;
605
606       expand_line (this, y, x1);
607       for (x = x0; x < x1; x++)
608         ext->lines[y].chars[x] = value;
609     }
610 }
611
612 static void
613 ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s)
614 {
615   extern struct som_table_class tab_table_class;
616
617   assert (s->class == &tab_table_class);
618   assert (s->type == SOM_CHART);
619 }
620
621 static void
622 text_draw (struct outp_driver *this,
623            enum outp_font font,
624            int x, int y,
625            enum outp_justification justification, int width,
626            const char *string, size_t length)
627 {
628   struct ascii_driver_ext *ext = this->ext;
629   unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
630
631   int line_len;
632
633   switch (justification)
634     {
635     case OUTP_LEFT:
636       break;
637     case OUTP_CENTER:
638       x += (width - length + 1) / 2;
639       break;
640     case OUTP_RIGHT:
641       x += width - length;
642       break;
643     default:
644       NOT_REACHED ();
645     }
646
647   if (y >= this->length || x >= this->width)
648     return;
649
650   if (x + length > this->width)
651     length = this->width - x;
652
653   line_len = x + length;
654
655   expand_line (this, y, line_len);
656   while (length-- > 0)
657     ext->lines[y].chars[x++] = *string++ | attr;
658 }
659
660 /* Divides the text T->S into lines of width T->H.  Sets *WIDTH
661    to the maximum width of a line and *HEIGHT to the number of
662    lines, if those arguments are non-null.  Actually draws the
663    text if DRAW is true. */
664 static void
665 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
666            int *width, int *height)
667 {
668   int max_width;
669   int height_left;
670
671   const char *cp = ss_data (text->string);
672
673   max_width = 0;
674   height_left = text->v;
675
676   while (height_left > 0)
677     {
678       size_t chars_left;
679       size_t line_len;
680       const char *end;
681
682       /* Initially the line is up to text->h characters long. */
683       chars_left = ss_end (text->string) - cp;
684       if (chars_left == 0)
685         break;
686       line_len = MIN (chars_left, text->h);
687
688       /* A new-line terminates the line prematurely. */
689       end = memchr (cp, '\n', line_len);
690       if (end != NULL)
691         line_len = end - cp;
692
693       /* Don't cut off words if it can be avoided. */
694       if (cp + line_len < ss_end (text->string))
695         {
696           size_t space_len = line_len;
697           while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
698             space_len--;
699           if (space_len > 0)
700             line_len = space_len;
701         }
702
703       /* Draw text. */
704       if (draw)
705         text_draw (this,
706                    text->font,
707                    text->x, text->y + (text->v - height_left),
708                    text->justification, text->h,
709                    cp, line_len);
710
711       /* Update. */
712       height_left--;
713       if (line_len > max_width)
714         max_width = line_len;
715
716       /* Next line. */
717       cp += line_len;
718       if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
719         cp++;
720     }
721
722   if (width != NULL)
723     *width = max_width;
724   if (height != NULL)
725     *height = text->v - height_left;
726 }
727
728 static void
729 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
730                     int *width, int *height)
731 {
732   delineate (this, t, false, width, height);
733 }
734
735 static void
736 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
737 {
738   assert (this->page_open);
739   delineate (this, t, true, NULL, NULL);
740 }
741 \f
742 /* ascii_close_page () and support routines. */
743
744 /* Writes the LENGTH characters in S to OUT.  */
745 static void
746 output_line (struct outp_driver *this, const struct line *line,
747              struct string *out)
748 {
749   struct ascii_driver_ext *ext = this->ext;
750   const unsigned short *s = line->chars;
751   size_t length;
752
753   for (length = line->char_cnt; length-- > 0; s++)
754     if (*s & ATTR_BOX)
755       ds_put_cstr (out, ext->box[*s & 0xff]);
756     else
757       {
758         if (*s & ATTR_EMPHASIS)
759           {
760             if (ext->emphasis == EMPH_BOLD)
761               {
762                 ds_put_char (out, *s);
763                 ds_put_char (out, '\b');
764               }
765             else if (ext->emphasis == EMPH_UNDERLINE)
766               ds_put_cstr (out, "_\b");
767           }
768         ds_put_char (out, *s);
769       }
770 }
771
772 static void
773 append_lr_justified (struct string *out, int width,
774                      const char *left, const char *right)
775 {
776   ds_put_char_multiple (out, ' ', width);
777   if (left != NULL)
778     {
779       size_t length = MIN (strlen (left), width);
780       memcpy (ds_end (out) - width, left, length);
781     }
782   if (right != NULL)
783     {
784       size_t length = MIN (strlen (right), width);
785       memcpy (ds_end (out) - length, right, length);
786     }
787   ds_put_char (out, '\n');
788 }
789
790 static void
791 dump_output (struct outp_driver *this, struct string *out)
792 {
793   struct ascii_driver_ext *x = this->ext;
794   fwrite (ds_data (out), ds_length (out), 1, x->file);
795   ds_clear (out);
796 }
797
798 static void
799 ascii_close_page (struct outp_driver *this)
800 {
801   struct ascii_driver_ext *x = this->ext;
802   struct string out;
803   int line_num;
804
805   if (x->file == NULL)
806     return;
807
808   ds_init_empty (&out);
809
810   ds_put_char_multiple (&out, '\n', x->top_margin);
811   if (x->headers)
812     {
813       char *r1, *r2;
814
815       r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
816       r2 = xasprintf ("%s - %s" , version, host_system);
817
818       append_lr_justified (&out, this->width, outp_title, r1);
819       append_lr_justified (&out, this->width, outp_subtitle, r2);
820       ds_put_char (&out, '\n');
821
822       free (r1);
823       free (r2);
824     }
825   dump_output (this, &out);
826
827   for (line_num = 0; line_num < this->length; line_num++)
828     {
829
830       /* Squeeze multiple blank lines into a single blank line if
831          requested. */
832       if (x->squeeze_blank_lines)
833         {
834           if (line_num >= x->line_cap)
835             break;
836           if (line_num > 0
837               && x->lines[line_num].char_cnt == 0
838               && x->lines[line_num - 1].char_cnt == 0)
839             continue;
840         }
841
842       if (line_num < x->line_cap)
843         output_line (this, &x->lines[line_num], &out);
844       ds_put_char (&out, '\n');
845       dump_output (this, &out);
846     }
847
848   ds_put_char_multiple (&out, '\n', x->bottom_margin);
849   if (x->paginate)
850     ds_put_char (&out, '\f');
851
852   dump_output (this, &out);
853   ds_destroy (&out);
854 }
855
856 /* Flushes all output to the user and lets the user deal with it.
857    This is applied only to output drivers that are designated as
858    "screen" drivers that the user is interacting with in real
859    time. */
860 static void
861 ascii_flush (struct outp_driver *this)
862 {
863   struct ascii_driver_ext *x = this->ext;
864   if (x->file != NULL)
865     {
866       if (fn_close (x->file_name, x->file) != 0)
867         error (0, errno, _("ascii: closing output file \"%s\""),
868                x->file_name);
869       pool_detach_file (x->pool, x->file);
870       x->file = NULL;
871     }
872 }
873
874 static void
875 ascii_chart_initialise (struct outp_driver *this, struct chart *ch)
876 {
877   struct ascii_driver_ext *x = this->ext;
878   struct outp_text t;
879   char *text;
880
881   if (x->chart_type == NULL)
882     return;
883
884   /* Initialize chart. */
885   chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt);
886   if (ch->file_name == NULL)
887     return;
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.", ch->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 (text);
916 }
917
918 static void
919 ascii_chart_finalise (struct outp_driver *this, struct chart *ch)
920 {
921   struct ascii_driver_ext *x = this->ext;
922   if (x->chart_type != NULL)
923     chart_finalise_separate (ch);
924 }
925
926 const struct outp_class ascii_class =
927 {
928   "ascii",
929   0,
930
931   ascii_open_driver,
932   ascii_close_driver,
933
934   ascii_open_page,
935   ascii_close_page,
936   ascii_flush,
937
938   ascii_submit,
939
940   ascii_line,
941   ascii_text_metrics,
942   ascii_text_draw,
943
944   ascii_chart_initialise,
945   ascii_chart_finalise
946 };