output: Add support for alternating row colors.
[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/file-handle-def.h"
32 #include "data/settings.h"
33 #include "libpspp/assertion.h"
34 #include "libpspp/cast.h"
35 #include "libpspp/compiler.h"
36 #include "libpspp/message.h"
37 #include "libpspp/start-date.h"
38 #include "libpspp/string-map.h"
39 #include "libpspp/u8-line.h"
40 #include "libpspp/version.h"
41 #include "output/ascii.h"
42 #include "output/cairo.h"
43 #include "output/chart-item-provider.h"
44 #include "output/driver-provider.h"
45 #include "output/message-item.h"
46 #include "output/options.h"
47 #include "output/render.h"
48 #include "output/tab.h"
49 #include "output/table-item.h"
50 #include "output/text-item.h"
51
52 #include "gl/minmax.h"
53 #include "gl/xalloc.h"
54 #include "gl/xsize.h"
55
56 #include "gettext.h"
57 #define _(msgid) gettext (msgid)
58
59 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
60 #define H TABLE_HORZ
61 #define V TABLE_VERT
62
63 enum
64   {
65     ASCII_LINE_NONE,
66     ASCII_LINE_SINGLE,
67     ASCII_LINE_DOUBLE,
68     ASCII_N_LINES
69   };
70
71 #define N_BOX (ASCII_N_LINES * ASCII_N_LINES \
72                * ASCII_N_LINES * ASCII_N_LINES)
73
74 static const ucs4_t ascii_box_chars[N_BOX] =
75   {
76     ' ', '|', '#',
77     '-', '+', '#',
78     '=', '#', '#',
79     '|', '|', '#',
80     '+', '+', '#',
81     '#', '#', '#',
82     '#', '#', '#',
83     '#', '#', '#',
84     '#', '#', '#',
85     '-', '+', '#',
86     '-', '+', '#',
87     '#', '#', '#',
88     '+', '+', '#',
89     '+', '+', '#',
90     '#', '#', '#',
91     '#', '#', '#',
92     '#', '#', '#',
93     '#', '#', '#',
94     '=', '#', '#',
95     '#', '#', '#',
96     '=', '#', '#',
97     '#', '#', '#',
98     '#', '#', '#',
99     '#', '#', '#',
100     '#', '#', '#',
101     '#', '#', '#',
102     '#', '#', '#',
103   };
104
105 static const ucs4_t unicode_box_chars[N_BOX] =
106   {
107     0x0020, 0x2575, 0x2551,
108     0x2574, 0x256f, 0x255c,
109     0x2550, 0x255b, 0x255d,
110     0x2577, 0x2502, 0x2551,
111     0x256e, 0x2524, 0x2562,
112     0x2555, 0x2561, 0x2563,
113     0x2551, 0x2551, 0x2551,
114     0x2556, 0x2562, 0x2562,
115     0x2557, 0x2563, 0x2563,
116     0x2576, 0x2570, 0x2559,
117     0x2500, 0x2534, 0x2568,
118     0x2550, 0x2567, 0x2569,
119     0x256d, 0x251c, 0x255f,
120     0x252c, 0x253c, 0x256a,
121     0x2564, 0x256a, 0x256c,
122     0x2553, 0x255f, 0x255f,
123     0x2565, 0x256b, 0x256b,
124     0x2566, 0x256c, 0x256c,
125     0x2550, 0x2558, 0x255a,
126     0x2550, 0x2567, 0x2569,
127     0x2550, 0x2567, 0x2569,
128     0x2552, 0x255e, 0x2560,
129     0x2564, 0x256a, 0x256c,
130     0x2564, 0x256a, 0x256c,
131     0x2554, 0x2560, 0x2560,
132     0x2560, 0x256c, 0x256c,
133     0x2566, 0x256c, 0x256c,
134   };
135
136 static int
137 ascii_line_from_render_line (int render_line)
138 {
139   switch (render_line)
140     {
141     case RENDER_LINE_NONE:
142       return ASCII_LINE_NONE;
143
144     case RENDER_LINE_SINGLE:
145     case RENDER_LINE_DASHED:
146     case RENDER_LINE_THICK:
147     case RENDER_LINE_THIN:
148       return ASCII_LINE_SINGLE;
149
150     case RENDER_LINE_DOUBLE:
151       return ASCII_LINE_DOUBLE;
152
153     default:
154       return ASCII_LINE_NONE;
155     }
156
157 }
158
159 static int
160 make_box_index (int left_, int right_, int top_, int bottom_)
161 {
162   bool rtl = render_direction_rtl ();
163   int left = ascii_line_from_render_line (rtl ? right_ : left_);
164   int right = ascii_line_from_render_line (rtl ? left_ : right_);
165   int top = ascii_line_from_render_line (top_);
166   int bottom = ascii_line_from_render_line (bottom_);
167
168   int idx = right;
169   idx = idx * ASCII_N_LINES + bottom;
170   idx = idx * ASCII_N_LINES + left;
171   idx = idx * ASCII_N_LINES + top;
172   return idx;
173 }
174
175 /* ASCII output driver. */
176 struct ascii_driver
177   {
178     struct output_driver driver;
179
180     /* User parameters. */
181     bool append;                /* Append if output file already exists? */
182     bool emphasis;              /* Enable bold and underline in output? */
183     char *chart_file_name;      /* Name of files used for charts. */
184
185 #ifdef HAVE_CAIRO
186     /* Colours for charts */
187     struct xr_color fg;
188     struct xr_color bg;
189 #endif
190
191     int width;                  /* Page width. */
192     bool auto_width;            /* Use viewwidth as page width? */
193
194     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
195
196     const ucs4_t *box;          /* Line & box drawing characters. */
197
198     /* Internal state. */
199     struct file_handle *handle;
200     FILE *file;                 /* Output file. */
201     bool error;                 /* Output error? */
202     struct u8_line *lines;      /* Page content. */
203     int allocated_lines;        /* Number of lines allocated. */
204     int chart_cnt;              /* Number of charts so far. */
205   };
206
207 static const struct output_driver_class ascii_driver_class;
208
209 static void ascii_submit (struct output_driver *, const struct output_item *);
210
211 static bool update_page_size (struct ascii_driver *, bool issue_error);
212 static int parse_page_size (struct driver_option *);
213
214 static bool ascii_open_page (struct ascii_driver *);
215
216 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
217                              enum render_line_style styles[TABLE_N_AXES][2],
218                              struct cell_color colors[TABLE_N_AXES][2]);
219 static void ascii_measure_cell_width (void *, const struct table_cell *,
220                                       int *min, int *max);
221 static int ascii_measure_cell_height (void *, const struct table_cell *,
222                                       int width);
223 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
224                              int bb[TABLE_N_AXES][2],
225                              int spill[TABLE_N_AXES][2],
226                              int clip[TABLE_N_AXES][2]);
227
228 static struct ascii_driver *
229 ascii_driver_cast (struct output_driver *driver)
230 {
231   assert (driver->class == &ascii_driver_class);
232   return UP_CAST (driver, struct ascii_driver, driver);
233 }
234
235 static struct driver_option *
236 opt (struct output_driver *d, struct string_map *options, const char *key,
237      const char *default_value)
238 {
239   return driver_option_get (d, options, key, default_value);
240 }
241
242 static struct output_driver *
243 ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
244               struct string_map *o)
245 {
246   enum { BOX_ASCII, BOX_UNICODE } box;
247   int min_break[TABLE_N_AXES];
248   struct output_driver *d;
249   struct ascii_driver *a;
250
251   a = xzalloc (sizeof *a);
252   d = &a->driver;
253   output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
254   a->append = parse_boolean (opt (d, o, "append", "false"));
255   a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
256
257   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
258   a->handle = fh;
259
260   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
261
262   a->width = parse_page_size (opt (d, o, "width", "79"));
263   a->auto_width = a->width < 0;
264   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
265 #ifdef HAVE_CAIRO
266   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
267   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
268 #endif
269   box = parse_enum (opt (d, o, "box", "ascii"),
270                     "ascii", BOX_ASCII,
271                     "unicode", BOX_UNICODE,
272                     NULL_SENTINEL);
273   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
274
275   a->file = NULL;
276   a->error = false;
277   a->lines = NULL;
278   a->allocated_lines = 0;
279   a->chart_cnt = 1;
280
281   if (!update_page_size (a, true))
282     goto error;
283
284   return d;
285
286 error:
287   output_driver_destroy (d);
288   return NULL;
289 }
290
291 static int
292 parse_page_size (struct driver_option *option)
293 {
294   int dim = atol (option->default_value);
295
296   if (option->value != NULL)
297     {
298       if (!strcmp (option->value, "auto"))
299         dim = -1;
300       else
301         {
302           int value;
303           char *tail;
304
305           errno = 0;
306           value = strtol (option->value, &tail, 0);
307           if (dim >= 1 && errno != ERANGE && *tail == '\0')
308             dim = value;
309           else
310             msg (MW, _("%s: %s must be positive integer or `auto'"),
311                    option->driver_name, option->name);
312         }
313     }
314
315   driver_option_destroy (option);
316
317   return dim;
318 }
319
320 /* Re-calculates the page width based on settings, margins, and, if "auto" is
321    set, the size of the user's terminal window or GUI output window. */
322 static bool
323 update_page_size (struct ascii_driver *a, bool issue_error)
324 {
325   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
326
327   if (a->auto_width)
328     {
329       a->width = settings_get_viewwidth ();
330       a->min_break[H] = a->width / 2;
331     }
332
333   if (a->width < MIN_WIDTH)
334     {
335       if (issue_error)
336         msg (ME,
337                _("ascii: page must be at least %d characters wide, but "
338                  "as configured is only %d characters"),
339                MIN_WIDTH,
340                a->width);
341       if (a->width < MIN_WIDTH)
342         a->width = MIN_WIDTH;
343       return false;
344     }
345
346   return true;
347 }
348
349 static void
350 ascii_destroy (struct output_driver *driver)
351 {
352   struct ascii_driver *a = ascii_driver_cast (driver);
353   int i;
354
355   if (a->file != NULL)
356     fn_close (a->handle, a->file);
357   fh_unref (a->handle);
358   free (a->chart_file_name);
359   for (i = 0; i < a->allocated_lines; i++)
360     u8_line_destroy (&a->lines[i]);
361   free (a->lines);
362   free (a);
363 }
364
365 static void
366 ascii_flush (struct output_driver *driver)
367 {
368   struct ascii_driver *a = ascii_driver_cast (driver);
369   if (a->file)
370     fflush (a->file);
371 }
372
373 static void
374 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
375 {
376   for (size_t y = 0; y < n_lines; y++)
377     {
378       struct u8_line *line = &a->lines[y];
379
380       while (ds_chomp_byte (&line->s, ' '))
381         continue;
382       fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
383       putc ('\n', a->file);
384
385       u8_line_clear (&a->lines[y]);
386     }
387 }
388
389 static void
390 ascii_output_table_item (struct ascii_driver *a,
391                          const struct table_item *table_item)
392 {
393   struct render_params params;
394   struct render_pager *p;
395   int i;
396
397   update_page_size (a, false);
398
399   params.draw_line = ascii_draw_line;
400   params.measure_cell_width = ascii_measure_cell_width;
401   params.measure_cell_height = ascii_measure_cell_height;
402   params.adjust_break = NULL;
403   params.draw_cell = ascii_draw_cell;
404   params.aux = a;
405   params.size[H] = a->width;
406   params.size[V] = INT_MAX;
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   for (i = 0; i < TABLE_N_AXES; i++)
416     params.min_break[i] = a->min_break[i];
417   params.supports_margins = false;
418
419   if (a->file)
420     putc ('\n', a->file);
421   else if (!ascii_open_page (a))
422     return;
423
424   p = render_pager_create (&params, table_item);
425   for (int i = 0; render_pager_has_next (p); i++)
426     {
427       if (i)
428         putc ('\n', a->file);
429       ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
430     }
431   render_pager_destroy (p);
432 }
433
434 static void
435 ascii_output_text (struct ascii_driver *a, const char *text)
436 {
437   struct table_item *table_item;
438
439   table_item = table_item_create (table_from_string (TAB_LEFT, text),
440                                   NULL, NULL);
441   ascii_output_table_item (a, table_item);
442   table_item_unref (table_item);
443 }
444
445 static void
446 ascii_submit (struct output_driver *driver,
447               const struct output_item *output_item)
448 {
449   struct ascii_driver *a = ascii_driver_cast (driver);
450
451   if (a->error)
452     return;
453
454   if (is_table_item (output_item))
455     ascii_output_table_item (a, to_table_item (output_item));
456 #ifdef HAVE_CAIRO
457   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
458     {
459       struct chart_item *chart_item = to_chart_item (output_item);
460       char *file_name;
461
462       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
463                                      a->chart_cnt++,
464                                      &a->fg,
465                                      &a->bg);
466       if (file_name != NULL)
467         {
468           struct text_item *text_item;
469
470           text_item = text_item_create_format (
471             TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
472
473           ascii_submit (driver, &text_item->output_item);
474           text_item_unref (text_item);
475           free (file_name);
476         }
477     }
478 #endif  /* HAVE_CAIRO */
479   else if (is_text_item (output_item))
480     {
481       const struct text_item *text_item = to_text_item (output_item);
482       enum text_item_type type = text_item_get_type (text_item);
483       const char *text = text_item_get_text (text_item);
484
485       switch (type)
486         {
487         case TEXT_ITEM_TITLE:
488         case TEXT_ITEM_SUBTITLE:
489         case TEXT_ITEM_COMMAND_OPEN:
490         case TEXT_ITEM_COMMAND_CLOSE:
491           break;
492
493         case TEXT_ITEM_BLANK_LINE:
494           break;
495
496         case TEXT_ITEM_EJECT_PAGE:
497           break;
498
499         default:
500           ascii_output_text (a, text);
501           break;
502         }
503     }
504   else if (is_message_item (output_item))
505     {
506       const struct message_item *message_item = to_message_item (output_item);
507       const struct msg *msg = message_item_get_msg (message_item);
508       char *s = msg_to_string (msg, message_item->command_name);
509       ascii_output_text (a, s);
510       free (s);
511     }
512 }
513
514 const struct output_driver_factory txt_driver_factory =
515   { "txt", "-", ascii_create };
516 const struct output_driver_factory list_driver_factory =
517   { "list", "-", ascii_create };
518
519 static const struct output_driver_class ascii_driver_class =
520   {
521     "text",
522     ascii_destroy,
523     ascii_submit,
524     ascii_flush,
525   };
526 \f
527 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
528                             int n);
529 static void ascii_layout_cell (struct ascii_driver *,
530                                const struct table_cell *,
531                                int bb[TABLE_N_AXES][2],
532                                int clip[TABLE_N_AXES][2],
533                                int *width, int *height);
534
535 static void
536 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
537                  enum render_line_style styles[TABLE_N_AXES][2],
538                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
539 {
540   struct ascii_driver *a = a_;
541   char mbchar[6];
542   int x0, y0, x1, y1;
543   ucs4_t uc;
544   int mblen;
545   int x, y;
546
547   /* Clip to the page. */
548   x0 = MAX (bb[H][0], 0);
549   y0 = MAX (bb[V][0], 0);
550   x1 = MIN (bb[H][1], a->width);
551   y1 = bb[V][1];
552   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
553     return;
554
555   /* Draw. */
556   uc = a->box[make_box_index (styles[V][0], styles[V][1],
557                               styles[H][0], styles[H][1])];
558   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
559   for (y = y0; y < y1; y++)
560     {
561       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
562       for (x = x0; x < x1; x++)
563         {
564           memcpy (p, mbchar, mblen);
565           p += mblen;
566         }
567     }
568 }
569
570 static void
571 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
572                           int *min_width, int *max_width)
573 {
574   struct ascii_driver *a = a_;
575   int bb[TABLE_N_AXES][2];
576   int clip[TABLE_N_AXES][2];
577   int h;
578
579   bb[H][0] = 0;
580   bb[H][1] = INT_MAX;
581   bb[V][0] = 0;
582   bb[V][1] = INT_MAX;
583   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
584   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
585
586   if (cell->n_contents != 1
587       || cell->contents[0].n_footnotes
588       || strchr (cell->contents[0].text, ' '))
589     {
590       bb[H][1] = 1;
591       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
592     }
593   else
594     *min_width = *max_width;
595 }
596
597 static int
598 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
599 {
600   struct ascii_driver *a = a_;
601   int bb[TABLE_N_AXES][2];
602   int clip[TABLE_N_AXES][2];
603   int w, h;
604
605   bb[H][0] = 0;
606   bb[H][1] = width;
607   bb[V][0] = 0;
608   bb[V][1] = INT_MAX;
609   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
610   ascii_layout_cell (a, cell, bb, clip, &w, &h);
611   return h;
612 }
613
614 static void
615 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
616                  int bb[TABLE_N_AXES][2],
617                  int spill[TABLE_N_AXES][2] UNUSED,
618                  int clip[TABLE_N_AXES][2])
619 {
620   struct ascii_driver *a = a_;
621   int w, h;
622
623   ascii_layout_cell (a, cell, bb, clip, &w, &h);
624 }
625
626 static char *
627 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
628 {
629   if (y >= a->allocated_lines)
630     {
631       size_t new_alloc = MAX (25, a->allocated_lines);
632       while (new_alloc <= y)
633         new_alloc = xtimes (new_alloc, 2);
634       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
635       for (size_t i = a->allocated_lines; i < new_alloc; i++)
636         u8_line_init (&a->lines[i]);
637       a->allocated_lines = new_alloc;
638     }
639   return u8_line_reserve (&a->lines[y], x0, x1, n);
640 }
641
642 static void
643 text_draw (struct ascii_driver *a, unsigned int options,
644            bool bold, bool underline,
645            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
646            int y, const uint8_t *string, int n, size_t width)
647 {
648   int x0 = MAX (0, clip[H][0]);
649   int y0 = MAX (0, clip[V][0]);
650   int x1 = MIN (a->width, clip[H][1]);
651   int y1 = clip[V][1];
652   int x;
653
654   if (y < y0 || y >= y1)
655     return;
656
657   switch (options & TAB_HALIGN)
658     {
659     case TAB_LEFT:
660       x = bb[H][0];
661       break;
662     case TAB_CENTER:
663       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
664       break;
665     case TAB_RIGHT:
666       x = bb[H][1] - width;
667       break;
668     default:
669       NOT_REACHED ();
670     }
671   if (x >= x1)
672     return;
673
674   while (x < x0)
675     {
676       ucs4_t uc;
677       int mblen;
678       int w;
679
680       if (n == 0)
681         return;
682       mblen = u8_mbtouc (&uc, string, n);
683
684       string += mblen;
685       n -= mblen;
686
687       w = uc_width (uc, "UTF-8");
688       if (w > 0)
689         {
690           x += w;
691           width -= w;
692         }
693     }
694   if (n == 0)
695     return;
696
697   if (x + width > x1)
698     {
699       int ofs;
700
701       ofs = width = 0;
702       for (ofs = 0; ofs < n; )
703         {
704           ucs4_t uc;
705           int mblen;
706           int w;
707
708           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
709
710           w = uc_width (uc, "UTF-8");
711           if (w > 0)
712             {
713               if (width + w > x1 - x)
714                 break;
715               width += w;
716             }
717           ofs += mblen;
718         }
719       n = ofs;
720       if (n == 0)
721         return;
722     }
723
724   if (!a->emphasis || (!bold && !underline))
725     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
726   else
727     {
728       size_t n_out;
729       size_t ofs;
730       char *out;
731       int mblen;
732
733       /* First figure out how many bytes need to be inserted. */
734       n_out = n;
735       for (ofs = 0; ofs < n; ofs += mblen)
736         {
737           ucs4_t uc;
738           int w;
739
740           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
741           w = uc_width (uc, "UTF-8");
742
743           if (w > 0)
744             {
745               if (bold)
746                 n_out += 1 + mblen;
747               if (underline)
748                 n_out += 2;
749             }
750         }
751
752       /* Then insert them. */
753       out = ascii_reserve (a, y, x, x + width, n_out);
754       for (ofs = 0; ofs < n; ofs += mblen)
755         {
756           ucs4_t uc;
757           int w;
758
759           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
760           w = uc_width (uc, "UTF-8");
761
762           if (w > 0)
763             {
764               if (bold)
765                 {
766                   out = mempcpy (out, string + ofs, mblen);
767                   *out++ = '\b';
768                 }
769               if (underline)
770                 {
771                   *out++ = '_';
772                   *out++ = '\b';
773                 }
774             }
775           out = mempcpy (out, string + ofs, mblen);
776         }
777     }
778 }
779
780 static int
781 ascii_layout_cell_text (struct ascii_driver *a,
782                         const struct cell_contents *contents,
783                         bool bold, bool underline,
784                         int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
785                         int *widthp)
786 {
787   size_t length;
788   const char *text;
789   char *breaks;
790   int bb_width;
791   size_t pos;
792   int y;
793
794   y = bb[V][0];
795   length = strlen (contents->text);
796   if (contents->n_footnotes)
797     {
798       struct string s;
799       int i;
800
801       ds_init_empty (&s);
802       ds_extend (&s, length + contents->n_footnotes * 4);
803       ds_put_cstr (&s, contents->text);
804       for (i = 0; i < contents->n_footnotes; i++)
805         ds_put_format (&s, "[%s]", contents->footnotes[i]->marker);
806
807       length = ds_length (&s);
808       text = ds_steal_cstr (&s);
809     }
810   else
811     {
812       if (length == 0)
813         return y;
814       text = contents->text;
815     }
816
817   breaks = xmalloc (length + 1);
818   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
819                           "UTF-8", breaks);
820   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
821                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
822
823   pos = 0;
824   bb_width = bb[H][1] - bb[H][0];
825   for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
826     {
827       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
828       const char *b = breaks + pos;
829       size_t n = length - pos;
830
831       size_t last_break_ofs = 0;
832       int last_break_width = 0;
833       int width = 0;
834       size_t graph_ofs;
835       size_t ofs;
836
837       for (ofs = 0; ofs < n; )
838         {
839           ucs4_t uc;
840           int mblen;
841           int w;
842
843           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
844           if (b[ofs] == UC_BREAK_MANDATORY)
845             break;
846           else if (b[ofs] == UC_BREAK_POSSIBLE)
847             {
848               last_break_ofs = ofs;
849               last_break_width = width;
850             }
851
852           w = uc_width (uc, "UTF-8");
853           if (w > 0)
854             {
855               if (width + w > bb_width)
856                 {
857                   if (isspace (line[ofs]))
858                     break;
859                   else if (last_break_ofs != 0)
860                     {
861                       ofs = last_break_ofs;
862                       width = last_break_width;
863                       break;
864                     }
865                 }
866               width += w;
867             }
868           ofs += mblen;
869         }
870
871       /* Trim any trailing spaces off the end of the text to be drawn. */
872       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
873         if (!isspace (line[graph_ofs - 1]))
874           break;
875       width -= ofs - graph_ofs;
876
877       /* Draw text. */
878       text_draw (a, contents->options, bold, underline,
879                  bb, clip, y, line, graph_ofs, width);
880
881       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
882          past any spaces past the end of the line (but not past a new-line). */
883       if (b[ofs] == UC_BREAK_MANDATORY)
884         ofs++;
885       else
886         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
887           ofs++;
888
889       if (width > *widthp)
890         *widthp = width;
891       pos += ofs;
892     }
893
894   free (breaks);
895   if (text != contents->text)
896     free (CONST_CAST (char *, text));
897
898   return y;
899 }
900
901 static void
902 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
903                    int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
904                    int *widthp, int *heightp)
905 {
906   int bb[TABLE_N_AXES][2];
907   size_t i;
908
909   *widthp = 0;
910   *heightp = 0;
911
912   memcpy (bb, bb_, sizeof bb);
913   for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++)
914     {
915       const struct cell_contents *contents = &cell->contents[i];
916
917       /* Put a blank line between contents. */
918       if (i > 0)
919         {
920           bb[V][0]++;
921           if (bb[V][0] >= bb[V][1])
922             break;
923         }
924
925       bb[V][0] = ascii_layout_cell_text (a, contents, cell->style->bold,
926                                          cell->style->underline,
927                                          bb, clip, widthp);
928     }
929   *heightp = bb[V][0] - bb_[V][0];
930 }
931
932 void
933 ascii_test_write (struct output_driver *driver,
934                   const char *s, int x, int y, bool bold, bool underline)
935 {
936   struct ascii_driver *a = ascii_driver_cast (driver);
937   int bb[TABLE_N_AXES][2];
938   int width, height;
939
940   if (a->file == NULL && !ascii_open_page (a))
941     return;
942
943   struct cell_contents contents = {
944     .options = TAB_LEFT,
945     .text = CONST_CAST (char *, s),
946   };
947   struct cell_style cell_style = {
948     .bold = bold,
949     .underline = underline,
950   };
951   struct table_cell cell = {
952     .contents = &contents,
953     .n_contents = 1,
954     .style = &cell_style,
955   };
956
957   bb[TABLE_HORZ][0] = x;
958   bb[TABLE_HORZ][1] = a->width;
959   bb[TABLE_VERT][0] = y;
960   bb[TABLE_VERT][1] = INT_MAX;
961
962   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
963 }
964
965 void
966 ascii_test_set_length (struct output_driver *driver, int y, int length)
967 {
968   struct ascii_driver *a = ascii_driver_cast (driver);
969
970   if (a->file == NULL && !ascii_open_page (a))
971     return;
972
973   if (y < 0)
974     return;
975   u8_line_set_length (&a->lines[y], length);
976 }
977
978 void
979 ascii_test_flush (struct output_driver *driver)
980 {
981   struct ascii_driver *a = ascii_driver_cast (driver);
982
983   for (size_t i = a->allocated_lines; i-- > 0; )
984     if (a->lines[i].width)
985       {
986         ascii_output_lines (a, i + 1);
987         break;
988       }
989 }
990 \f
991 /* ascii_close_page () and support routines. */
992
993 #if HAVE_DECL_SIGWINCH
994 static struct ascii_driver *the_driver;
995
996 static void
997 winch_handler (int signum UNUSED)
998 {
999   update_page_size (the_driver, false);
1000 }
1001 #endif
1002
1003 static bool
1004 ascii_open_page (struct ascii_driver *a)
1005 {
1006   if (a->error)
1007     return false;
1008
1009   if (a->file == NULL)
1010     {
1011       a->file = fn_open (a->handle, a->append ? "a" : "w");
1012       if (a->file != NULL)
1013         {
1014           if ( isatty (fileno (a->file)))
1015             {
1016 #if HAVE_DECL_SIGWINCH
1017               struct sigaction action;
1018               sigemptyset (&action.sa_mask);
1019               action.sa_flags = 0;
1020               action.sa_handler = winch_handler;
1021               the_driver = a;
1022               sigaction (SIGWINCH, &action, NULL);
1023 #endif
1024               a->auto_width = true;
1025             }
1026         }
1027       else
1028         {
1029           msg_error (errno, _("ascii: opening output file `%s'"),
1030                      fh_get_file_name (a->handle));
1031           a->error = true;
1032           return false;
1033         }
1034     }
1035
1036   return true;
1037 }