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