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