Add support for reading and writing SPV files.
[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       if (y < a->allocated_lines)
420         {
421           struct u8_line *line = &a->lines[y];
422
423           while (ds_chomp_byte (&line->s, ' '))
424             continue;
425           fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
426           u8_line_clear (&a->lines[y]);
427         }
428       putc ('\n', a->file);
429     }
430 }
431
432 static void
433 ascii_output_table_item (struct ascii_driver *a,
434                          const struct table_item *table_item)
435 {
436   struct render_pager *p;
437
438   update_page_size (a, false);
439
440   if (a->file)
441     putc ('\n', a->file);
442   else if (!ascii_open_page (a))
443     return;
444
445   p = render_pager_create (&a->params, table_item);
446   for (int i = 0; render_pager_has_next (p); i++)
447     {
448       if (i)
449         putc ('\n', a->file);
450       ascii_output_lines (a, render_pager_draw_next (p, INT_MAX));
451     }
452   render_pager_destroy (p);
453 }
454
455 static void
456 ascii_output_table_item_unref (struct ascii_driver *a,
457                                struct table_item *table_item)
458 {
459   ascii_output_table_item (a, table_item);
460   table_item_unref (table_item);
461 }
462
463 static void
464 ascii_output_text (struct ascii_driver *a, const char *text)
465 {
466   struct pivot_table *pt = pivot_table_create_for_text (
467     NULL, pivot_value_new_user_text (text, -1));
468   ascii_output_table_item_unref (a, table_item_create (pt));
469 }
470
471 static void
472 ascii_submit (struct output_driver *driver,
473               const struct output_item *output_item)
474 {
475   struct ascii_driver *a = ascii_driver_cast (driver);
476
477   if (a->error)
478     return;
479
480   if (is_table_item (output_item))
481     ascii_output_table_item (a, to_table_item (output_item));
482 #ifdef HAVE_CAIRO
483   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
484     {
485       struct chart_item *chart_item = to_chart_item (output_item);
486       char *file_name;
487
488       file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
489                                      a->chart_cnt++,
490                                      &a->fg,
491                                      &a->bg);
492       if (file_name != NULL)
493         {
494           struct text_item *text_item;
495
496           text_item = text_item_create_format (
497             TEXT_ITEM_LOG, _("See %s for a chart."), file_name);
498
499           ascii_submit (driver, &text_item->output_item);
500           text_item_unref (text_item);
501           free (file_name);
502         }
503     }
504 #endif  /* HAVE_CAIRO */
505   else if (is_text_item (output_item))
506     {
507       const struct text_item *text_item = to_text_item (output_item);
508       enum text_item_type type = text_item_get_type (text_item);
509
510       if (type != TEXT_ITEM_PAGE_TITLE && type != TEXT_ITEM_EJECT_PAGE)
511         ascii_output_table_item_unref (
512           a, text_item_to_table_item (text_item_ref (text_item)));
513     }
514   else if (is_message_item (output_item))
515     {
516       const struct message_item *message_item = to_message_item (output_item);
517       char *s = msg_to_string (message_item_get_msg (message_item));
518       ascii_output_text (a, s);
519       free (s);
520     }
521 }
522
523 const struct output_driver_factory txt_driver_factory =
524   { "txt", "-", ascii_create };
525 const struct output_driver_factory list_driver_factory =
526   { "list", "-", ascii_create };
527
528 static const struct output_driver_class ascii_driver_class =
529   {
530     "text",
531     ascii_destroy,
532     ascii_submit,
533     ascii_flush,
534   };
535 \f
536 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
537                             int n);
538 static void ascii_layout_cell (struct ascii_driver *,
539                                const struct table_cell *,
540                                int bb[TABLE_N_AXES][2],
541                                int clip[TABLE_N_AXES][2],
542                                int *width, int *height);
543
544 static void
545 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
546                  enum render_line_style styles[TABLE_N_AXES][2],
547                  struct cell_color colors[TABLE_N_AXES][2] UNUSED)
548 {
549   struct ascii_driver *a = a_;
550   char mbchar[6];
551   int x0, y0, x1, y1;
552   ucs4_t uc;
553   int mblen;
554   int x, y;
555
556   /* Clip to the page. */
557   x0 = MAX (bb[H][0], 0);
558   y0 = MAX (bb[V][0], 0);
559   x1 = MIN (bb[H][1], a->width);
560   y1 = bb[V][1];
561   if (x1 <= 0 || y1 <= 0 || x0 >= a->width)
562     return;
563
564   /* Draw. */
565   uc = a->box[make_box_index (styles[V][0], styles[V][1],
566                               styles[H][0], styles[H][1])];
567   mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
568   for (y = y0; y < y1; y++)
569     {
570       char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
571       for (x = x0; x < x1; x++)
572         {
573           memcpy (p, mbchar, mblen);
574           p += mblen;
575         }
576     }
577 }
578
579 static void
580 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
581                           int *min_width, int *max_width)
582 {
583   struct ascii_driver *a = a_;
584   int bb[TABLE_N_AXES][2];
585   int clip[TABLE_N_AXES][2];
586   int h;
587
588   bb[H][0] = 0;
589   bb[H][1] = INT_MAX;
590   bb[V][0] = 0;
591   bb[V][1] = INT_MAX;
592   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
593   ascii_layout_cell (a, cell, bb, clip, max_width, &h);
594
595   if (cell->n_footnotes || strchr (cell->text, ' '))
596     {
597       bb[H][1] = 1;
598       ascii_layout_cell (a, cell, bb, clip, min_width, &h);
599     }
600   else
601     *min_width = *max_width;
602 }
603
604 static int
605 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
606 {
607   struct ascii_driver *a = a_;
608   int bb[TABLE_N_AXES][2];
609   int clip[TABLE_N_AXES][2];
610   int w, h;
611
612   bb[H][0] = 0;
613   bb[H][1] = width;
614   bb[V][0] = 0;
615   bb[V][1] = INT_MAX;
616   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
617   ascii_layout_cell (a, cell, bb, clip, &w, &h);
618   return h;
619 }
620
621 static void
622 ascii_draw_cell (void *a_, const struct table_cell *cell, int color_idx UNUSED,
623                  int bb[TABLE_N_AXES][2],
624                  int spill[TABLE_N_AXES][2] UNUSED,
625                  int clip[TABLE_N_AXES][2])
626 {
627   struct ascii_driver *a = a_;
628   int w, h;
629
630   ascii_layout_cell (a, cell, bb, clip, &w, &h);
631 }
632
633 static char *
634 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
635 {
636   if (y >= a->allocated_lines)
637     {
638       size_t new_alloc = MAX (25, a->allocated_lines);
639       while (new_alloc <= y)
640         new_alloc = xtimes (new_alloc, 2);
641       a->lines = xnrealloc (a->lines, new_alloc, sizeof *a->lines);
642       for (size_t i = a->allocated_lines; i < new_alloc; i++)
643         u8_line_init (&a->lines[i]);
644       a->allocated_lines = new_alloc;
645     }
646   return u8_line_reserve (&a->lines[y], x0, x1, n);
647 }
648
649 static void
650 text_draw (struct ascii_driver *a, enum table_halign halign, int options,
651            bool bold, bool underline,
652            int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
653            int y, const uint8_t *string, int n, size_t width)
654 {
655   int x0 = MAX (0, clip[H][0]);
656   int y0 = MAX (0, clip[V][0]);
657   int x1 = MIN (a->width, clip[H][1]);
658   int y1 = clip[V][1];
659   int x;
660
661   if (y < y0 || y >= y1)
662     return;
663
664   switch (table_halign_interpret (halign, options & TAB_NUMERIC))
665     {
666     case TABLE_HALIGN_LEFT:
667       x = bb[H][0];
668       break;
669     case TABLE_HALIGN_CENTER:
670       x = (bb[H][0] + bb[H][1] - width + 1) / 2;
671       break;
672     case TABLE_HALIGN_RIGHT:
673     case TABLE_HALIGN_DECIMAL:
674       x = bb[H][1] - width;
675       break;
676     default:
677       NOT_REACHED ();
678     }
679   if (x >= x1)
680     return;
681
682   while (x < x0)
683     {
684       ucs4_t uc;
685       int mblen;
686       int w;
687
688       if (n == 0)
689         return;
690       mblen = u8_mbtouc (&uc, string, n);
691
692       string += mblen;
693       n -= mblen;
694
695       w = uc_width (uc, "UTF-8");
696       if (w > 0)
697         {
698           x += w;
699           width -= w;
700         }
701     }
702   if (n == 0)
703     return;
704
705   if (x + width > x1)
706     {
707       int ofs;
708
709       ofs = width = 0;
710       for (ofs = 0; ofs < n; )
711         {
712           ucs4_t uc;
713           int mblen;
714           int w;
715
716           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
717
718           w = uc_width (uc, "UTF-8");
719           if (w > 0)
720             {
721               if (width + w > x1 - x)
722                 break;
723               width += w;
724             }
725           ofs += mblen;
726         }
727       n = ofs;
728       if (n == 0)
729         return;
730     }
731
732   if (!a->emphasis || (!bold && !underline))
733     memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
734   else
735     {
736       size_t n_out;
737       size_t ofs;
738       char *out;
739       int mblen;
740
741       /* First figure out how many bytes need to be inserted. */
742       n_out = n;
743       for (ofs = 0; ofs < n; ofs += mblen)
744         {
745           ucs4_t uc;
746           int w;
747
748           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
749           w = uc_width (uc, "UTF-8");
750
751           if (w > 0)
752             {
753               if (bold)
754                 n_out += 1 + mblen;
755               if (underline)
756                 n_out += 2;
757             }
758         }
759
760       /* Then insert them. */
761       out = ascii_reserve (a, y, x, x + width, n_out);
762       for (ofs = 0; ofs < n; ofs += mblen)
763         {
764           ucs4_t uc;
765           int w;
766
767           mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
768           w = uc_width (uc, "UTF-8");
769
770           if (w > 0)
771             {
772               if (bold)
773                 {
774                   out = mempcpy (out, string + ofs, mblen);
775                   *out++ = '\b';
776                 }
777               if (underline)
778                 {
779                   *out++ = '_';
780                   *out++ = '\b';
781                 }
782             }
783           out = mempcpy (out, string + ofs, mblen);
784         }
785     }
786 }
787
788 static char *
789 add_footnote_markers (const char *text, const struct table_cell *cell)
790 {
791   struct string s = DS_EMPTY_INITIALIZER;
792   ds_put_cstr (&s, text);
793   for (size_t i = 0; i < cell->n_footnotes; i++)
794     ds_put_format (&s, "[%s]", cell->footnotes[i]->marker);
795   return ds_steal_cstr (&s);
796 }
797
798 static void
799 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
800                    int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
801                    int *widthp, int *heightp)
802 {
803   *widthp = 0;
804   *heightp = 0;
805
806   /* Get the basic textual contents. */
807   const char *plain_text = (cell->options & TAB_MARKUP
808                             ? output_get_text_from_markup (cell->text)
809                             : cell->text);
810
811   /* Append footnote markers if any. */
812   const char *text;
813   if (cell->n_footnotes)
814     {
815       text = add_footnote_markers (plain_text, cell);
816       if (plain_text != cell->text)
817         free (CONST_CAST (char *, plain_text));
818     }
819   else
820     text = plain_text;
821
822   /* Calculate length; if it's zero, then there's nothing to do. */
823   size_t length = strlen (text);
824   if (!length)
825     {
826       if (text != cell->text)
827         free (CONST_CAST (char *, text));
828       return;
829     }
830
831   char *breaks = xmalloc (length + 1);
832   u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
833                           "UTF-8", breaks);
834   breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
835                     ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
836
837   size_t pos = 0;
838   int bb_width = bb[H][1] - bb[H][0];
839   for (int y = bb[V][0]; y < bb[V][1] && pos < length; y++)
840     {
841       const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
842       const char *b = breaks + pos;
843       size_t n = length - pos;
844
845       size_t last_break_ofs = 0;
846       int last_break_width = 0;
847       int width = 0;
848       size_t graph_ofs;
849       size_t ofs;
850
851       for (ofs = 0; ofs < n; )
852         {
853           ucs4_t uc;
854           int mblen;
855           int w;
856
857           mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
858           if (b[ofs] == UC_BREAK_MANDATORY)
859             break;
860           else if (b[ofs] == UC_BREAK_POSSIBLE)
861             {
862               last_break_ofs = ofs;
863               last_break_width = width;
864             }
865
866           w = uc_width (uc, "UTF-8");
867           if (w > 0)
868             {
869               if (width + w > bb_width)
870                 {
871                   if (isspace (line[ofs]))
872                     break;
873                   else if (last_break_ofs != 0)
874                     {
875                       ofs = last_break_ofs;
876                       width = last_break_width;
877                       break;
878                     }
879                 }
880               width += w;
881             }
882           ofs += mblen;
883         }
884
885       /* Trim any trailing spaces off the end of the text to be drawn. */
886       for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
887         if (!isspace (line[graph_ofs - 1]))
888           break;
889       width -= ofs - graph_ofs;
890
891       /* Draw text. */
892       text_draw (a, cell->style->cell_style.halign, cell->options,
893                  cell->style->font_style.bold,
894                  cell->style->font_style.underline,
895                  bb, clip, y, line, graph_ofs, width);
896
897       /* If a new-line ended the line, just skip the new-line.  Otherwise, skip
898          past any spaces past the end of the line (but not past a new-line). */
899       if (b[ofs] == UC_BREAK_MANDATORY)
900         ofs++;
901       else
902         while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
903           ofs++;
904
905       if (width > *widthp)
906         *widthp = width;
907       ++*heightp;
908       pos += ofs;
909     }
910
911   free (breaks);
912   if (text != cell->text)
913     free (CONST_CAST (char *, text));
914 }
915
916 void
917 ascii_test_write (struct output_driver *driver,
918                   const char *s, int x, int y, bool bold, bool underline)
919 {
920   struct ascii_driver *a = ascii_driver_cast (driver);
921   int bb[TABLE_N_AXES][2];
922   int width, height;
923
924   if (a->file == NULL && !ascii_open_page (a))
925     return;
926
927   struct area_style style = {
928     .cell_style.halign = TABLE_HALIGN_LEFT,
929     .font_style.bold = bold,
930     .font_style.underline = underline,
931   };
932   struct table_cell cell = {
933     .text = CONST_CAST (char *, s),
934     .style = &style,
935   };
936
937   bb[TABLE_HORZ][0] = x;
938   bb[TABLE_HORZ][1] = a->width;
939   bb[TABLE_VERT][0] = y;
940   bb[TABLE_VERT][1] = INT_MAX;
941
942   ascii_layout_cell (a, &cell, bb, bb, &width, &height);
943 }
944
945 void
946 ascii_test_set_length (struct output_driver *driver, int y, int length)
947 {
948   struct ascii_driver *a = ascii_driver_cast (driver);
949
950   if (a->file == NULL && !ascii_open_page (a))
951     return;
952
953   if (y < 0)
954     return;
955   u8_line_set_length (&a->lines[y], length);
956 }
957
958 void
959 ascii_test_flush (struct output_driver *driver)
960 {
961   struct ascii_driver *a = ascii_driver_cast (driver);
962
963   for (size_t i = a->allocated_lines; i-- > 0; )
964     if (a->lines[i].width)
965       {
966         ascii_output_lines (a, i + 1);
967         break;
968       }
969 }
970 \f
971 /* ascii_close_page () and support routines. */
972
973 #if HAVE_DECL_SIGWINCH
974 static struct ascii_driver *the_driver;
975
976 static void
977 winch_handler (int signum UNUSED)
978 {
979   update_page_size (the_driver, false);
980 }
981 #endif
982
983 static bool
984 ascii_open_page (struct ascii_driver *a)
985 {
986   if (a->error)
987     return false;
988
989   if (a->file == NULL)
990     {
991       a->file = fn_open (a->handle, a->append ? "a" : "w");
992       if (a->file != NULL)
993         {
994           if ( isatty (fileno (a->file)))
995             {
996 #if HAVE_DECL_SIGWINCH
997               struct sigaction action;
998               sigemptyset (&action.sa_mask);
999               action.sa_flags = 0;
1000               action.sa_handler = winch_handler;
1001               the_driver = a;
1002               sigaction (SIGWINCH, &action, NULL);
1003 #endif
1004               a->auto_width = true;
1005             }
1006         }
1007       else
1008         {
1009           msg_error (errno, _("ascii: opening output file `%s'"),
1010                      fh_get_file_name (a->handle));
1011           a->error = true;
1012           return false;
1013         }
1014     }
1015
1016   return true;
1017 }