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