4978e125cc67829c1fd9356f838c3a39f4e36b52
[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/start-date.h>
30 #include <libpspp/string-map.h>
31 #include <libpspp/version.h>
32 #include <output/cairo.h>
33 #include <output/chart-item-provider.h>
34 #include "output/options.h"
35 #include <output/tab.h>
36 #include <output/text-item.h>
37 #include <output/driver-provider.h>
38 #include <output/render.h>
39 #include <output/table-item.h>
40
41 #include "error.h"
42 #include "minmax.h"
43 #include "xalloc.h"
44
45 #include "gettext.h"
46 #define _(msgid) gettext (msgid)
47
48 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
49 #define H TABLE_HORZ
50 #define V TABLE_VERT
51
52 /* Line styles bit shifts. */
53 enum
54   {
55     LNS_TOP = 0,
56     LNS_LEFT = 2,
57     LNS_BOTTOM = 4,
58     LNS_RIGHT = 6,
59
60     LNS_COUNT = 256
61   };
62
63 static inline int
64 make_box_index (int left, int right, int top, int bottom)
65 {
66   return ((left << LNS_LEFT) | (right << LNS_RIGHT)
67           | (top << LNS_TOP) | (bottom << LNS_BOTTOM));
68 }
69
70 /* Character attributes. */
71 #define ATTR_EMPHASIS   0x100   /* Bold-face. */
72 #define ATTR_BOX        0x200   /* Line drawing character. */
73
74 /* A line of text. */
75 struct ascii_line
76   {
77     unsigned short *chars;      /* Characters and attributes. */
78     int n_chars;                /* Length. */
79     int allocated_chars;        /* Allocated "chars" elements. */
80   };
81
82 /* How to emphasize text. */
83 enum emphasis_style
84   {
85     EMPH_BOLD,                  /* Overstrike for bold. */
86     EMPH_UNDERLINE,             /* Overstrike for underlining. */
87     EMPH_NONE                   /* No emphasis. */
88   };
89
90 /* ASCII output driver. */
91 struct ascii_driver
92   {
93     struct output_driver driver;
94
95     /* User parameters. */
96     bool append;                /* Append if output-file already exists? */
97     bool headers;               /* Print headers at top of page? */
98     bool paginate;              /* Insert formfeeds? */
99     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
100     enum emphasis_style emphasis; /* How to emphasize text. */
101     int tab_width;              /* Width of a tab; 0 not to use tabs. */
102     char *chart_file_name;      /* Name of files used for charts. */
103
104     int width;                  /* Page width. */
105     int length;                 /* Page length minus margins and header. */
106     bool auto_width;            /* Use viewwidth as page width? */
107     bool auto_length;           /* Use viewlength as page width? */
108
109     int top_margin;             /* Top margin in lines. */
110     int bottom_margin;          /* Bottom margin in lines. */
111
112     char *box[LNS_COUNT];       /* Line & box drawing characters. */
113     char *init;                 /* Device initialization string. */
114
115     /* Internal state. */
116     char *title;
117     char *subtitle;
118     char *file_name;            /* Output file name. */
119     FILE *file;                 /* Output file. */
120     bool reported_error;        /* Reported file open error? */
121     int page_number;            /* Current page number. */
122     struct ascii_line *lines;   /* Page content. */
123     int allocated_lines;        /* Number of lines allocated. */
124     int chart_cnt;              /* Number of charts so far. */
125     int y;
126   };
127
128 static int vertical_margins (const struct ascii_driver *);
129
130 static const char *get_default_box (int right, int bottom, int left, int top);
131 static bool update_page_size (struct ascii_driver *, bool issue_error);
132 static int parse_page_size (struct driver_option *);
133
134 static void ascii_close_page (struct ascii_driver *);
135 static void ascii_open_page (struct ascii_driver *);
136
137 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
138                              enum render_line_style styles[TABLE_N_AXES][2]);
139 static void ascii_measure_cell_width (void *, const struct table_cell *,
140                                       int *min, int *max);
141 static int ascii_measure_cell_height (void *, const struct table_cell *,
142                                       int width);
143 static void ascii_draw_cell (void *, const struct table_cell *,
144                              int bb[TABLE_N_AXES][2],
145                              int clip[TABLE_N_AXES][2]);
146
147 static struct ascii_driver *
148 ascii_driver_cast (struct output_driver *driver)
149 {
150   assert (driver->class == &ascii_class);
151   return UP_CAST (driver, struct ascii_driver, driver);
152 }
153
154 static struct driver_option *
155 opt (struct output_driver *d, struct string_map *options, const char *key,
156      const char *default_value)
157 {
158   return driver_option_get (d, options, key, default_value);
159 }
160
161 static struct output_driver *
162 ascii_create (const char *name, enum output_device_type device_type,
163               struct string_map *o)
164 {
165   struct output_driver *d;
166   struct ascii_driver *a;
167   int paper_length;
168   int right, bottom, left, top;
169
170   a = xzalloc (sizeof *a);
171   d = &a->driver;
172   output_driver_init (&a->driver, &ascii_class, name, device_type);
173   a->append = parse_boolean (opt (d, o, "append", "false"));
174   a->headers = parse_boolean (opt (d, o, "headers", "true"));
175   a->paginate = parse_boolean (opt (d, o, "paginate", "true"));
176   a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "false"));
177   a->emphasis = parse_enum (opt (d, o, "emphasis", "bold"),
178                             "bold", EMPH_BOLD,
179                             "underline", EMPH_UNDERLINE,
180                             "none", EMPH_NONE,
181                             (char *) NULL);
182   a->tab_width = parse_int (opt (d, o, "tab-width", "0"), 8, INT_MAX);
183
184   if (parse_enum (opt (d, o, "chart-type", "png"),
185                   "png", true,
186                   "none", false,
187                   (char *) NULL))
188     a->chart_file_name = parse_chart_file_name (opt (d, o, "chart-files",
189                                                      "pspp-#.png"));
190   else
191     a->chart_file_name = NULL;
192
193   a->top_margin = parse_int (opt (d, o, "top-margin", "2"), 0, INT_MAX);
194   a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "2"), 0, INT_MAX);
195
196   a->width = parse_page_size (opt (d, o, "width", "79"));
197   paper_length = parse_page_size (opt (d, o, "length", "66"));
198   a->auto_width = a->width < 0;
199   a->auto_length = paper_length < 0;
200   a->length = paper_length - vertical_margins (a);
201
202   for (right = 0; right < 4; right++)
203     for (bottom = 0; bottom < 4; bottom++)
204       for (left = 0; left < 4; left++)
205         for (top = 0; top < 4; top++)
206           {
207             int indx = make_box_index (left, right, top, bottom);
208             const char *default_value;
209             char name[16];
210
211             sprintf (name, "box[%d%d%d%d]", right, bottom, left, top);
212             default_value = get_default_box (right, bottom, left, top);
213             a->box[indx] = parse_string (opt (d, o, name, default_value));
214           }
215   a->init = parse_string (opt (d, o, "init", ""));
216
217   a->title = xstrdup ("");
218   a->subtitle = xstrdup ("");
219   a->file_name = parse_string (opt (d, o, "output-file", "pspp.list"));
220   a->file = NULL;
221   a->reported_error = false;
222   a->page_number = 0;
223   a->lines = NULL;
224   a->allocated_lines = 0;
225   a->chart_cnt = 1;
226
227   if (!update_page_size (a, true))
228     goto error;
229
230   return d;
231
232 error:
233   output_driver_destroy (d);
234   return NULL;
235 }
236
237 static const char *
238 get_default_box (int right, int bottom, int left, int top)
239 {
240   switch ((top << 12) | (left << 8) | (bottom << 4) | (right << 0))
241     {
242     case 0x0000:
243       return " ";
244
245     case 0x0100: case 0x0101: case 0x0001:
246       return "-";
247
248     case 0x1000: case 0x1010: case 0x0010:
249       return "|";
250
251     case 0x0300: case 0x0303: case 0x0003:
252     case 0x0200: case 0x0202: case 0x0002:
253       return "=";
254
255     default:
256       return left > 1 || top > 1 || right > 1 || bottom > 1 ? "#" : "+";
257     }
258 }
259
260 static int
261 parse_page_size (struct driver_option *option)
262 {
263   int dim = atol (option->default_value);
264
265   if (option->value != NULL)
266     {
267       if (!strcmp (option->value, "auto"))
268         dim = -1;
269       else
270         {
271           int value;
272           char *tail;
273
274           errno = 0;
275           value = strtol (option->value, &tail, 0);
276           if (dim >= 1 && errno != ERANGE && *tail == '\0')
277             dim = value;
278           else
279             error (0, 0, _("%s: %s must be positive integer or `auto'"),
280                    option->driver_name, option->name);
281         }
282     }
283
284   driver_option_destroy (option);
285
286   return dim;
287 }
288
289 static int
290 vertical_margins (const struct ascii_driver *a)
291 {
292   return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
293 }
294
295 /* Re-calculates the page width and length based on settings,
296    margins, and, if "auto" is set, the size of the user's
297    terminal window or GUI output window. */
298 static bool
299 update_page_size (struct ascii_driver *a, bool issue_error)
300 {
301   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
302
303   if (a->auto_width)
304     a->width = settings_get_viewwidth ();
305   if (a->auto_length)
306     a->length = settings_get_viewlength () - vertical_margins (a);
307
308   if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
309     {
310       if (issue_error)
311         error (0, 0,
312                _("ascii: page excluding margins and headers "
313                  "must be at least %d characters wide by %d lines long, but "
314                  "as configured is only %d characters by %d lines"),
315                MIN_WIDTH, MIN_LENGTH,
316                a->width, a->length);
317       if (a->width < MIN_WIDTH)
318         a->width = MIN_WIDTH;
319       if (a->length < MIN_LENGTH)
320         a->length = MIN_LENGTH;
321       return false;
322     }
323
324   return true;
325 }
326
327 static void
328 ascii_destroy (struct output_driver *driver)
329 {
330   struct ascii_driver *a = ascii_driver_cast (driver);
331   int i;
332
333   if (a->y > 0)
334     ascii_close_page (a);
335
336   free (a->title);
337   free (a->subtitle);
338   free (a->file_name);
339   free (a->chart_file_name);
340   for (i = 0; i < LNS_COUNT; i++)
341     free (a->box[i]);
342   free (a->init);
343   if (a->file != NULL)
344     fclose (a->file);
345   for (i = 0; i < a->allocated_lines; i++)
346     free (a->lines[i].chars);
347   free (a->lines);
348   free (a);
349 }
350
351 static void
352 ascii_flush (struct output_driver *driver)
353 {
354   struct ascii_driver *a = ascii_driver_cast (driver);
355   if (a->file != NULL)
356     fflush (a->file);
357 }
358
359 static void
360 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
361 {
362   cell->contents = caption;
363   cell->options = TAB_LEFT;
364   cell->destructor = NULL;
365 }
366
367 static void
368 ascii_submit (struct output_driver *driver,
369               const struct output_item *output_item)
370 {
371   struct ascii_driver *a = ascii_driver_cast (driver);
372   if (is_table_item (output_item))
373     {
374       struct table_item *table_item = to_table_item (output_item);
375       const char *caption = table_item_get_caption (table_item);
376       struct render_params params;
377       struct render_page *page;
378       struct render_break x_break;
379       int caption_height;
380       int i;
381
382       update_page_size (a, false);
383
384       if (caption != NULL)
385         {
386           /* XXX doesn't do well with very large captions */
387           struct table_cell cell;
388           ascii_init_caption_cell (caption, &cell);
389           caption_height = ascii_measure_cell_height (a, &cell, a->width);
390         }
391       else
392         caption_height = 0;
393
394       params.draw_line = ascii_draw_line;
395       params.measure_cell_width = ascii_measure_cell_width;
396       params.measure_cell_height = ascii_measure_cell_height;
397       params.draw_cell = ascii_draw_cell,
398       params.aux = a;
399       params.size[H] = a->width;
400       params.size[V] = a->length - caption_height;
401       params.font_size[H] = 1;
402       params.font_size[V] = 1;
403       for (i = 0; i < RENDER_N_LINES; i++)
404         {
405           int width = i == RENDER_LINE_NONE ? 0 : 1;
406           params.line_widths[H][i] = width;
407           params.line_widths[V][i] = width;
408         }
409
410       if (a->file == NULL)
411         {
412           ascii_open_page (a);
413           a->y = 0;
414         }
415
416       page = render_page_create (&params, table_item_get_table (table_item));
417       for (render_break_init (&x_break, page, H);
418            render_break_has_next (&x_break); )
419         {
420           struct render_page *x_slice;
421           struct render_break y_break;
422
423           x_slice = render_break_next (&x_break, a->width);
424           for (render_break_init (&y_break, x_slice, V);
425                render_break_has_next (&y_break); )
426             {
427               struct render_page *y_slice;
428               int space;
429
430               if (a->y > 0)
431                 a->y++;
432
433               space = a->length - a->y - caption_height;
434               if (render_break_next_size (&y_break) > space)
435                 {
436                   assert (a->y > 0);
437                   ascii_close_page (a);
438                   a->y = 0;
439                   ascii_open_page (a);
440                   continue;
441                 }
442
443               y_slice = render_break_next (&y_break, space);
444               if (caption_height)
445                 {
446                   struct table_cell cell;
447                   int bb[TABLE_N_AXES][2];
448
449                   ascii_init_caption_cell (caption, &cell);
450                   bb[H][0] = 0;
451                   bb[H][1] = a->width;
452                   bb[V][0] = 0;
453                   bb[V][1] = caption_height;
454                   ascii_draw_cell (a, &cell, bb, bb);
455                   a->y += caption_height;
456                   caption_height = 0;
457                 }
458               render_page_draw (y_slice);
459               a->y += render_page_get_size (y_slice, V);
460               render_page_unref (y_slice);
461             }
462           render_break_destroy (&y_break);
463         }
464       render_break_destroy (&x_break);
465     }
466   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
467     {
468       struct chart_item *chart_item = to_chart_item (output_item);
469       char *file_name;
470
471       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
472                                      a->chart_cnt++);
473       if (file_name != NULL)
474         {
475           struct text_item *text_item;
476
477           text_item = text_item_create_format (
478             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
479
480           ascii_submit (driver, &text_item->output_item);
481           text_item_unref (text_item);
482           free (file_name);
483         }
484     }
485   else if (is_text_item (output_item))
486     {
487       const struct text_item *text_item = to_text_item (output_item);
488       enum text_item_type type = text_item_get_type (text_item);
489       const char *text = text_item_get_text (text_item);
490
491       switch (type)
492         {
493         case TEXT_ITEM_TITLE:
494           free (a->title);
495           a->title = xstrdup (text);
496           break;
497
498         case TEXT_ITEM_SUBTITLE:
499           free (a->subtitle);
500           a->subtitle = xstrdup (text);
501           break;
502
503         case TEXT_ITEM_COMMAND_CLOSE:
504           break;
505
506         case TEXT_ITEM_BLANK_LINE:
507           if (a->y > 0)
508             a->y++;
509           break;
510
511         case TEXT_ITEM_EJECT_PAGE:
512           if (a->y > 0)
513             ascii_close_page (a);
514           break;
515
516         default:
517           {
518             struct table_item *item;
519
520             item = table_item_create (table_from_string (0, text), NULL);
521             ascii_submit (&a->driver, &item->output_item);
522             table_item_unref (item);
523           }
524           break;
525         }
526     }
527 }
528
529 const struct output_driver_class ascii_class =
530   {
531     "ascii",
532     ascii_create,
533     ascii_destroy,
534     ascii_submit,
535     ascii_flush,
536   };
537 \f
538 enum wrap_mode
539   {
540     WRAP_WORD,
541     WRAP_CHAR,
542     WRAP_WORD_CHAR
543   };
544
545 static void ascii_expand_line (struct ascii_driver *, int y, int length);
546 static void ascii_layout_cell (struct ascii_driver *,
547                                const struct table_cell *,
548                                int bb[TABLE_N_AXES][2],
549                                int clip[TABLE_N_AXES][2], enum wrap_mode wrap,
550                                int *width, int *height);
551
552 static void
553 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
554                  enum render_line_style styles[TABLE_N_AXES][2])
555 {
556   struct ascii_driver *a = a_;
557   unsigned short int value;
558   int x1, y1;
559   int x, y;
560
561   /* Clip to the page. */
562   if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
563     return;
564   x1 = MIN (bb[H][1], a->width);
565   y1 = MIN (bb[V][1] + a->y, a->length);
566
567   /* Draw. */
568   value = ATTR_BOX | make_box_index (styles[V][0], styles[V][1],
569                                      styles[H][0], styles[H][1]);
570   for (y = bb[V][0] + a->y; y < y1; y++)
571     {
572       ascii_expand_line (a, y, x1);
573       for (x = bb[H][0]; x < x1; x++)
574         a->lines[y].chars[x] = value;
575     }
576 }
577
578 static void
579 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
580                           int *min_width, int *max_width)
581 {
582   struct ascii_driver *a = a_;
583   int bb[TABLE_N_AXES][2];
584   int clip[TABLE_N_AXES][2];
585   int h;
586
587   bb[H][0] = 0;
588   bb[H][1] = INT_MAX;
589   bb[V][0] = 0;
590   bb[V][1] = INT_MAX;
591   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
592   ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, max_width, &h);
593
594   if (strchr (cell->contents, ' '))
595     {
596       bb[H][1] = 1;
597       ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, min_width, &h);
598     }
599   else
600     *min_width = *max_width;
601 }
602
603 static int
604 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
605 {
606   struct ascii_driver *a = a_;
607   int bb[TABLE_N_AXES][2];
608   int clip[TABLE_N_AXES][2];
609   int w, h;
610
611   bb[H][0] = 0;
612   bb[H][1] = width;
613   bb[V][0] = 0;
614   bb[V][1] = INT_MAX;
615   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
616   ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, &w, &h);
617   return h;
618 }
619
620 static void
621 ascii_draw_cell (void *a_, const struct table_cell *cell,
622                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
623 {
624   struct ascii_driver *a = a_;
625   int w, h;
626
627   ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, &w, &h);
628 }
629
630 /* Ensures that at least the first LENGTH characters of line Y in
631    ascii driver A have been cleared out. */
632 static void
633 ascii_expand_line (struct ascii_driver *a, int y, int length)
634 {
635   struct ascii_line *line = &a->lines[y];
636   if (line->n_chars < length)
637     {
638       int x;
639       if (line->allocated_chars < length)
640         {
641           line->allocated_chars = MAX (length, MIN (length * 2, a->width));
642           line->chars = xnrealloc (line->chars, line->allocated_chars,
643                                    sizeof *line->chars);
644         }
645       for (x = line->n_chars; x < length; x++)
646         line->chars[x] = ' ';
647       line->n_chars = length;
648     }
649 }
650
651 static void
652 text_draw (struct ascii_driver *a, const struct table_cell *cell,
653            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
654            int y, const char *string, int n)
655 {
656   int x0 = MAX (0, clip[H][0]);
657   int y0 = MAX (0, clip[V][0] + a->y);
658   int x1 = clip[H][1];
659   int y1 = MIN (a->length, clip[V][1] + a->y);
660   int x;
661
662   y += a->y;
663   if (y < y0 || y >= y1)
664     return;
665
666   switch (cell->options & TAB_ALIGNMENT)
667     {
668     case TAB_LEFT:
669       x = bb[H][0];
670       break;
671     case TAB_CENTER:
672       x = (bb[H][0] + bb[H][1] - n + 1) / 2;
673       break;
674     case TAB_RIGHT:
675       x = bb[H][1] - n;
676       break;
677     default:
678       NOT_REACHED ();
679     }
680
681   if (x0 > x)
682     {
683       n -= x0 - x;
684       if (n <= 0)
685         return;
686       string += x0 - x;
687       x = x0;
688     }
689   if (x + n >= x1)
690     n = x1 - x;
691
692   if (n > 0)
693     {
694       int attr = cell->options & TAB_EMPH ? ATTR_EMPHASIS : 0;
695       size_t i;
696
697       ascii_expand_line (a, y, x + n);
698       for (i = 0; i < n; i++)
699         a->lines[y].chars[x + i] = string[i] | attr;
700     }
701 }
702
703 static void
704 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
705                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
706                    enum wrap_mode wrap, int *width, int *height)
707 {
708   size_t length = strlen (cell->contents);
709   int y, pos;
710
711   *width = 0;
712   pos = 0;
713   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
714     {
715       const char *line = &cell->contents[pos];
716       const char *new_line;
717       size_t line_len;
718
719       /* Find line length without considering word wrap. */
720       line_len = MIN (bb[H][1] - bb[H][0], length - pos);
721       new_line = memchr (line, '\n', line_len);
722       if (new_line != NULL)
723         line_len = new_line - line;
724
725       /* Word wrap. */
726       if (pos + line_len < length && wrap != WRAP_CHAR)
727         {
728           size_t space_len = line_len;
729           while (space_len > 0 && !isspace ((unsigned char) line[space_len]))
730             space_len--;
731           if (space_len > 0)
732             line_len = space_len;
733           else if (wrap == WRAP_WORD)
734             {
735               while (pos + line_len < length
736                      && !isspace ((unsigned char) line[line_len]))
737                 line_len++;
738             }
739         }
740       if (line_len > *width)
741         *width = line_len;
742
743       /* Draw text. */
744       text_draw (a, cell, bb, clip, y, line, line_len);
745
746       /* Next line. */
747       pos += line_len;
748       if (pos < length && isspace ((unsigned char) cell->contents[pos]))
749         pos++;
750     }
751   *height = y - bb[V][0];
752 }
753 \f
754 /* ascii_close_page () and support routines. */
755
756 static void
757 ascii_open_page (struct ascii_driver *a)
758 {
759   int i;
760
761   if (a->file == NULL)
762     {
763       a->file = fn_open (a->file_name, a->append ? "a" : "w");
764       if (a->file != NULL)
765         {
766           if (a->init != NULL)
767             fputs (a->init, a->file);
768         }
769       else
770         {
771           /* Report the error to the user and complete
772              initialization.  If we do not finish initialization,
773              then calls to other driver functions will segfault
774              later.  It would be better to simply drop the driver
775              entirely, but we do not have a convenient mechanism
776              for this (yet). */
777           if (!a->reported_error)
778             error (0, errno, _("ascii: opening output file \"%s\""),
779                    a->file_name);
780           a->reported_error = true;
781         }
782     }
783
784   a->page_number++;
785
786   if (a->length > a->allocated_lines)
787     {
788       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
789       for (i = a->allocated_lines; i < a->length; i++)
790         {
791           struct ascii_line *line = &a->lines[i];
792           line->chars = NULL;
793           line->allocated_chars = 0;
794         }
795       a->allocated_lines = a->length;
796     }
797
798   for (i = 0; i < a->length; i++)
799     a->lines[i].n_chars = 0;
800 }
801
802 /* Writes LINE to A's output file.  */
803 static void
804 output_line (struct ascii_driver *a, const struct ascii_line *line)
805 {
806   size_t length;
807   size_t i;
808
809   length = line->n_chars;
810   while (length > 0 && line->chars[length - 1] == ' ')
811     length--;
812
813   for (i = 0; i < length; i++)
814     {
815       int attribute = line->chars[i] & (ATTR_BOX | ATTR_EMPHASIS);
816       int ch = line->chars[i] & ~(ATTR_BOX | ATTR_EMPHASIS);
817
818       switch (attribute)
819         {
820         case ATTR_BOX:
821           fputs (a->box[ch], a->file);
822           break;
823
824         case ATTR_EMPHASIS:
825           if (a->emphasis == EMPH_BOLD)
826             fprintf (a->file, "%c\b%c", ch, ch);
827           else if (a->emphasis == EMPH_UNDERLINE)
828             fprintf (a->file, "_\b%c", ch);
829           else
830             putc (ch, a->file);
831           break;
832
833         default:
834           putc (ch, a->file);
835           break;
836         }
837     }
838
839   putc ('\n', a->file);
840 }
841
842 static void
843 output_title_line (FILE *out, int width, const char *left, const char *right)
844 {
845   struct string s = DS_EMPTY_INITIALIZER;
846   ds_put_char_multiple (&s, ' ', width);
847   if (left != NULL)
848     {
849       size_t length = MIN (strlen (left), width);
850       memcpy (ds_end (&s) - width, left, length);
851     }
852   if (right != NULL)
853     {
854       size_t length = MIN (strlen (right), width);
855       memcpy (ds_end (&s) - length, right, length);
856     }
857   ds_put_char (&s, '\n');
858   fputs (ds_cstr (&s), out);
859   ds_destroy (&s);
860 }
861
862 static void
863 ascii_close_page (struct ascii_driver *a)
864 {
865   bool any_blank;
866   int i, y;
867
868   if (a->file == NULL)
869     return;
870
871   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
872       && !a->paginate && a->page_number > 1)
873     putc ('\n', a->file);
874
875   for (i = 0; i < a->top_margin; i++)
876     putc ('\n', a->file);
877   if (a->headers)
878     {
879       char *r1, *r2;
880
881       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
882       r2 = xasprintf ("%s - %s" , version, host_system);
883
884       output_title_line (a->file, a->width, a->title, r1);
885       output_title_line (a->file, a->width, a->subtitle, r2);
886       putc ('\n', a->file);
887
888       free (r1);
889       free (r2);
890     }
891
892   any_blank = false;
893   for (y = 0; y < a->allocated_lines; y++)
894     {
895       struct ascii_line *line = &a->lines[y];
896
897       if (a->squeeze_blank_lines && y > 0 && line->n_chars == 0)
898         any_blank = true;
899       else
900         {
901           if (any_blank)
902             {
903               putc ('\n', a->file);
904               any_blank = false;
905             }
906
907           output_line (a, line);
908         }
909     }
910   if (!a->squeeze_blank_lines)
911     for (y = a->allocated_lines; y < a->length; y++)
912       putc ('\n', a->file);
913
914   for (i = 0; i < a->bottom_margin; i++)
915     putc ('\n', a->file);
916   if (a->paginate)
917     putc ('\f', a->file);
918 }