251e22d443d2d27f37f1f4fb741f7151b7765c8e
[pspp] / src / output / ascii.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 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 <unilbrk.h>
26 #include <unistd.h>
27 #include <unistr.h>
28 #include <uniwidth.h>
29
30 #include "data/file-name.h"
31 #include "data/settings.h"
32 #include "libpspp/assertion.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/compiler.h"
35 #include "libpspp/message.h"
36 #include "libpspp/start-date.h"
37 #include "libpspp/string-map.h"
38 #include "libpspp/u8-line.h"
39 #include "libpspp/version.h"
40 #include "output/ascii.h"
41 #include "output/cairo.h"
42 #include "output/chart-item-provider.h"
43 #include "output/driver-provider.h"
44 #include "output/message-item.h"
45 #include "output/options.h"
46 #include "output/render.h"
47 #include "output/tab.h"
48 #include "output/table-item.h"
49 #include "output/text-item.h"
50
51 #include "gl/minmax.h"
52 #include "gl/xalloc.h"
53
54 #include "gettext.h"
55 #define _(msgid) gettext (msgid)
56
57 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
58 #define H TABLE_HORZ
59 #define V TABLE_VERT
60
61 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
62                * RENDER_N_LINES * RENDER_N_LINES)
63
64 static const ucs4_t ascii_box_chars[N_BOX] =
65   {
66     ' ', '|', '#',
67     '-', '+', '#',
68     '=', '#', '#',
69     '|', '|', '#',
70     '+', '+', '#',
71     '#', '#', '#',
72     '#', '#', '#',
73     '#', '#', '#',
74     '#', '#', '#',
75     '-', '+', '#',
76     '-', '+', '#',
77     '#', '#', '#',
78     '+', '+', '#',
79     '+', '+', '#',
80     '#', '#', '#',
81     '#', '#', '#',
82     '#', '#', '#',
83     '#', '#', '#',
84     '=', '#', '#',
85     '#', '#', '#',
86     '=', '#', '#',
87     '#', '#', '#',
88     '#', '#', '#',
89     '#', '#', '#',
90     '#', '#', '#',
91     '#', '#', '#',
92     '#', '#', '#',
93   };
94
95 static const ucs4_t unicode_box_chars[N_BOX] =
96   {
97     0x0020, 0x2575, 0x2551,
98     0x2574, 0x256f, 0x255c,
99     0x2550, 0x255b, 0x255d,
100     0x2577, 0x2502, 0x2551,
101     0x256e, 0x2524, 0x2562,
102     0x2555, 0x2561, 0x2563,
103     0x2551, 0x2551, 0x2551,
104     0x2556, 0x2562, 0x2562,
105     0x2557, 0x2563, 0x2563,
106     0x2576, 0x2570, 0x2559,
107     0x2500, 0x2534, 0x2568,
108     0x2550, 0x2567, 0x2569,
109     0x256d, 0x251c, 0x255f,
110     0x252c, 0x253c, 0x256a,
111     0x2564, 0x256a, 0x256c,
112     0x2553, 0x255f, 0x255f,
113     0x2565, 0x256b, 0x256b,
114     0x2566, 0x256c, 0x256c,
115     0x2550, 0x2558, 0x255a,
116     0x2550, 0x2567, 0x2569,
117     0x2550, 0x2567, 0x2569,
118     0x2552, 0x255e, 0x2560,
119     0x2564, 0x256a, 0x256c,
120     0x2564, 0x256a, 0x256c,
121     0x2554, 0x2560, 0x2560,
122     0x2560, 0x256c, 0x256c,
123     0x2566, 0x256c, 0x256c,
124   };
125
126 static inline int
127 make_box_index (int left, int right, int top, int bottom)
128 {
129   return ((right * RENDER_N_LINES + bottom) * RENDER_N_LINES + left) * RENDER_N_LINES + top;
130 }
131
132 /* How to emphasize text. */
133 enum emphasis_style
134   {
135     EMPH_BOLD,                  /* Overstrike for bold. */
136     EMPH_UNDERLINE,             /* Overstrike for underlining. */
137     EMPH_NONE                   /* No emphasis. */
138   };
139
140 /* ASCII output driver. */
141 struct ascii_driver
142   {
143     struct output_driver driver;
144
145     /* User parameters. */
146     bool append;                /* Append if output file already exists? */
147     bool headers;               /* Print headers at top of page? */
148     bool paginate;              /* Insert formfeeds? */
149     bool squeeze_blank_lines;   /* Squeeze multiple blank lines into one? */
150     enum emphasis_style emphasis; /* How to emphasize text. */
151     char *chart_file_name;      /* Name of files used for charts. */
152
153 #ifdef HAVE_CAIRO
154     /* Colours for charts */
155     struct xr_color fg;
156     struct xr_color bg;
157 #endif
158
159     int width;                  /* Page width. */
160     int length;                 /* Page length minus margins and header. */
161     bool auto_width;            /* Use viewwidth as page width? */
162     bool auto_length;           /* Use viewlength as page width? */
163
164     int top_margin;             /* Top margin in lines. */
165     int bottom_margin;          /* Bottom margin in lines. */
166
167     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
168
169     const ucs4_t *box;          /* Line & box drawing characters. */
170
171     /* Internal state. */
172     char *command_name;
173     char *title;
174     char *subtitle;
175     char *file_name;            /* Output file name. */
176     FILE *file;                 /* Output file. */
177     bool error;                 /* Output error? */
178     int page_number;            /* Current page number. */
179     struct u8_line *lines;      /* Page content. */
180     int allocated_lines;        /* Number of lines allocated. */
181     int chart_cnt;              /* Number of charts so far. */
182     int x, y;
183   };
184
185 static const struct output_driver_class ascii_driver_class;
186
187 static void ascii_submit (struct output_driver *, const struct output_item *);
188
189 static int vertical_margins (const struct ascii_driver *);
190
191 static bool update_page_size (struct ascii_driver *, bool issue_error);
192 static int parse_page_size (struct driver_option *);
193
194 static void ascii_close_page (struct ascii_driver *);
195 static bool ascii_open_page (struct ascii_driver *);
196
197 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
198                              enum render_line_style styles[TABLE_N_AXES][2]);
199 static void ascii_measure_cell_width (void *, const struct table_cell *,
200                                       int *min, int *max);
201 static int ascii_measure_cell_height (void *, const struct table_cell *,
202                                       int width);
203 static void ascii_draw_cell (void *, const struct table_cell *,
204                              int bb[TABLE_N_AXES][2],
205                              int clip[TABLE_N_AXES][2]);
206
207 static void
208 reallocate_lines (struct ascii_driver *a)
209 {
210   if (a->length > a->allocated_lines)
211     {
212       int i;
213       a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
214       for (i = a->allocated_lines; i < a->length; i++)
215         u8_line_init (&a->lines[i]);
216       a->allocated_lines = a->length;
217     }
218 }
219
220
221 static struct ascii_driver *
222 ascii_driver_cast (struct output_driver *driver)
223 {
224   assert (driver->class == &ascii_driver_class);
225   return UP_CAST (driver, struct ascii_driver, driver);
226 }
227
228 static struct driver_option *
229 opt (struct output_driver *d, struct string_map *options, const char *key,
230      const char *default_value)
231 {
232   return driver_option_get (d, options, key, default_value);
233 }
234
235 static struct output_driver *
236 ascii_create (const char *file_name, enum settings_output_devices device_type,
237               struct string_map *o)
238 {
239   enum { BOX_ASCII, BOX_UNICODE } box;
240   int min_break[TABLE_N_AXES];
241   struct output_driver *d;
242   struct ascii_driver *a;
243   int paper_length;
244
245   a = xzalloc (sizeof *a);
246   d = &a->driver;
247   output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
248   a->append = parse_boolean (opt (d, o, "append", "false"));
249   a->headers = parse_boolean (opt (d, o, "headers", "false"));
250   a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
251   a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
252   a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
253                             "bold", EMPH_BOLD,
254                             "underline", EMPH_UNDERLINE,
255                             "none", EMPH_NONE,
256                             NULL_SENTINEL);
257
258   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
259
260   a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
261   a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
262
263   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
264   min_break[V] = parse_int (opt (d, o, "min-vbreak", "-1"), -1, INT_MAX);
265
266   a->width = parse_page_size (opt (d, o, "width", "79"));
267   paper_length = parse_page_size (opt (d, o, "length", "66"));
268   a->auto_width = a->width < 0;
269   a->auto_length = paper_length < 0;
270   a->length = paper_length - vertical_margins (a);
271   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
272   a->min_break[V] = min_break[V] >= 0 ? min_break[V] : a->length / 2;
273 #ifdef HAVE_CAIRO
274   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
275   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
276 #endif
277   box = parse_enum (opt (d, o, "box", "ascii"),
278                     "ascii", BOX_ASCII,
279                     "unicode", BOX_UNICODE,
280                     NULL_SENTINEL);
281   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
282
283   a->command_name = NULL;
284   a->title = xstrdup ("");
285   a->subtitle = xstrdup ("");
286   a->file_name = xstrdup (file_name);
287   a->file = NULL;
288   a->error = false;
289   a->page_number = 0;
290   a->lines = NULL;
291   a->allocated_lines = 0;
292   a->chart_cnt = 1;
293
294   if (!update_page_size (a, true))
295     goto error;
296
297   return d;
298
299 error:
300   output_driver_destroy (d);
301   return NULL;
302 }
303
304 static int
305 parse_page_size (struct driver_option *option)
306 {
307   int dim = atol (option->default_value);
308
309   if (option->value != NULL)
310     {
311       if (!strcmp (option->value, "auto"))
312         dim = -1;
313       else
314         {
315           int value;
316           char *tail;
317
318           errno = 0;
319           value = strtol (option->value, &tail, 0);
320           if (dim >= 1 && errno != ERANGE && *tail == '\0')
321             dim = value;
322           else
323             msg (MW, _("%s: %s must be positive integer or `auto'"),
324                    option->driver_name, option->name);
325         }
326     }
327
328   driver_option_destroy (option);
329
330   return dim;
331 }
332
333 static int
334 vertical_margins (const struct ascii_driver *a)
335 {
336   return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
337 }
338
339 /* Re-calculates the page width and length based on settings,
340    margins, and, if "auto" is set, the size of the user's
341    terminal window or GUI output window. */
342 static bool
343 update_page_size (struct ascii_driver *a, bool issue_error)
344 {
345   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
346
347   if (a->auto_width)
348     a->width = settings_get_viewwidth ();
349   if (a->auto_length)
350     a->length = settings_get_viewlength () - vertical_margins (a);
351
352   if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
353     {
354       if (issue_error)
355         msg (ME,
356                _("ascii: page excluding margins and headers "
357                  "must be at least %d characters wide by %d lines long, but "
358                  "as configured is only %d characters by %d lines"),
359                MIN_WIDTH, MIN_LENGTH,
360                a->width, a->length);
361       if (a->width < MIN_WIDTH)
362         a->width = MIN_WIDTH;
363       if (a->length < MIN_LENGTH)
364         a->length = MIN_LENGTH;
365       return false;
366     }
367
368   reallocate_lines (a);
369
370   return true;
371 }
372
373 static void
374 ascii_destroy (struct output_driver *driver)
375 {
376   struct ascii_driver *a = ascii_driver_cast (driver);
377   int i;
378
379   if (a->y > 0)
380     ascii_close_page (a);
381
382   if (a->file != NULL)
383     fn_close (a->file_name, a->file);
384   free (a->command_name);
385   free (a->title);
386   free (a->subtitle);
387   free (a->file_name);
388   free (a->chart_file_name);
389   for (i = 0; i < a->allocated_lines; i++)
390     u8_line_destroy (&a->lines[i]);
391   free (a->lines);
392   free (a);
393 }
394
395 static void
396 ascii_flush (struct output_driver *driver)
397 {
398   struct ascii_driver *a = ascii_driver_cast (driver);
399   if (a->y > 0)
400     {
401       ascii_close_page (a);
402
403       if (fn_close (a->file_name, a->file) != 0)
404         msg_error (errno, _("ascii: closing output file `%s'"), a->file_name);
405       a->file = NULL;
406     }
407 }
408
409 static void
410 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
411 {
412   cell->inline_contents.options = TAB_LEFT;
413   cell->inline_contents.text = CONST_CAST (char *, caption);
414   cell->inline_contents.table = NULL;
415   cell->contents = &cell->inline_contents;
416   cell->n_contents = 1;
417   cell->destructor = NULL;
418 }
419
420 static void
421 ascii_output_table_item (struct ascii_driver *a,
422                          const struct table_item *table_item)
423 {
424   const char *caption = table_item_get_caption (table_item);
425   struct render_params params;
426   struct render_page *page;
427   struct render_break x_break;
428   int caption_height;
429   int i;
430
431   update_page_size (a, false);
432
433   if (caption != NULL)
434     {
435       /* XXX doesn't do well with very large captions */
436       struct table_cell cell;
437       ascii_init_caption_cell (caption, &cell);
438       caption_height = ascii_measure_cell_height (a, &cell, a->width);
439     }
440   else
441     caption_height = 0;
442
443   params.draw_line = ascii_draw_line;
444   params.measure_cell_width = ascii_measure_cell_width;
445   params.measure_cell_height = ascii_measure_cell_height;
446   params.adjust_break = NULL;
447   params.draw_cell = ascii_draw_cell;
448   params.aux = a;
449   params.size[H] = a->width;
450   params.size[V] = a->length - caption_height;
451   params.font_size[H] = 1;
452   params.font_size[V] = 1;
453   for (i = 0; i < RENDER_N_LINES; i++)
454     {
455       int width = i == RENDER_LINE_NONE ? 0 : 1;
456       params.line_widths[H][i] = width;
457       params.line_widths[V][i] = width;
458     }
459   for (i = 0; i < TABLE_N_AXES; i++)
460     params.min_break[i] = a->min_break[i];
461
462   if (a->file == NULL && !ascii_open_page (a))
463     return;
464
465   page = render_page_create (&params, table_item_get_table (table_item));
466   for (render_break_init (&x_break, page, H);
467        render_break_has_next (&x_break); )
468     {
469       struct render_page *x_slice;
470       struct render_break y_break;
471
472       x_slice = render_break_next (&x_break, a->width);
473       for (render_break_init (&y_break, x_slice, V);
474            render_break_has_next (&y_break); )
475         {
476           struct render_page *y_slice;
477           int space;
478
479           if (a->y > 0)
480             a->y++;
481
482           space = a->length - a->y - caption_height;
483           if (render_break_next_size (&y_break) > space)
484             {
485               assert (a->y > 0);
486               ascii_close_page (a);
487               if (!ascii_open_page (a))
488                 return;
489               continue;
490             }
491
492           y_slice = render_break_next (&y_break, space);
493           if (caption_height)
494             {
495               struct table_cell cell;
496               int bb[TABLE_N_AXES][2];
497
498               ascii_init_caption_cell (caption, &cell);
499               bb[H][0] = 0;
500               bb[H][1] = a->width;
501               bb[V][0] = 0;
502               bb[V][1] = caption_height;
503               ascii_draw_cell (a, &cell, bb, bb);
504               a->y += caption_height;
505               caption_height = 0;
506             }
507           render_page_draw (y_slice);
508           a->y += render_page_get_size (y_slice, V);
509           render_page_unref (y_slice);
510         }
511       render_break_destroy (&y_break);
512     }
513   render_break_destroy (&x_break);
514 }
515
516 static void
517 ascii_output_text (struct ascii_driver *a, const char *text)
518 {
519   struct table_item *table_item;
520
521   table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
522   ascii_output_table_item (a, table_item);
523   table_item_unref (table_item);
524 }
525
526 static void
527 ascii_submit (struct output_driver *driver,
528               const struct output_item *output_item)
529 {
530   struct ascii_driver *a = ascii_driver_cast (driver);
531
532   output_driver_track_current_command (output_item, &a->command_name);
533
534   if (a->error)
535     return;
536
537   if (is_table_item (output_item))
538     ascii_output_table_item (a, to_table_item (output_item));
539 #ifdef HAVE_CAIRO
540   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
541     {
542       struct chart_item *chart_item = to_chart_item (output_item);
543       char *file_name;
544
545       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
546                                      a->chart_cnt++,
547                                      &a->fg, 
548                                      &a->bg);
549       if (file_name != NULL)
550         {
551           struct text_item *text_item;
552
553           text_item = text_item_create_format (
554             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
555
556           ascii_submit (driver, &text_item->output_item);
557           text_item_unref (text_item);
558           free (file_name);
559         }
560     }
561 #endif  /* HAVE_CAIRO */
562   else if (is_text_item (output_item))
563     {
564       const struct text_item *text_item = to_text_item (output_item);
565       enum text_item_type type = text_item_get_type (text_item);
566       const char *text = text_item_get_text (text_item);
567
568       switch (type)
569         {
570         case TEXT_ITEM_TITLE:
571           free (a->title);
572           a->title = xstrdup (text);
573           break;
574
575         case TEXT_ITEM_SUBTITLE:
576           free (a->subtitle);
577           a->subtitle = xstrdup (text);
578           break;
579
580         case TEXT_ITEM_COMMAND_OPEN:
581         case TEXT_ITEM_COMMAND_CLOSE:
582           break;
583
584         case TEXT_ITEM_BLANK_LINE:
585           if (a->y > 0)
586             a->y++;
587           break;
588
589         case TEXT_ITEM_EJECT_PAGE:
590           if (a->y > 0)
591             ascii_close_page (a);
592           break;
593
594         default:
595           ascii_output_text (a, text);
596           break;
597         }
598     }
599   else if (is_message_item (output_item))
600     {
601       const struct message_item *message_item = to_message_item (output_item);
602       const struct msg *msg = message_item_get_msg (message_item);
603       char *s = msg_to_string (msg, a->command_name);
604       ascii_output_text (a, s);
605       free (s);
606     }
607 }
608
609 const struct output_driver_factory txt_driver_factory =
610   { "txt", "-", ascii_create };
611 const struct output_driver_factory list_driver_factory =
612   { "list", "-", ascii_create };
613
614 static const struct output_driver_class ascii_driver_class =
615   {
616     "text",
617     ascii_destroy,
618     ascii_submit,
619     ascii_flush,
620   };
621 \f
622 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
623                             int n);
624 static void ascii_layout_cell (struct ascii_driver *,
625                                const struct table_cell *,
626                                int bb[TABLE_N_AXES][2],
627                                int clip[TABLE_N_AXES][2],
628                                int *width, int *height);
629
630 static void
631 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
632                  enum render_line_style styles[TABLE_N_AXES][2])
633 {
634   struct ascii_driver *a = a_;
635   char mbchar[6];
636   int x0, y0, x1, y1;
637   ucs4_t uc;
638   int mblen;
639   int x, y;
640
641   /* Clip to the page. */
642   x0 = MAX (bb[H][0] + a->x, 0);
643   y0 = MAX (bb[V][0] + a->y, 0);
644   x1 = MIN (bb[H][1] + a->x, a->width);
645   y1 = MIN (bb[V][1] + a->y, a->length);
646   if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length)
647     return;
648
649   /* Draw. */
650   uc = a->box[make_box_index (styles[V][0], styles[V][1],
651                               styles[H][0], styles[H][1])];
652   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
653   for (y = y0; y < y1; y++)
654     {
655       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
656       for (x = x0; x < x1; x++)
657         {
658           memcpy (p, mbchar, mblen);
659           p += mblen;
660         }
661     }
662 }
663
664 static void
665 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
666                           int *min_width, int *max_width)
667 {
668   struct ascii_driver *a = a_;
669   int bb[TABLE_N_AXES][2];
670   int clip[TABLE_N_AXES][2];
671   int h;
672
673   bb[H][0] = 0;
674   bb[H][1] = INT_MAX;
675   bb[V][0] = 0;
676   bb[V][1] = INT_MAX;
677   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
678   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
679
680   if (cell->n_contents != 1
681       || cell->contents[0].table
682       || strchr (cell->contents[0].text, ' '))
683     {
684       bb[H][1] = 1;
685       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
686     }
687   else
688     *min_width = *max_width;
689 }
690
691 static int
692 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
693 {
694   struct ascii_driver *a = a_;
695   int bb[TABLE_N_AXES][2];
696   int clip[TABLE_N_AXES][2];
697   int w, h;
698
699   bb[H][0] = 0;
700   bb[H][1] = width;
701   bb[V][0] = 0;
702   bb[V][1] = INT_MAX;
703   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
704   ascii_layout_cell (a, cell, bb, clip, &w, &h);
705   return h;
706 }
707
708 static void
709 ascii_draw_cell (void *a_, const struct table_cell *cell,
710                  int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
711 {
712   struct ascii_driver *a = a_;
713   int w, h;
714
715   ascii_layout_cell (a, cell, bb, clip, &w, &h);
716 }
717
718 static char *
719 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
720 {
721   assert (y < a->allocated_lines);
722   return u8_line_reserve (&a->lines[y], x0, x1, n);
723 }
724
725 static void
726 text_draw (struct ascii_driver *a, unsigned int options,
727            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
728            int y, const uint8_t *string, int n, size_t width)
729 {
730   int x0 = MAX (0, clip[H][0] + a->x);
731   int y0 = MAX (0, clip[V][0] + a->y);
732   int x1 = MIN (a->width, clip[H][1] + a->x);
733   int y1 = MIN (a->length, clip[V][1] + a->y);
734   int x;
735
736   y += a->y;
737   if (y < y0 || y >= y1)
738     return;
739
740   switch (options & TAB_ALIGNMENT)
741     {
742     case TAB_LEFT:
743       x = bb[H][0];
744       break;
745     case TAB_CENTER:
746       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
747       break;
748     case TAB_RIGHT:
749       x = bb[H][1] - width;
750       break;
751     default:
752       NOT_REACHED ();
753     }
754   x += a->x;
755   if (x >= x1)
756     return;
757
758   while (x < x0)
759     {
760       ucs4_t uc;
761       int mblen;
762       int w;
763
764       if (n == 0)
765         return;
766       mblen = u8_mbtouc (&uc, string, n);
767
768       string += mblen;
769       n -= mblen;
770
771       w = uc_width (uc, "UTF-8");
772       if (w > 0)
773         {
774           x += w;
775           width -= w;
776         }
777     }
778   if (n == 0)
779     return;
780
781   if (x + width > x1)
782     {
783       int ofs;
784
785       ofs = width = 0;
786       for (ofs = 0; ofs < n; )
787         {
788           ucs4_t uc;
789           int mblen;
790           int w;
791
792           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
793
794           w = uc_width (uc, "UTF-8");
795           if (w > 0)
796             {
797               if (width + w > x1 - x)
798                 break;
799               width += w;
800             }
801           ofs += mblen;
802         }
803       n = ofs;
804       if (n == 0)
805         return;
806     }
807
808   if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
809     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
810   else
811     {
812       size_t n_out;
813       size_t ofs;
814       char *out;
815       int mblen;
816
817       /* First figure out how many bytes need to be inserted. */
818       n_out = n;
819       for (ofs = 0; ofs < n; ofs += mblen)
820         {
821           ucs4_t uc;
822           int w;
823
824           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
825           w = uc_width (uc, "UTF-8");
826
827           if (w > 0)
828             n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
829         }
830
831       /* Then insert them. */
832       out = ascii_reserve (a, y, x, x + width, n_out);
833       for (ofs = 0; ofs < n; ofs += mblen)
834         {
835           ucs4_t uc;
836           int w;
837
838           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
839           w = uc_width (uc, "UTF-8");
840
841           if (w > 0)
842             {
843               if (a->emphasis == EMPH_UNDERLINE)
844                 *out++ = '_';
845               else
846                 out = mempcpy (out, string + ofs, mblen);
847               *out++ = '\b';
848             }
849           out = mempcpy (out, string + ofs, mblen);
850         }
851     }
852 }
853
854 static int
855 ascii_layout_cell_text (struct ascii_driver *a,
856                         const struct cell_contents *contents,
857                         int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
858                         int *widthp)
859 {
860   size_t length = strlen (contents->text);
861   char *breaks;
862   int bb_width;
863   size_t pos;
864   int y;
865
866   y = bb[V][0];
867   if (length == 0)
868     return y;
869
870   breaks = xmalloc (length + 1);
871   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, contents->text), length,
872                           "UTF-8", breaks);
873   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
874                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
875
876   pos = 0;
877   bb_width = bb[H][1] - bb[H][0];
878   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
879     {
880       const uint8_t *line = CHAR_CAST (const uint8_t *, contents->text + pos);
881       const char *b = breaks + pos;
882       size_t n = length - pos;
883
884       size_t last_break_ofs = 0;
885       int last_break_width = 0;
886       int width = 0;
887       size_t graph_ofs;
888       size_t ofs;
889
890       for (ofs = 0; ofs < n; )
891         {
892           ucs4_t uc;
893           int mblen;
894           int w;
895
896           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
897           if (b[ofs] == UC_BREAK_MANDATORY)
898             break;
899           else if (b[ofs] == UC_BREAK_POSSIBLE)
900             {
901               last_break_ofs = ofs;
902               last_break_width = width;
903             }
904
905           w = uc_width (uc, "UTF-8");
906           if (w > 0)
907             {
908               if (width + w > bb_width)
909                 {
910                   if (isspace (line[ofs]))
911                     break;
912                   else if (last_break_ofs != 0)
913                     {
914                       ofs = last_break_ofs;
915                       width = last_break_width;
916                       break;
917                     }
918                 }
919               width += w;
920             }
921           ofs += mblen;
922         }
923
924       /* Trim any trailing spaces off the end of the text to be drawn. */
925       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
926         if (!isspace (line[graph_ofs - 1]))
927           break;
928       width -= ofs - graph_ofs;
929
930       /* Draw text. */
931       text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width);
932
933       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
934          past any spaces past the end of the line (but not past a new-line). */
935       if (b[ofs] == UC_BREAK_MANDATORY)
936         ofs++;
937       else
938         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
939           ofs++;
940
941       if (width > *widthp)
942         *widthp = width;
943       pos += ofs;
944     }
945
946   free (breaks);
947
948   return y;
949 }
950
951 static int
952 ascii_layout_subtable (struct ascii_driver *a,
953                        const struct cell_contents *contents,
954                        int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED,
955                        int *widthp)
956 {
957   const struct table *table = contents->table;
958   struct render_params params;
959   struct render_page *page;
960   int r[TABLE_N_AXES][2];
961   int width, height;
962   int i;
963
964   params.draw_line = ascii_draw_line;
965   params.measure_cell_width = ascii_measure_cell_width;
966   params.measure_cell_height = ascii_measure_cell_height;
967   params.adjust_break = NULL;
968   params.draw_cell = ascii_draw_cell,
969   params.aux = a;
970   params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0];
971   params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0];
972   params.font_size[H] = 1;
973   params.font_size[V] = 1;
974   for (i = 0; i < RENDER_N_LINES; i++)
975     {
976       int width = i == RENDER_LINE_NONE ? 0 : 1;
977       params.line_widths[H][i] = width;
978       params.line_widths[V][i] = width;
979     }
980
981   page = render_page_create (&params, table);
982   width = render_page_get_size (page, TABLE_HORZ);
983   height = render_page_get_size (page, TABLE_VERT);
984
985   /* r = intersect(bb, clip) - bb. */
986   for (i = 0; i < TABLE_N_AXES; i++)
987     {
988       r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0];
989       r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0];
990     }
991
992   if (r[H][0] < r[H][1] && r[V][0] < r[V][1])
993     {
994       unsigned int alignment = contents->options & TAB_ALIGNMENT;
995       int save_x = a->x;
996
997       a->x += bb[TABLE_HORZ][0];
998       if (alignment == TAB_RIGHT)
999         a->x += params.size[H] - width;
1000       else if (alignment == TAB_CENTER)
1001         a->x += (params.size[H] - width) / 2;
1002       a->y += bb[TABLE_VERT][0];
1003       render_page_draw (page);
1004       a->y -= bb[TABLE_VERT][0];
1005       a->x = save_x;
1006     }
1007   render_page_unref (page);
1008
1009   if (width > *widthp)
1010     *widthp = width;
1011   return bb[V][0] + height;
1012 }
1013
1014 static void
1015 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
1016                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1017                    int *widthp, int *heightp)
1018 {
1019   int bb[TABLE_N_AXES][2];
1020   size_t i;
1021
1022   *widthp = 0;
1023   *heightp = 0;
1024
1025   memcpy (bb, bb_, sizeof bb);
1026   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
1027     {
1028       const struct cell_contents *contents = &cell->contents[i];
1029
1030       /* Put a blank line between contents. */
1031       if (i > 0)
1032         {
1033           bb[V][0]++;
1034           if (bb[V][0] >= bb[V][1])
1035             break;
1036         }
1037
1038       if (contents->text)
1039         bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp);
1040       else
1041         bb[V][0] = ascii_layout_subtable (a, contents, bb, clip, widthp);
1042     }
1043   *heightp = bb[V][0] - bb_[V][0];
1044 }
1045
1046 void
1047 ascii_test_write (struct output_driver *driver,
1048                   const char *s, int x, int y, unsigned int options)
1049 {
1050   struct ascii_driver *a = ascii_driver_cast (driver);
1051   struct cell_contents contents;
1052   struct table_cell cell;
1053   int bb[TABLE_N_AXES][2];
1054   int width, height;
1055
1056   if (a->file == NULL && !ascii_open_page (a))
1057     return;
1058   a->y = 0;
1059
1060   contents.options = options | TAB_LEFT;
1061   contents.text = CONST_CAST (char *, s);
1062   contents.table = NULL;
1063
1064   memset (&cell, 0, sizeof cell);
1065   cell.contents = &contents;
1066   cell.n_contents = 1;
1067
1068   bb[TABLE_HORZ][0] = x;
1069   bb[TABLE_HORZ][1] = a->width;
1070   bb[TABLE_VERT][0] = y;
1071   bb[TABLE_VERT][1] = a->length;
1072
1073   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
1074
1075   a->y = 1;
1076 }
1077
1078 void
1079 ascii_test_set_length (struct output_driver *driver, int y, int length)
1080 {
1081   struct ascii_driver *a = ascii_driver_cast (driver);
1082
1083   if (a->file == NULL && !ascii_open_page (a))
1084     return;
1085
1086   if (y < 0 || y >= a->length)
1087     return;
1088   u8_line_set_length (&a->lines[y], length);
1089 }
1090 \f
1091 /* ascii_close_page () and support routines. */
1092
1093 #if HAVE_DECL_SIGWINCH
1094 static struct ascii_driver *the_driver;
1095
1096 static void
1097 winch_handler (int signum UNUSED)
1098 {
1099   update_page_size (the_driver, false);
1100 }
1101 #endif
1102
1103 static bool
1104 ascii_open_page (struct ascii_driver *a)
1105 {
1106   int i;
1107
1108   if (a->error)
1109     return false;
1110
1111   if (a->file == NULL)
1112     {
1113       a->file = fn_open (a->file_name, a->append ? "a" : "w");
1114       if (a->file != NULL)
1115         {
1116           if ( isatty (fileno (a->file)))
1117             {
1118 #if HAVE_DECL_SIGWINCH
1119               struct sigaction action;
1120               sigemptyset (&action.sa_mask);
1121               action.sa_flags = 0;
1122               action.sa_handler = winch_handler;
1123               the_driver = a;
1124               sigaction (SIGWINCH, &action, NULL);
1125 #endif
1126               a->auto_width = true;
1127               a->auto_length = true;
1128             }
1129         }
1130       else
1131         {
1132           msg_error (errno, _("ascii: opening output file `%s'"),
1133                  a->file_name);
1134           a->error = true;
1135           return false;
1136         }
1137     }
1138
1139   a->page_number++;
1140
1141   reallocate_lines (a);
1142
1143   for (i = 0; i < a->length; i++)
1144     u8_line_clear (&a->lines[i]);
1145
1146   return true;
1147 }
1148
1149 static void
1150 output_title_line (FILE *out, int width, const char *left, const char *right)
1151 {
1152   struct string s = DS_EMPTY_INITIALIZER;
1153   ds_put_byte_multiple (&s, ' ', width);
1154   if (left != NULL)
1155     {
1156       size_t length = MIN (strlen (left), width);
1157       memcpy (ds_end (&s) - width, left, length);
1158     }
1159   if (right != NULL)
1160     {
1161       size_t length = MIN (strlen (right), width);
1162       memcpy (ds_end (&s) - length, right, length);
1163     }
1164   ds_put_byte (&s, '\n');
1165   fputs (ds_cstr (&s), out);
1166   ds_destroy (&s);
1167 }
1168
1169 static void
1170 ascii_close_page (struct ascii_driver *a)
1171 {
1172   bool any_blank;
1173   int i, y;
1174
1175   a->y = 0;
1176   if (a->file == NULL)
1177     return;
1178
1179   if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1180       && !a->paginate && a->page_number > 1)
1181     putc ('\n', a->file);
1182
1183   for (i = 0; i < a->top_margin; i++)
1184     putc ('\n', a->file);
1185   if (a->headers)
1186     {
1187       char *r1, *r2;
1188
1189       r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1190       r2 = xasprintf ("%s - %s" , version, host_system);
1191
1192       output_title_line (a->file, a->width, a->title, r1);
1193       output_title_line (a->file, a->width, a->subtitle, r2);
1194       putc ('\n', a->file);
1195
1196       free (r1);
1197       free (r2);
1198     }
1199
1200   any_blank = false;
1201   for (y = 0; y < a->allocated_lines; y++)
1202     {
1203       struct u8_line *line = &a->lines[y];
1204
1205       if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1206         any_blank = true;
1207       else
1208         {
1209           if (any_blank)
1210             {
1211               putc ('\n', a->file);
1212               any_blank = false;
1213             }
1214
1215           while (ds_chomp_byte (&line->s, ' '))
1216             continue;
1217           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1218           putc ('\n', a->file);
1219         }
1220     }
1221   if (!a->squeeze_blank_lines)
1222     for (y = a->allocated_lines; y < a->length; y++)
1223       putc ('\n', a->file);
1224
1225   for (i = 0; i < a->bottom_margin; i++)
1226     putc ('\n', a->file);
1227   if (a->paginate)
1228     putc ('\f', a->file);
1229 }