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