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