ascii: Fix closing pipe files in ascii_destroy().
[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   if (a->file != NULL)
337     fn_close (a->file_name, a->file);
338   free (a->title);
339   free (a->subtitle);
340   free (a->file_name);
341   free (a->chart_file_name);
342   for (i = 0; i < LNS_COUNT; i++)
343     free (a->box[i]);
344   free (a->init);
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->y > 0)
356     {
357       ascii_close_page (a);
358
359       if (fn_close (a->file_name, a->file) != 0)
360         error (0, errno, _("ascii: closing output file \"%s\""),
361                a->file_name);
362       a->file = NULL;
363     }
364 }
365
366 static void
367 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
368 {
369   cell->contents = caption;
370   cell->options = TAB_LEFT;
371   cell->destructor = NULL;
372 }
373
374 static void
375 ascii_submit (struct output_driver *driver,
376               const struct output_item *output_item)
377 {
378   struct ascii_driver *a = ascii_driver_cast (driver);
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)
418         ascii_open_page (a);
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                   ascii_open_page (a);
443                   continue;
444                 }
445
446               y_slice = render_break_next (&y_break, space);
447               if (caption_height)
448                 {
449                   struct table_cell cell;
450                   int bb[TABLE_N_AXES][2];
451
452                   ascii_init_caption_cell (caption, &cell);
453                   bb[H][0] = 0;
454                   bb[H][1] = a->width;
455                   bb[V][0] = 0;
456                   bb[V][1] = caption_height;
457                   ascii_draw_cell (a, &cell, bb, bb);
458                   a->y += caption_height;
459                   caption_height = 0;
460                 }
461               render_page_draw (y_slice);
462               a->y += render_page_get_size (y_slice, V);
463               render_page_unref (y_slice);
464             }
465           render_break_destroy (&y_break);
466         }
467       render_break_destroy (&x_break);
468     }
469   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
470     {
471       struct chart_item *chart_item = to_chart_item (output_item);
472       char *file_name;
473
474       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
475                                      a->chart_cnt++);
476       if (file_name != NULL)
477         {
478           struct text_item *text_item;
479
480           text_item = text_item_create_format (
481             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
482
483           ascii_submit (driver, &text_item->output_item);
484           text_item_unref (text_item);
485           free (file_name);
486         }
487     }
488   else if (is_text_item (output_item))
489     {
490       const struct text_item *text_item = to_text_item (output_item);
491       enum text_item_type type = text_item_get_type (text_item);
492       const char *text = text_item_get_text (text_item);
493
494       switch (type)
495         {
496         case TEXT_ITEM_TITLE:
497           free (a->title);
498           a->title = xstrdup (text);
499           break;
500
501         case TEXT_ITEM_SUBTITLE:
502           free (a->subtitle);
503           a->subtitle = xstrdup (text);
504           break;
505
506         case TEXT_ITEM_COMMAND_CLOSE:
507           break;
508
509         case TEXT_ITEM_BLANK_LINE:
510           if (a->y > 0)
511             a->y++;
512           break;
513
514         case TEXT_ITEM_EJECT_PAGE:
515           if (a->y > 0)
516             ascii_close_page (a);
517           break;
518
519         default:
520           {
521             struct table_item *item;
522
523             item = table_item_create (table_from_string (0, text), NULL);
524             ascii_submit (&a->driver, &item->output_item);
525             table_item_unref (item);
526           }
527           break;
528         }
529     }
530 }
531
532 const struct output_driver_class ascii_class =
533   {
534     "ascii",
535     ascii_create,
536     ascii_destroy,
537     ascii_submit,
538     ascii_flush,
539   };
540 \f
541 enum wrap_mode
542   {
543     WRAP_WORD,
544     WRAP_CHAR,
545     WRAP_WORD_CHAR
546   };
547
548 static void ascii_expand_line (struct ascii_driver *, int y, int length);
549 static void ascii_layout_cell (struct ascii_driver *,
550                                const struct table_cell *,
551                                int bb[TABLE_N_AXES][2],
552                                int clip[TABLE_N_AXES][2], enum wrap_mode wrap,
553                                int *width, int *height);
554
555 static void
556 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
557                  enum render_line_style styles[TABLE_N_AXES][2])
558 {
559   struct ascii_driver *a = a_;
560   unsigned short int value;
561   int x1, y1;
562   int x, y;
563
564   /* Clip to the page. */
565   if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
566     return;
567   x1 = MIN (bb[H][1], a->width);
568   y1 = MIN (bb[V][1] + a->y, a->length);
569
570   /* Draw. */
571   value = ATTR_BOX | make_box_index (styles[V][0], styles[V][1],
572                                      styles[H][0], styles[H][1]);
573   for (y = bb[V][0] + a->y; y < y1; y++)
574     {
575       ascii_expand_line (a, y, x1);
576       for (x = bb[H][0]; x < x1; x++)
577         a->lines[y].chars[x] = value;
578     }
579 }
580
581 static void
582 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
583                           int *min_width, int *max_width)
584 {
585   struct ascii_driver *a = a_;
586   int bb[TABLE_N_AXES][2];
587   int clip[TABLE_N_AXES][2];
588   int h;
589
590   bb[H][0] = 0;
591   bb[H][1] = INT_MAX;
592   bb[V][0] = 0;
593   bb[V][1] = INT_MAX;
594   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
595   ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, max_width, &h);
596
597   if (strchr (cell->contents, ' '))
598     {
599       bb[H][1] = 1;
600       ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, min_width, &h);
601     }
602   else
603     *min_width = *max_width;
604 }
605
606 static int
607 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
608 {
609   struct ascii_driver *a = a_;
610   int bb[TABLE_N_AXES][2];
611   int clip[TABLE_N_AXES][2];
612   int w, h;
613
614   bb[H][0] = 0;
615   bb[H][1] = width;
616   bb[V][0] = 0;
617   bb[V][1] = INT_MAX;
618   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
619   ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, &w, &h);
620   return h;
621 }
622
623 static void
624 ascii_draw_cell (void *a_, const struct table_cell *cell,
625                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
626 {
627   struct ascii_driver *a = a_;
628   int w, h;
629
630   ascii_layout_cell (a, cell, bb, clip, WRAP_WORD, &w, &h);
631 }
632
633 /* Ensures that at least the first LENGTH characters of line Y in
634    ascii driver A have been cleared out. */
635 static void
636 ascii_expand_line (struct ascii_driver *a, int y, int length)
637 {
638   struct ascii_line *line = &a->lines[y];
639   if (line->n_chars < length)
640     {
641       int x;
642       if (line->allocated_chars < length)
643         {
644           line->allocated_chars = MAX (length, MIN (length * 2, a->width));
645           line->chars = xnrealloc (line->chars, line->allocated_chars,
646                                    sizeof *line->chars);
647         }
648       for (x = line->n_chars; x < length; x++)
649         line->chars[x] = ' ';
650       line->n_chars = length;
651     }
652 }
653
654 static void
655 text_draw (struct ascii_driver *a, const struct table_cell *cell,
656            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
657            int y, const char *string, int n)
658 {
659   int x0 = MAX (0, clip[H][0]);
660   int y0 = MAX (0, clip[V][0] + a->y);
661   int x1 = clip[H][1];
662   int y1 = MIN (a->length, clip[V][1] + a->y);
663   int x;
664
665   y += a->y;
666   if (y < y0 || y >= y1)
667     return;
668
669   switch (cell->options & TAB_ALIGNMENT)
670     {
671     case TAB_LEFT:
672       x = bb[H][0];
673       break;
674     case TAB_CENTER:
675       x = (bb[H][0] + bb[H][1] - n + 1) / 2;
676       break;
677     case TAB_RIGHT:
678       x = bb[H][1] - n;
679       break;
680     default:
681       NOT_REACHED ();
682     }
683
684   if (x0 > x)
685     {
686       n -= x0 - x;
687       if (n <= 0)
688         return;
689       string += x0 - x;
690       x = x0;
691     }
692   if (x + n >= x1)
693     n = x1 - x;
694
695   if (n > 0)
696     {
697       int attr = cell->options & TAB_EMPH ? ATTR_EMPHASIS : 0;
698       size_t i;
699
700       ascii_expand_line (a, y, x + n);
701       for (i = 0; i < n; i++)
702         a->lines[y].chars[x + i] = string[i] | attr;
703     }
704 }
705
706 static void
707 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
708                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
709                    enum wrap_mode wrap, int *width, int *height)
710 {
711   size_t length = strlen (cell->contents);
712   int y, pos;
713
714   *width = 0;
715   pos = 0;
716   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
717     {
718       const char *line = &cell->contents[pos];
719       const char *new_line;
720       size_t line_len;
721
722       /* Find line length without considering word wrap. */
723       line_len = MIN (bb[H][1] - bb[H][0], length - pos);
724       new_line = memchr (line, '\n', line_len);
725       if (new_line != NULL)
726         line_len = new_line - line;
727
728       /* Word wrap. */
729       if (pos + line_len < length && wrap != WRAP_CHAR)
730         {
731           size_t space_len = line_len;
732           while (space_len > 0 && !isspace ((unsigned char) line[space_len]))
733             space_len--;
734           if (space_len > 0)
735             line_len = space_len;
736           else if (wrap == WRAP_WORD)
737             {
738               while (pos + line_len < length
739                      && !isspace ((unsigned char) line[line_len]))
740                 line_len++;
741             }
742         }
743       if (line_len > *width)
744         *width = line_len;
745
746       /* Draw text. */
747       text_draw (a, cell, bb, clip, y, line, line_len);
748
749       /* Next line. */
750       pos += line_len;
751       if (pos < length && isspace ((unsigned char) cell->contents[pos]))
752         pos++;
753     }
754   *height = y - bb[V][0];
755 }
756 \f
757 /* ascii_close_page () and support routines. */
758
759 static void
760 ascii_open_page (struct ascii_driver *a)
761 {
762   int i;
763
764   if (a->file == NULL)
765     {
766       a->file = fn_open (a->file_name, a->append ? "a" : "w");
767       if (a->file != NULL)
768         {
769           if (a->init != NULL)
770             fputs (a->init, a->file);
771         }
772       else
773         {
774           /* Report the error to the user and complete
775              initialization.  If we do not finish initialization,
776              then calls to other driver functions will segfault
777              later.  It would be better to simply drop the driver
778              entirely, but we do not have a convenient mechanism
779              for this (yet). */
780           if (!a->reported_error)
781             error (0, errno, _("ascii: opening output file \"%s\""),
782                    a->file_name);
783           a->reported_error = true;
784         }
785     }
786
787   a->page_number++;
788
789   if (a->length > a->allocated_lines)
790     {
791       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
792       for (i = a->allocated_lines; i < a->length; i++)
793         {
794           struct ascii_line *line = &a->lines[i];
795           line->chars = NULL;
796           line->allocated_chars = 0;
797         }
798       a->allocated_lines = a->length;
799     }
800
801   for (i = 0; i < a->length; i++)
802     a->lines[i].n_chars = 0;
803 }
804
805 /* Writes LINE to A's output file.  */
806 static void
807 output_line (struct ascii_driver *a, const struct ascii_line *line)
808 {
809   size_t length;
810   size_t i;
811
812   length = line->n_chars;
813   while (length > 0 && line->chars[length - 1] == ' ')
814     length--;
815
816   for (i = 0; i < length; i++)
817     {
818       int attribute = line->chars[i] & (ATTR_BOX | ATTR_EMPHASIS);
819       int ch = line->chars[i] & ~(ATTR_BOX | ATTR_EMPHASIS);
820
821       switch (attribute)
822         {
823         case ATTR_BOX:
824           fputs (a->box[ch], a->file);
825           break;
826
827         case ATTR_EMPHASIS:
828           if (a->emphasis == EMPH_BOLD)
829             fprintf (a->file, "%c\b%c", ch, ch);
830           else if (a->emphasis == EMPH_UNDERLINE)
831             fprintf (a->file, "_\b%c", ch);
832           else
833             putc (ch, a->file);
834           break;
835
836         default:
837           putc (ch, a->file);
838           break;
839         }
840     }
841
842   putc ('\n', a->file);
843 }
844
845 static void
846 output_title_line (FILE *out, int width, const char *left, const char *right)
847 {
848   struct string s = DS_EMPTY_INITIALIZER;
849   ds_put_char_multiple (&s, ' ', width);
850   if (left != NULL)
851     {
852       size_t length = MIN (strlen (left), width);
853       memcpy (ds_end (&s) - width, left, length);
854     }
855   if (right != NULL)
856     {
857       size_t length = MIN (strlen (right), width);
858       memcpy (ds_end (&s) - length, right, length);
859     }
860   ds_put_char (&s, '\n');
861   fputs (ds_cstr (&s), out);
862   ds_destroy (&s);
863 }
864
865 static void
866 ascii_close_page (struct ascii_driver *a)
867 {
868   bool any_blank;
869   int i, y;
870
871   if (a->file == NULL)
872     return;
873
874   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
875       && !a->paginate && a->page_number > 1)
876     putc ('\n', a->file);
877
878   for (i = 0; i < a->top_margin; i++)
879     putc ('\n', a->file);
880   if (a->headers)
881     {
882       char *r1, *r2;
883
884       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
885       r2 = xasprintf ("%s - %s" , version, host_system);
886
887       output_title_line (a->file, a->width, a->title, r1);
888       output_title_line (a->file, a->width, a->subtitle, r2);
889       putc ('\n', a->file);
890
891       free (r1);
892       free (r2);
893     }
894
895   any_blank = false;
896   for (y = 0; y < a->allocated_lines; y++)
897     {
898       struct ascii_line *line = &a->lines[y];
899
900       if (a->squeeze_blank_lines && y > 0 && line->n_chars == 0)
901         any_blank = true;
902       else
903         {
904           if (any_blank)
905             {
906               putc ('\n', a->file);
907               any_blank = false;
908             }
909
910           output_line (a, line);
911         }
912     }
913   if (!a->squeeze_blank_lines)
914     for (y = a->allocated_lines; y < a->length; y++)
915       putc ('\n', a->file);
916
917   for (i = 0; i < a->bottom_margin; i++)
918     putc ('\n', a->file);
919   if (a->paginate)
920     putc ('\f', a->file);
921
922   a->y = 0;
923 }