2584faffaacf22777388bb4a9170a5c9b3cf037e
[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/pivot-table.h"
48 #include "output/render.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 cell_color fg;
188     struct cell_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     struct render_params params;
206   };
207
208 static const struct output_driver_class ascii_driver_class;
209
210 static void ascii_submit (struct output_driver *, const struct output_item *);
211
212 static bool update_page_size (struct ascii_driver *, bool issue_error);
213 static int parse_page_size (struct driver_option *);
214
215 static bool ascii_open_page (struct ascii_driver *);
216
217 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
218                              enum render_line_style styles[TABLE_N_AXES][2],
219                              struct cell_color colors[TABLE_N_AXES][2]);
220 static void ascii_measure_cell_width (void *, const struct table_cell *,
221                                       int *min, int *max);
222 static int ascii_measure_cell_height (void *, const struct table_cell *,
223                                       int width);
224 static void ascii_draw_cell (void *, const struct table_cell *, int color_idx,
225                              int bb[TABLE_N_AXES][2],
226                              int spill[TABLE_N_AXES][2],
227                              int clip[TABLE_N_AXES][2]);
228
229 static struct ascii_driver *
230 ascii_driver_cast (struct output_driver *driver)
231 {
232   assert (driver->class == &ascii_driver_class);
233   return UP_CAST (driver, struct ascii_driver, driver);
234 }
235
236 static struct driver_option *
237 opt (struct output_driver *d, struct string_map *options, const char *key,
238      const char *default_value)
239 {
240   return driver_option_get (d, options, key, default_value);
241 }
242
243 /* Return true iff the terminal appears to be an xterm with
244    UTF-8 capabilities */
245 static bool
246 term_is_utf8_xterm (void)
247 {
248   const char *term = getenv ("TERM");
249   const char *xterm_locale = getenv ("XTERM_LOCAL");
250   return (term && xterm_locale
251           && !strcmp (term, "xterm")
252           && (strcasestr (xterm_locale, "utf8")
253               || strcasestr (xterm_locale, "utf-8")));
254 }
255
256 static struct output_driver *
257 ascii_create (struct  file_handle *fh, enum settings_output_devices device_type,
258               struct string_map *o)
259 {
260   enum { BOX_ASCII, BOX_UNICODE } box;
261   int min_break[TABLE_N_AXES];
262   struct output_driver *d;
263   struct ascii_driver *a;
264
265   a = xzalloc (sizeof *a);
266   d = &a->driver;
267   output_driver_init (&a->driver, &ascii_driver_class, fh_get_file_name (fh), device_type);
268   a->append = parse_boolean (opt (d, o, "append", "false"));
269   a->emphasis = parse_boolean (opt (d, o, "emphasis", "false"));
270
271   a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", fh_get_file_name (fh)));
272   a->handle = fh;
273
274   min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
275
276   a->width = parse_page_size (opt (d, o, "width", "79"));
277   a->auto_width = a->width < 0;
278   a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
279 #ifdef HAVE_CAIRO
280   parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
281   parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
282 #endif
283
284   const char *default_box = (!strcmp (fh_get_file_name (fh), "-")
285                              && isatty (STDOUT_FILENO)
286                              && (!strcmp (locale_charset (), "UTF-8")
287                                  || term_is_utf8_xterm ())
288                              ? "unicode" : "ascii");
289   box = parse_enum (opt (d, o, "box", default_box),
290                     "ascii", BOX_ASCII,
291                     "unicode", BOX_UNICODE,
292                     NULL_SENTINEL);
293   a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
294
295   a->file = NULL;
296   a->error = false;
297   a->lines = NULL;
298   a->allocated_lines = 0;
299   a->chart_cnt = 1;
300
301   a->params.draw_line = ascii_draw_line;
302   a->params.measure_cell_width = ascii_measure_cell_width;
303   a->params.measure_cell_height = ascii_measure_cell_height;
304   a->params.adjust_break = NULL;
305   a->params.draw_cell = ascii_draw_cell;
306   a->params.aux = a;
307   a->params.size[H] = a->width;
308   a->params.size[V] = INT_MAX;
309   a->params.font_size[H] = 1;
310   a->params.font_size[V] = 1;
311   for (int i = 0; i < RENDER_N_LINES; i++)
312     {
313       int width = i == RENDER_LINE_NONE ? 0 : 1;
314       a->params.line_widths[H][i] = width;
315       a->params.line_widths[V][i] = width;
316     }
317   for (int i = 0; i < TABLE_N_AXES; i++)
318     a->params.min_break[i] = a->min_break[i];
319   a->params.supports_margins = false;
320   a->params.rtl = render_direction_rtl ();
321
322   if (!update_page_size (a, true))
323     goto error;
324
325   return d;
326
327 error:
328   output_driver_destroy (d);
329   return NULL;
330 }
331
332 static int
333 parse_page_size (struct driver_option *option)
334 {
335   int dim = atol (option->default_value);
336
337   if (option->value != NULL)
338     {
339       if (!strcmp (option->value, "auto"))
340         dim = -1;
341       else
342         {
343           int value;
344           char *tail;
345
346           errno = 0;
347           value = strtol (option->value, &tail, 0);
348           if (dim >= 1 && errno != ERANGE && *tail == '\0')
349             dim = value;
350           else
351             msg (MW, _("%s: %s must be positive integer or `auto'"),
352                    option->driver_name, option->name);
353         }
354     }
355
356   driver_option_destroy (option);
357
358   return dim;
359 }
360
361 /* Re-calculates the page width based on settings, margins, and, if "auto" is
362    set, the size of the user's terminal window or GUI output window. */
363 static bool
364 update_page_size (struct ascii_driver *a, bool issue_error)
365 {
366   enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
367
368   if (a->auto_width)
369     {
370       a->params.size[H] = a->width = settings_get_viewwidth ();
371       a->params.min_break[H] = a->min_break[H] = a->width / 2;
372     }
373
374   if (a->width < MIN_WIDTH)
375     {
376       if (issue_error)
377         msg (ME,
378                _("ascii: page must be at least %d characters wide, but "
379                  "as configured is only %d characters"),
380                MIN_WIDTH,
381                a->width);
382       if (a->width < MIN_WIDTH)
383         a->params.size[H] = a->width = MIN_WIDTH;
384       return false;
385     }
386
387   return true;
388 }
389
390 static void
391 ascii_destroy (struct output_driver *driver)
392 {
393   struct ascii_driver *a = ascii_driver_cast (driver);
394   int i;
395
396   if (a->file != NULL)
397     fn_close (a->handle, a->file);
398   fh_unref (a->handle);
399   free (a->chart_file_name);
400   for (i = 0; i < a->allocated_lines; i++)
401     u8_line_destroy (&a->lines[i]);
402   free (a->lines);
403   free (a);
404 }
405
406 static void
407 ascii_flush (struct output_driver *driver)
408 {
409   struct ascii_driver *a = ascii_driver_cast (driver);
410   if (a->file)
411     fflush (a->file);
412 }
413
414 static void
415 ascii_output_lines (struct ascii_driver *a, size_t n_lines)
416 {
417   for (size_t y = 0; y < n_lines; y++)
418     {
419       struct u8_line *line = &a->lines[y];
420
421       while (ds_chomp_byte (&line->s, ' '))
422         continue;
423       fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
424       putc ('\n', a->file);
425
426       u8_line_clear (&a->lines[y]);
427     }
428 }
429
430 static void
431 ascii_output_table_item (struct ascii_driver *a,
432                          const struct table_item *table_item)
433 {
434   struct render_pager *p;
435
436   update_page_size (a, false);
437
438   if (a->file)
439     putc ('\n', a->file);
440   else if (!ascii_open_page (a))
441     return;
442
443   p = render_pager_create (&a->params, table_item);
444   for (int i = 0; render_pager_has_next (p); i++)
445     {
446       if (i)
447         putc ('\n', a->file);
448       ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
449     }
450   render_pager_destroy (p);
451 }
452
453 static void
454 ascii_output_table_item_unref (struct ascii_driver *a,
455                                struct table_item *table_item)
456 {
457   ascii_output_table_item (a, table_item);
458   table_item_unref (table_item);
459 }
460
461 static void
462 ascii_output_text (struct ascii_driver *a, const char *text)
463 {
464   struct pivot_table *pt = pivot_table_create_for_text (
465     NULL, pivot_value_new_user_text (text, -1));
466   ascii_output_table_item_unref (a, table_item_create (pt));
467 }
468
469 static void
470 ascii_submit (struct output_driver *driver,
471               const struct output_item *output_item)
472 {
473   struct ascii_driver *a = ascii_driver_cast (driver);
474
475   if (a->error)
476     return;
477
478   if (is_table_item (output_item))
479     ascii_output_table_item (a, to_table_item (output_item));
480 #ifdef HAVE_CAIRO
481   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
482     {
483       struct chart_item *chart_item = to_chart_item (output_item);
484       char *file_name;
485
486       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
487                                      a->chart_cnt++,
488                                      &a->fg,
489                                      &a->bg);
490       if (file_name != NULL)
491         {
492           struct text_item *text_item;
493
494           text_item = text_item_create_format (
495             TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
496
497           ascii_submit (driver, &text_item->output_item);
498           text_item_unref (text_item);
499           free (file_name);
500         }
501     }
502 #endif  /* HAVE_CAIRO */
503   else if (is_text_item (output_item))
504     {
505       const struct text_item *text_item = to_text_item (output_item);
506       enum text_item_type type = text_item_get_type (text_item);
507
508       if (type != TEXT_ITEM_PAGE_TITLE && type != TEXT_ITEM_EJECT_PAGE)
509         ascii_output_table_item_unref (
510           a, text_item_to_table_item (text_item_ref (text_item)));
511     }
512   else if (is_message_item (output_item))
513     {
514       const struct message_item *message_item = to_message_item (output_item);
515       char *s = msg_to_string (message_item_get_msg (message_item));
516       ascii_output_text (a, s);
517       free (s);
518     }
519 }
520
521 const struct output_driver_factory txt_driver_factory =
522   { "txt", "-", ascii_create };
523 const struct output_driver_factory list_driver_factory =
524   { "list", "-", ascii_create };
525
526 static const struct output_driver_class ascii_driver_class =
527   {
528     "text",
529     ascii_destroy,
530     ascii_submit,
531     ascii_flush,
532   };
533 \f
534 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
535                             int n);
536 static void ascii_layout_cell (struct ascii_driver *,
537                                const struct table_cell *,
538                                int bb[TABLE_N_AXES][2],
539                                int clip[TABLE_N_AXES][2],
540                                int *width, int *height);
541
542 static void
543 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
544                  enum render_line_style styles[TABLE_N_AXES][2],
545                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
546 {
547   struct ascii_driver *a = a_;
548   char mbchar[6];
549   int x0, y0, x1, y1;
550   ucs4_t uc;
551   int mblen;
552   int x, y;
553
554   /* Clip to the page. */
555   x0 = MAX (bb[H][0], 0);
556   y0 = MAX (bb[V][0], 0);
557   x1 = MIN (bb[H][1], a->width);
558   y1 = bb[V][1];
559   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
560     return;
561
562   /* Draw. */
563   uc = a->box[make_box_index (styles[V][0], styles[V][1],
564                               styles[H][0], styles[H][1])];
565   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
566   for (y = y0; y < y1; y++)
567     {
568       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
569       for (x = x0; x < x1; x++)
570         {
571           memcpy (p, mbchar, mblen);
572           p += mblen;
573         }
574     }
575 }
576
577 static void
578 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
579                           int *min_width, int *max_width)
580 {
581   struct ascii_driver *a = a_;
582   int bb[TABLE_N_AXES][2];
583   int clip[TABLE_N_AXES][2];
584   int h;
585
586   bb[H][0] = 0;
587   bb[H][1] = INT_MAX;
588   bb[V][0] = 0;
589   bb[V][1] = INT_MAX;
590   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
591   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
592
593   if (cell->n_footnotes || strchr (cell->text, ' '))
594     {
595       bb[H][1] = 1;
596       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
597     }
598   else
599     *min_width = *max_width;
600 }
601
602 static int
603 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
604 {
605   struct ascii_driver *a = a_;
606   int bb[TABLE_N_AXES][2];
607   int clip[TABLE_N_AXES][2];
608   int w, h;
609
610   bb[H][0] = 0;
611   bb[H][1] = width;
612   bb[V][0] = 0;
613   bb[V][1] = INT_MAX;
614   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
615   ascii_layout_cell (a, cell, bb, clip, &w, &h);
616   return h;
617 }
618
619 static void
620 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
621                  int bb[TABLE_N_AXES][2],
622                  int spill[TABLE_N_AXES][2] UNUSED,
623                  int clip[TABLE_N_AXES][2])
624 {
625   struct ascii_driver *a = a_;
626   int w, h;
627
628   ascii_layout_cell (a, cell, bb, clip, &w, &h);
629 }
630
631 static char *
632 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
633 {
634   if (y >= a->allocated_lines)
635     {
636       size_t new_alloc = MAX (25, a->allocated_lines);
637       while (new_alloc <= y)
638         new_alloc = xtimes (new_alloc, 2);
639       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
640       for (size_t i = a->allocated_lines; i < new_alloc; i++)
641         u8_line_init (&a->lines[i]);
642       a->allocated_lines = new_alloc;
643     }
644   return u8_line_reserve (&a->lines[y], x0, x1, n);
645 }
646
647 static void
648 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
649            bool bold, bool underline,
650            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
651            int y, const uint8_t *string, int n, size_t width)
652 {
653   int x0 = MAX (0, clip[H][0]);
654   int y0 = MAX (0, clip[V][0]);
655   int x1 = MIN (a->width, clip[H][1]);
656   int y1 = clip[V][1];
657   int x;
658
659   if (y < y0 || y >= y1)
660     return;
661
662   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
663     {
664     case TABLE_HALIGN_LEFT:
665       x = bb[H][0];
666       break;
667     case TABLE_HALIGN_CENTER:
668       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
669       break;
670     case TABLE_HALIGN_RIGHT:
671     case TABLE_HALIGN_DECIMAL:
672       x = bb[H][1] - width;
673       break;
674     default:
675       NOT_REACHED ();
676     }
677   if (x >= x1)
678     return;
679
680   while (x < x0)
681     {
682       ucs4_t uc;
683       int mblen;
684       int w;
685
686       if (n == 0)
687         return;
688       mblen = u8_mbtouc (&uc, string, n);
689
690       string += mblen;
691       n -= mblen;
692
693       w = uc_width (uc, "UTF-8");
694       if (w > 0)
695         {
696           x += w;
697           width -= w;
698         }
699     }
700   if (n == 0)
701     return;
702
703   if (x + width > x1)
704     {
705       int ofs;
706
707       ofs = width = 0;
708       for (ofs = 0; ofs < n; )
709         {
710           ucs4_t uc;
711           int mblen;
712           int w;
713
714           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
715
716           w = uc_width (uc, "UTF-8");
717           if (w > 0)
718             {
719               if (width + w > x1 - x)
720                 break;
721               width += w;
722             }
723           ofs += mblen;
724         }
725       n = ofs;
726       if (n == 0)
727         return;
728     }
729
730   if (!a->emphasis || (!bold && !underline))
731     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
732   else
733     {
734       size_t n_out;
735       size_t ofs;
736       char *out;
737       int mblen;
738
739       /* First figure out how many bytes need to be inserted. */
740       n_out = n;
741       for (ofs = 0; ofs < n; ofs += mblen)
742         {
743           ucs4_t uc;
744           int w;
745
746           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
747           w = uc_width (uc, "UTF-8");
748
749           if (w > 0)
750             {
751               if (bold)
752                 n_out += 1 + mblen;
753               if (underline)
754                 n_out += 2;
755             }
756         }
757
758       /* Then insert them. */
759       out = ascii_reserve (a, y, x, x + width, n_out);
760       for (ofs = 0; ofs < n; ofs += mblen)
761         {
762           ucs4_t uc;
763           int w;
764
765           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
766           w = uc_width (uc, "UTF-8");
767
768           if (w > 0)
769             {
770               if (bold)
771                 {
772                   out = mempcpy (out, string + ofs, mblen);
773                   *out++ = '\b';
774                 }
775               if (underline)
776                 {
777                   *out++ = '_';
778                   *out++ = '\b';
779                 }
780             }
781           out = mempcpy (out, string + ofs, mblen);
782         }
783     }
784 }
785
786 static char *
787 add_footnote_markers (const char *text, const struct table_cell *cell)
788 {
789   struct string s = DS_EMPTY_INITIALIZER;
790   ds_put_cstr (&s, text);
791   for (size_t i = 0; i < cell->n_footnotes; i++)
792     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
793   return ds_steal_cstr (&s);
794 }
795
796 static void
797 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
798                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
799                    int *widthp, int *heightp)
800 {
801   *widthp = 0;
802   *heightp = 0;
803
804   /* Get the basic textual contents. */
805   const char *plain_text = (cell->options & TAB_MARKUP
806                             ? output_get_text_from_markup (cell->text)
807                             : cell->text);
808
809   /* Append footnote markers if any. */
810   const char *text;
811   if (cell->n_footnotes)
812     {
813       text = add_footnote_markers (plain_text, cell);
814       if (plain_text != cell->text)
815         free (CONST_CAST (char *, plain_text));
816     }
817   else
818     text = plain_text;
819
820   /* Calculate length; if it's zero, then there's nothing to do. */
821   size_t length = strlen (text);
822   if (!length)
823     {
824       if (text != cell->text)
825         free (CONST_CAST (char *, text));
826       return;
827     }
828
829   char *breaks = xmalloc (length + 1);
830   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
831                           "UTF-8", breaks);
832   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
833                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
834
835   size_t pos = 0;
836   int bb_width = bb[H][1] - bb[H][0];
837   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
838     {
839       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
840       const char *b = breaks + pos;
841       size_t n = length - pos;
842
843       size_t last_break_ofs = 0;
844       int last_break_width = 0;
845       int width = 0;
846       size_t graph_ofs;
847       size_t ofs;
848
849       for (ofs = 0; ofs < n; )
850         {
851           ucs4_t uc;
852           int mblen;
853           int w;
854
855           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
856           if (b[ofs] == UC_BREAK_MANDATORY)
857             break;
858           else if (b[ofs] == UC_BREAK_POSSIBLE)
859             {
860               last_break_ofs = ofs;
861               last_break_width = width;
862             }
863
864           w = uc_width (uc, "UTF-8");
865           if (w > 0)
866             {
867               if (width + w > bb_width)
868                 {
869                   if (isspace (line[ofs]))
870                     break;
871                   else if (last_break_ofs != 0)
872                     {
873                       ofs = last_break_ofs;
874                       width = last_break_width;
875                       break;
876                     }
877                 }
878               width += w;
879             }
880           ofs += mblen;
881         }
882
883       /* Trim any trailing spaces off the end of the text to be drawn. */
884       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
885         if (!isspace (line[graph_ofs - 1]))
886           break;
887       width -= ofs - graph_ofs;
888
889       /* Draw text. */
890       text_draw (a, cell->style->cell_style.halign, cell->options,
891                  cell->style->font_style.bold,
892                  cell->style->font_style.underline,
893                  bb, clip, y, line, graph_ofs, width);
894
895       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
896          past any spaces past the end of the line (but not past a new-line). */
897       if (b[ofs] == UC_BREAK_MANDATORY)
898         ofs++;
899       else
900         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
901           ofs++;
902
903       if (width > *widthp)
904         *widthp = width;
905       ++*heightp;
906       pos += ofs;
907     }
908
909   free (breaks);
910   if (text != cell->text)
911     free (CONST_CAST (char *, text));
912 }
913
914 void
915 ascii_test_write (struct output_driver *driver,
916                   const char *s, int x, int y, bool bold, bool underline)
917 {
918   struct ascii_driver *a = ascii_driver_cast (driver);
919   int bb[TABLE_N_AXES][2];
920   int width, height;
921
922   if (a->file == NULL && !ascii_open_page (a))
923     return;
924
925   struct area_style style = {
926     .cell_style.halign = TABLE_HALIGN_LEFT,
927     .font_style.bold = bold,
928     .font_style.underline = underline,
929   };
930   struct table_cell cell = {
931     .text = CONST_CAST (char *, s),
932     .style = &style,
933   };
934
935   bb[TABLE_HORZ][0] = x;
936   bb[TABLE_HORZ][1] = a->width;
937   bb[TABLE_VERT][0] = y;
938   bb[TABLE_VERT][1] = INT_MAX;
939
940   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
941 }
942
943 void
944 ascii_test_set_length (struct output_driver *driver, int y, int length)
945 {
946   struct ascii_driver *a = ascii_driver_cast (driver);
947
948   if (a->file == NULL && !ascii_open_page (a))
949     return;
950
951   if (y < 0)
952     return;
953   u8_line_set_length (&a->lines[y], length);
954 }
955
956 void
957 ascii_test_flush (struct output_driver *driver)
958 {
959   struct ascii_driver *a = ascii_driver_cast (driver);
960
961   for (size_t i = a->allocated_lines; i-- > 0; )
962     if (a->lines[i].width)
963       {
964         ascii_output_lines (a, i + 1);
965         break;
966       }
967 }
968 \f
969 /* ascii_close_page () and support routines. */
970
971 #if HAVE_DECL_SIGWINCH
972 static struct ascii_driver *the_driver;
973
974 static void
975 winch_handler (int signum UNUSED)
976 {
977   update_page_size (the_driver, false);
978 }
979 #endif
980
981 static bool
982 ascii_open_page (struct ascii_driver *a)
983 {
984   if (a->error)
985     return false;
986
987   if (a->file == NULL)
988     {
989       a->file = fn_open (a->handle, a->append ? "a" : "w");
990       if (a->file != NULL)
991         {
992           if ( isatty (fileno (a->file)))
993             {
994 #if HAVE_DECL_SIGWINCH
995               struct sigaction action;
996               sigemptyset (&action.sa_mask);
997               action.sa_flags = 0;
998               action.sa_handler = winch_handler;
999               the_driver = a;
1000               sigaction (SIGWINCH, &action, NULL);
1001 #endif
1002               a->auto_width = true;
1003             }
1004         }
1005       else
1006         {
1007           msg_error (errno, _("ascii: opening output file `%s'"),
1008                      fh_get_file_name (a->handle));
1009           a->error = true;
1010           return false;
1011         }
1012     }
1013
1014   return true;
1015 }