output: Use Cairo and Pango to draw charts, instead of libplot.
[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|none
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     bool enable_charts;         /* Enable charts? */
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->enable_charts = true;
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 (!strcmp (value, "png"))
489             x->enable_charts = true;
490           else if (!strcmp (value, "none"))
491             x->enable_charts = false;
492           else
493             {
494               error (0, 0,
495                      _("ascii: `png' or `none' expected for `chart-type'"));
496               return false;
497             }
498           break;
499         case 3:
500           x->init = pool_strdup (x->pool, value);
501           break;
502         }
503       break;
504     default:
505       NOT_REACHED ();
506     }
507
508   return true;
509 }
510
511 static void
512 ascii_open_page (struct outp_driver *this)
513 {
514   struct ascii_driver_ext *x = this->ext;
515   int i;
516
517   update_page_size (this, false);
518
519   if (x->file == NULL)
520     {
521       x->file = fn_open (x->file_name, x->append ? "a" : "w");
522       if (x->file != NULL)
523         {
524           pool_attach_file (x->pool, x->file);
525           if (x->init != NULL)
526             fputs (x->init, x->file);
527         }
528       else
529         {
530           /* Report the error to the user and complete
531              initialization.  If we do not finish initialization,
532              then calls to other driver functions will segfault
533              later.  It would be better to simply drop the driver
534              entirely, but we do not have a convenient mechanism
535              for this (yet). */
536           if (!x->reported_error)
537             error (0, errno, _("ascii: opening output file \"%s\""),
538                    x->file_name);
539           x->reported_error = true;
540         }
541     }
542
543   x->page_number++;
544
545   if (this->length > x->line_cap)
546     {
547       x->lines = pool_nrealloc (x->pool,
548                                 x->lines, this->length, sizeof *x->lines);
549       for (i = x->line_cap; i < this->length; i++)
550         {
551           struct line *line = &x->lines[i];
552           line->chars = NULL;
553           line->char_cap = 0;
554         }
555       x->line_cap = this->length;
556     }
557
558   for (i = 0; i < this->length; i++)
559     x->lines[i].char_cnt = 0;
560 }
561
562 /* Ensures that at least the first LENGTH characters of line Y in
563    THIS driver identified X have been cleared out. */
564 static inline void
565 expand_line (struct outp_driver *this, int y, int length)
566 {
567   struct ascii_driver_ext *ext = this->ext;
568   struct line *line = &ext->lines[y];
569   if (line->char_cnt < length)
570     {
571       int x;
572       if (line->char_cap < length)
573         {
574           line->char_cap = MIN (length * 2, this->width);
575           line->chars = pool_nrealloc (ext->pool,
576                                        line->chars,
577                                        line->char_cap, sizeof *line->chars);
578         }
579       for (x = line->char_cnt; x < length; x++)
580         line->chars[x] = ' ';
581       line->char_cnt = length;
582     }
583 }
584
585 static void
586 ascii_line (struct outp_driver *this,
587             int x0, int y0, int x1, int y1,
588             enum outp_line_style top, enum outp_line_style left,
589             enum outp_line_style bottom, enum outp_line_style right)
590 {
591   struct ascii_driver_ext *ext = this->ext;
592   int y;
593   unsigned short value;
594
595   assert (this->page_open);
596 #if DEBUGGING
597   if (x0 < 0 || x1 > this->width || y0 < 0 || y1 > this->length)
598     {
599 #if !SUPPRESS_WARNINGS
600       printf (_("ascii: bad line (%d,%d)-(%d,%d) out of (%d,%d)\n"),
601               x0, y0, x1, y1, this->width, this->length);
602 #endif
603       return;
604     }
605 #endif
606
607   value = ((left << LNS_LEFT) | (right << LNS_RIGHT)
608            | (top << LNS_TOP) | (bottom << LNS_BOTTOM) | ATTR_BOX);
609   for (y = y0; y < y1; y++)
610     {
611       int x;
612
613       expand_line (this, y, x1);
614       for (x = x0; x < x1; x++)
615         ext->lines[y].chars[x] = value;
616     }
617 }
618
619 static void
620 text_draw (struct outp_driver *this,
621            enum outp_font font,
622            int x, int y,
623            enum outp_justification justification, int width,
624            const char *string, size_t length)
625 {
626   struct ascii_driver_ext *ext = this->ext;
627   unsigned short attr = font == OUTP_EMPHASIS ? ATTR_EMPHASIS : 0;
628
629   int line_len;
630
631   switch (justification)
632     {
633     case OUTP_LEFT:
634       break;
635     case OUTP_CENTER:
636       x += (width - length + 1) / 2;
637       break;
638     case OUTP_RIGHT:
639       x += width - length;
640       break;
641     default:
642       NOT_REACHED ();
643     }
644
645   if (y >= this->length || x >= this->width)
646     return;
647
648   if (x + length > this->width)
649     length = this->width - x;
650
651   line_len = x + length;
652
653   expand_line (this, y, line_len);
654   while (length-- > 0)
655     ext->lines[y].chars[x++] = *string++ | attr;
656 }
657
658 /* Divides the text T->S into lines of width T->H.  Sets *WIDTH
659    to the maximum width of a line and *HEIGHT to the number of
660    lines, if those arguments are non-null.  Actually draws the
661    text if DRAW is true. */
662 static void
663 delineate (struct outp_driver *this, const struct outp_text *text, bool draw,
664            int *width, int *height)
665 {
666   int max_width;
667   int height_left;
668
669   const char *cp = ss_data (text->string);
670
671   max_width = 0;
672   height_left = text->v;
673
674   while (height_left > 0)
675     {
676       size_t chars_left;
677       size_t line_len;
678       const char *end;
679
680       /* Initially the line is up to text->h characters long. */
681       chars_left = ss_end (text->string) - cp;
682       if (chars_left == 0)
683         break;
684       line_len = MIN (chars_left, text->h);
685
686       /* A new-line terminates the line prematurely. */
687       end = memchr (cp, '\n', line_len);
688       if (end != NULL)
689         line_len = end - cp;
690
691       /* Don't cut off words if it can be avoided. */
692       if (cp + line_len < ss_end (text->string))
693         {
694           size_t space_len = line_len;
695           while (space_len > 0 && !isspace ((unsigned char) cp[space_len]))
696             space_len--;
697           if (space_len > 0)
698             line_len = space_len;
699         }
700
701       /* Draw text. */
702       if (draw)
703         text_draw (this,
704                    text->font,
705                    text->x, text->y + (text->v - height_left),
706                    text->justification, text->h,
707                    cp, line_len);
708
709       /* Update. */
710       height_left--;
711       if (line_len > max_width)
712         max_width = line_len;
713
714       /* Next line. */
715       cp += line_len;
716       if (cp < ss_end (text->string) && isspace ((unsigned char) *cp))
717         cp++;
718     }
719
720   if (width != NULL)
721     *width = max_width;
722   if (height != NULL)
723     *height = text->v - height_left;
724 }
725
726 static void
727 ascii_text_metrics (struct outp_driver *this, const struct outp_text *t,
728                     int *width, int *height)
729 {
730   delineate (this, t, false, width, height);
731 }
732
733 static void
734 ascii_text_draw (struct outp_driver *this, const struct outp_text *t)
735 {
736   assert (this->page_open);
737   delineate (this, t, true, NULL, NULL);
738 }
739 \f
740 /* ascii_close_page () and support routines. */
741
742 /* Writes the LENGTH characters in S to OUT.  */
743 static void
744 output_line (struct outp_driver *this, const struct line *line,
745              struct string *out)
746 {
747   struct ascii_driver_ext *ext = this->ext;
748   const unsigned short *s = line->chars;
749   size_t length;
750
751   for (length = line->char_cnt; length-- > 0; s++)
752     if (*s & ATTR_BOX)
753       ds_put_cstr (out, ext->box[*s & 0xff]);
754     else
755       {
756         if (*s & ATTR_EMPHASIS)
757           {
758             if (ext->emphasis == EMPH_BOLD)
759               {
760                 ds_put_char (out, *s);
761                 ds_put_char (out, '\b');
762               }
763             else if (ext->emphasis == EMPH_UNDERLINE)
764               ds_put_cstr (out, "_\b");
765           }
766         ds_put_char (out, *s);
767       }
768 }
769
770 static void
771 append_lr_justified (struct string *out, int width,
772                      const char *left, const char *right)
773 {
774   ds_put_char_multiple (out, ' ', width);
775   if (left != NULL)
776     {
777       size_t length = MIN (strlen (left), width);
778       memcpy (ds_end (out) - width, left, length);
779     }
780   if (right != NULL)
781     {
782       size_t length = MIN (strlen (right), width);
783       memcpy (ds_end (out) - length, right, length);
784     }
785   ds_put_char (out, '\n');
786 }
787
788 static void
789 dump_output (struct outp_driver *this, struct string *out)
790 {
791   struct ascii_driver_ext *x = this->ext;
792   fwrite (ds_data (out), ds_length (out), 1, x->file);
793   ds_clear (out);
794 }
795
796 static void
797 ascii_close_page (struct outp_driver *this)
798 {
799   struct ascii_driver_ext *x = this->ext;
800   struct string out;
801   int line_num;
802
803   if (x->file == NULL)
804     return;
805
806   ds_init_empty (&out);
807
808   ds_put_char_multiple (&out, '\n', x->top_margin);
809   if (x->headers)
810     {
811       char *r1, *r2;
812
813       r1 = xasprintf (_("%s - Page %d"), get_start_date (), x->page_number);
814       r2 = xasprintf ("%s - %s" , version, host_system);
815
816       append_lr_justified (&out, this->width, outp_title, r1);
817       append_lr_justified (&out, this->width, outp_subtitle, r2);
818       ds_put_char (&out, '\n');
819
820       free (r1);
821       free (r2);
822     }
823   dump_output (this, &out);
824
825   for (line_num = 0; line_num < this->length; line_num++)
826     {
827
828       /* Squeeze multiple blank lines into a single blank line if
829          requested. */
830       if (x->squeeze_blank_lines)
831         {
832           if (line_num >= x->line_cap)
833             break;
834           if (line_num > 0
835               && x->lines[line_num].char_cnt == 0
836               && x->lines[line_num - 1].char_cnt == 0)
837             continue;
838         }
839
840       if (line_num < x->line_cap)
841         output_line (this, &x->lines[line_num], &out);
842       ds_put_char (&out, '\n');
843       dump_output (this, &out);
844     }
845
846   ds_put_char_multiple (&out, '\n', x->bottom_margin);
847   if (x->paginate)
848     ds_put_char (&out, '\f');
849
850   dump_output (this, &out);
851   ds_destroy (&out);
852 }
853
854 /* Flushes all output to the user and lets the user deal with it.
855    This is applied only to output drivers that are designated as
856    "screen" drivers that the user is interacting with in real
857    time. */
858 static void
859 ascii_flush (struct outp_driver *this)
860 {
861   struct ascii_driver_ext *x = this->ext;
862   if (x->file != NULL)
863     {
864       if (fn_close (x->file_name, x->file) != 0)
865         error (0, errno, _("ascii: closing output file \"%s\""),
866                x->file_name);
867       pool_detach_file (x->pool, x->file);
868       x->file = NULL;
869     }
870 }
871
872 static void
873 ascii_output_chart (struct outp_driver *this, const struct chart *chart)
874 {
875   struct ascii_driver_ext *x = this->ext;
876   struct outp_text t;
877   char *file_name;
878   char *text;
879
880   /* Draw chart into separate file */
881   file_name = chart_draw_png (chart, x->chart_file_name, x->chart_cnt++);
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.", 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 (file_name);
910   free (text);
911 }
912
913 const struct outp_class ascii_class =
914 {
915   "ascii",
916   0,
917
918   ascii_open_driver,
919   ascii_close_driver,
920
921   ascii_open_page,
922   ascii_close_page,
923   ascii_flush,
924
925   ascii_output_chart,
926
927   NULL,                         /* submit */
928
929   ascii_line,
930   ascii_text_metrics,
931   ascii_text_draw,
932 };