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